// 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 "testcharging.h"
#include
TestCharging::TestCharging(QObject *parent):
EnergyTestBase(parent)
{
}
void TestCharging::testAddCharger()
{
// Add
QUuid chargerThingId = addCharger("ABC", 16);
QVERIFY2(!chargerThingId.isNull(), "Did not receive valid ThingId");
// Remove
QVariant response = removeDevice(chargerThingId);
verifyThingError(response);
}
void TestCharging::testAddSimpleCharger()
{
// Add
QUuid chargerThingId = addSimpleCharger(20);
QVERIFY2(!chargerThingId.isNull(), "Did not receive valid ThingId");
// Remove
QVariant response = removeDevice(chargerThingId);
verifyThingError(response);
}
void TestCharging::testAddCar()
{
// Add
QUuid carThingId = addCar();
QVERIFY2(!carThingId.isNull(), "Did not receive valid ThingId");
// Remove
QVariant response = removeDevice(carThingId);
verifyThingError(response);
}
void TestCharging::testAddMeter()
{
QVariant response, notification;
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;
// Make sure there is no root meter
response = injectAndWait("Energy.GetRootMeter");
QVERIFY(response.toMap().value("params").toMap().isEmpty());
QVERIFY(response.toMap().value("status").toString() == "success");
// Add mock meter
QUuid meterThingId = addMeter();
QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId");
// Check notification Energy.RootMeterChanged containing our added meter thing id
if (notificationSpy.count() == 0) notificationSpy.wait();
notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
QCOMPARE(notification.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
// Make sure this is our root meter now
response = injectAndWait("Energy.GetRootMeter");
QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
// Undo all steps
// Remove meter
notificationSpy.clear();
response = removeDevice(meterThingId);
qDebug() << response.toMap();
// Check notification Energy.RootMeterChanged containing our added meter thing id
if (notificationSpy.count() == 0) notificationSpy.wait();
notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
QVERIFY(notification.toMap().value("params").toMap().isEmpty());
// Make sure there is no root meter
response = injectAndWait("Energy.GetRootMeter");
QVERIFY(response.toMap().value("params").toMap().isEmpty());
QVERIFY(response.toMap().value("status").toString() == "success");
}
void TestCharging::getSetPhaseLimits()
{
QVariant response, notification;
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;
uint phasePowerLimit = 0;
uint newPhasePowerLimit = 30;
response = injectAndWait("NymeaEnergy.GetPhasePowerLimit");
phasePowerLimit = response.toMap().value("params").toMap().value("phasePowerLimit").toUInt();
// Initially 0 since we have no things at all set up
if (phasePowerLimit == newPhasePowerLimit) {
newPhasePowerLimit++;
}
notificationSpy.clear();
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", newPhasePowerLimit}}));
QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
if (notificationSpy.count() == 0) notificationSpy.wait();
notification = checkNotification(notificationSpy, "NymeaEnergy.PhasePowerLimitChanged");
QCOMPARE(notification.toMap().value("params").toMap().value("phasePowerLimit").toUInt(), newPhasePowerLimit);
response = injectAndWait("NymeaEnergy.GetPhasePowerLimit");
phasePowerLimit = response.toMap().value("params").toMap().value("phasePowerLimit").toUInt();
QCOMPARE(phasePowerLimit, newPhasePowerLimit);
notificationSpy.clear();
newPhasePowerLimit = 30000;
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", newPhasePowerLimit}}));
QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
if (notificationSpy.count() == 0) notificationSpy.wait();
notification = checkNotification(notificationSpy, "NymeaEnergy.PhasePowerLimitChanged");
QCOMPARE(notification.toMap().value("params").toMap().value("phasePowerLimit").toUInt(), newPhasePowerLimit);
response = injectAndWait("NymeaEnergy.GetPhasePowerLimit");
phasePowerLimit = response.toMap().value("params").toMap().value("phasePowerLimit").toUInt();
QCOMPARE(phasePowerLimit, newPhasePowerLimit);
}
void TestCharging::getSetLockOnUnplug_data()
{
QTest::addColumn("lockOnUnplug");
QTest::newRow("Enable") << true;
QTest::newRow("Disable") << false;
}
void TestCharging::getSetLockOnUnplug()
{
QFETCH(bool, lockOnUnplug);
QVariantMap params;
QVariant response, notification;
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;
// Set initial lockOnUnplug to oposit, so we get the changed notification for sure
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
params.insert("lockOnUnplug", !lockOnUnplug);
response = injectAndWait("NymeaEnergy.SetLockOnUnplug", params);
verifyEnergyError(response);
// Set actual lockOnUnplug
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
params.insert("lockOnUnplug", lockOnUnplug);
response = injectAndWait("NymeaEnergy.SetLockOnUnplug", params);
verifyEnergyError(response);
// If the value has changed, verify the notification
if (notificationSpy.count() == 0) notificationSpy.wait();
notification = checkNotification(notificationSpy, "NymeaEnergy.LockOnUnplugChanged");
QCOMPARE(notification.toMap().value("params").toMap().value("lockOnUnplug").toDouble(), lockOnUnplug);
// Get acquisition tolerance
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
response = injectAndWait("NymeaEnergy.GetLockOnUnplug", params);
QVERIFY2(response.toMap().value("params").toMap().contains("lockOnUnplug"), "Did not get aquisition tolerance.");
QCOMPARE(response.toMap().value("params").toMap().value("lockOnUnplug").toBool(), lockOnUnplug);
}
void TestCharging::getSetAcquisitionTolerance_data()
{
QTest::addColumn("initialAcquisitionTolerance");
QTest::addColumn("acquisitionTolerance");
QTest::addColumn("expectedError");
QTest::newRow("Default") << 0.1 << 0.5 << EnergyManager::EnergyErrorNoError;
QTest::newRow("Unchanged") << 0.5 << 0.5 << EnergyManager::EnergyErrorNoError;
QTest::newRow("< 0.0") << 0.5 << -1.0 << EnergyManager::EnergyErrorInvalidParameter;
QTest::newRow("> 1.0") << 0.5 << 2.0 << EnergyManager::EnergyErrorInvalidParameter;
}
void TestCharging::getSetAcquisitionTolerance()
{
QFETCH(double, initialAcquisitionTolerance);
QFETCH(double, acquisitionTolerance);
QFETCH(EnergyManager::EnergyError, expectedError);
QVariantMap params;
QVariant response, notification;
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;
// Set initial acquisition tolerance
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
params.insert("acquisitionTolerance", initialAcquisitionTolerance);
response = injectAndWait("NymeaEnergy.SetAcquisitionTolerance", params);
verifyEnergyError(response);
bool testNotificationReceived = (initialAcquisitionTolerance != acquisitionTolerance);
// Set acquisition tolerance
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
params.insert("acquisitionTolerance", acquisitionTolerance);
response = injectAndWait("NymeaEnergy.SetAcquisitionTolerance", params);
verifyEnergyError(response, expectedError);
// If the value has changed, verify the notification
if (testNotificationReceived && expectedError == EnergyManager::EnergyErrorNoError) {
if (notificationSpy.count() == 0) notificationSpy.wait();
notification = checkNotification(notificationSpy, "NymeaEnergy.AcquisitionToleranceChanged");
QCOMPARE(notification.toMap().value("params").toMap().value("acquisitionTolerance").toDouble(), acquisitionTolerance);
}
// Get acquisition tolerance
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
response = injectAndWait("NymeaEnergy.GetAcquisitionTolerance", params);
QVERIFY2(response.toMap().value("params").toMap().contains("acquisitionTolerance"), "Did not get aquisition tolerance.");
}
void TestCharging::getSetBatteryLevelConsideration_data()
{
QTest::addColumn("initialBatteryLevelConsideration");
QTest::addColumn("batteryLevelConsideration");
QTest::addColumn("expectedError");
QTest::newRow("Default") << 0.1 << 0.5 << EnergyManager::EnergyErrorNoError;
QTest::newRow("Unchanged") << 0.5 << 0.5 << EnergyManager::EnergyErrorNoError;
QTest::newRow("< 0.0") << 0.5 << -1.0 << EnergyManager::EnergyErrorInvalidParameter;
QTest::newRow("> 1.0") << 0.5 << 2.0 << EnergyManager::EnergyErrorInvalidParameter;
}
void TestCharging::getSetBatteryLevelConsideration()
{
QFETCH(double, initialBatteryLevelConsideration);
QFETCH(double, batteryLevelConsideration);
QFETCH(EnergyManager::EnergyError, expectedError);
QVariantMap params;
QVariant response, notification;
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;
// Set initial acquisition tolerance
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
params.insert("batteryLevelConsideration", initialBatteryLevelConsideration);
response = injectAndWait("NymeaEnergy.SetBatteryLevelConsideration", params);
verifyEnergyError(response);
bool testNotificationReceived = (initialBatteryLevelConsideration != batteryLevelConsideration);
// Set acquisition tolerance
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
params.insert("batteryLevelConsideration", batteryLevelConsideration);
response = injectAndWait("NymeaEnergy.SetBatteryLevelConsideration", params);
verifyEnergyError(response, expectedError);
// If the value has changed, verify the notification
if (testNotificationReceived && expectedError == EnergyManager::EnergyErrorNoError) {
if (notificationSpy.count() == 0) notificationSpy.wait();
notification = checkNotification(notificationSpy, "NymeaEnergy.BatteryLevelConsiderationChanged");
QCOMPARE(notification.toMap().value("params").toMap().value("batteryLevelConsideration").toDouble(), batteryLevelConsideration);
}
// Get acquisition tolerance
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
response = injectAndWait("NymeaEnergy.GetBatteryLevelConsideration", params);
QVERIFY2(response.toMap().value("params").toMap().contains("batteryLevelConsideration"), "Did not get battery level consideration.");
}
void TestCharging::addChargingInfo_data()
{
QTest::addColumn("addingCharger");
QTest::addColumn("addingCar");
QTest::addColumn("carValid");
QTest::addColumn("chargingMode");
QTest::addColumn("endDateTime");
QTest::addColumn>("repeatDays");
QTest::addColumn("targetPercentage");
QTest::addColumn("acquisitionTolerance");
QTest::addColumn("jsonSuccess");
QTest::addColumn("expectedError");
QDateTime endDateTime = QDateTime::currentDateTime().addDays(1);
endDateTime.setTime(QTime(7, 0));
QTest::newRow("Default") << true << true << true << "ChargingModeNormal" << endDateTime << QList{} << 100 << 0.5 << true << EnergyManager::EnergyErrorNoError;
QTest::newRow("Default Eco") << true << true << true << "ChargingModeEco" << endDateTime << QList{} << 100 << 0.5 << true << EnergyManager::EnergyErrorNoError;
QTest::newRow("Invalid mode") << true << true << true << "ChargingModeBlablaa" << endDateTime << QList{} << 100 << 0.5 << false << EnergyManager::EnergyErrorNoError;
QTest::newRow("Invalid charger") << false << true << true << "ChargingModeNormal" << endDateTime << QList{} << 100 << 0.5 << true << EnergyManager::EnergyErrorInvalidParameter;
QTest::newRow("with repeatdays") << true << false << true << "ChargingModeNormal" << endDateTime << QList{1,3,6} << 100 << 0.5 << true << EnergyManager::EnergyErrorNoError;
QTest::newRow("Invalid repeatDays") << true << false << true << "ChargingModeNormal" << endDateTime << QList{0,1,2} << 100 << 0.5 << true << EnergyManager::EnergyErrorInvalidParameter;
QTest::newRow("Invalid car") << true << true << false << "ChargingModeEco" << endDateTime << QList{} << 100 << 0.5 << true << EnergyManager::EnergyErrorInvalidParameter;
QTest::newRow("Invalid %") << true << true << true << "ChargingModeNormal" << endDateTime << QList{} << 200 << 0.5 << true << EnergyManager::EnergyErrorInvalidParameter;
}
void TestCharging::addChargingInfo()
{
QFETCH(bool, addingCharger);
QFETCH(bool, addingCar);
QFETCH(bool, carValid);
QFETCH(QString, chargingMode);
QFETCH(QDateTime, endDateTime);
QFETCH(QList, repeatDays);
QFETCH(int, targetPercentage);
QFETCH(double, acquisitionTolerance);
QFETCH(bool, jsonSuccess);
QFETCH(EnergyManager::EnergyError, expectedError);
QVariant response, notification;
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;
// Set phase power limit
uint phasePowerLimit = 25000;
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
verifyEnergyError(response);
removeDevices();
// Add mock meter
QUuid meterThingId = addMeter();
QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
QCOMPARE(notification.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
// Make sure this is our root meter now
response = injectAndWait("Energy.GetRootMeter");
QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
notificationSpy.clear();
// Add the charger
QUuid evChargerId;
if (addingCharger) {
evChargerId = addCharger("ABC", 16);
QVERIFY2(!evChargerId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
checkNotification(notificationSpy, "Integrations.ThingAdded");
} else {
evChargerId = QUuid::createUuid();
}
// Add the car
QUuid assignedCarId;
if (addingCar) {
assignedCarId = addCar();
QVERIFY2(!assignedCarId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
checkNotification(notificationSpy, "Integrations.ThingAdded");
}
if (!carValid)
assignedCarId = QUuid::createUuid();
// Set aquisition tolerance
QVariantMap params;
params.insert("acquisitionTolerance", acquisitionTolerance);
response = injectAndWait("NymeaEnergy.SetAcquisitionTolerance", params);
verifyEnergyError(response);
notificationSpy.clear();
// Set charging info with our charger and car
QVariantMap chargingInfoMap;
chargingInfoMap.insert("evChargerId", evChargerId);
if (!assignedCarId.isNull())
chargingInfoMap.insert("assignedCarId", assignedCarId);
chargingInfoMap.insert("chargingMode", chargingMode);
chargingInfoMap.insert("endDateTime", endDateTime.toMSecsSinceEpoch() / 1000);
QVariantList repDaysList;
foreach (int day, repeatDays) {
repDaysList.append(day);
}
chargingInfoMap.insert("repeatDays", repDaysList);
chargingInfoMap.insert("targetPercentage", targetPercentage);
//qCDebug(dcTests()) << "Sending charging info:" << qUtf8Printable(QJsonDocument::fromVariant(chargingInfoMap).toJson(QJsonDocument::Indented));
response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}}));
if (jsonSuccess) {
QCOMPARE(response.toMap().value("status").toString(), QString("success"));
verifyEnergyError(response, expectedError);
// Get the charging infos
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
response = injectAndWait("NymeaEnergy.GetChargingInfos", params);
QVERIFY2(response.toMap().value("params").toMap().contains("chargingInfos"), "Did not get charginginfos.");
QVariantList chargingInfos = response.toMap().value("params").toMap().value("chargingInfos").toList();
qDebug() << chargingInfos;
if (expectedError == EnergyManager::EnergyErrorNoError) {
QCOMPARE(chargingInfos.count(), 1);
}
} else {
QCOMPARE(response.toMap().value("status").toString(), QString("error"));
}
}
void TestCharging::testEcoMode_data()
{
// Houshold info
QTest::addColumn("phasePowerLimit");
// ChargingInfo
QTest::addColumn("acquisitionTolerance");
QTest::addColumn("targetPercentage");
QTest::addColumn("hoursLeftToTargetTime");
// Car information and states
QTest::addColumn("carBatteryLevel");
QTest::addColumn("carCapacity");
QTest::addColumn("carMinChargingCurrent");
QTest::addColumn("carPhaseCount");
// Charger information and states
QTest::addColumn("chargerConnected");
QTest::addColumn("chargerPower");
QTest::addColumn("chargerPhases");
QTest::addColumn("chargerMaxChargingCurrent");
QTest::addColumn("chargerMaxChargingCurrentMaxValue");
// Current meter power states
QTest::addColumn("meterPhasesPower");
// Desired result on the evCharger
QTest::addColumn("expectedChargerMaxChargingCurrent");
QTest::addColumn("expectedChargerPower");
QTest::newRow("Default: 1 phase, p: 4kW, 16A, ON")
<< 32 // phase limit (A)
<< 0.5 // acquisitionTolerance
<< 100 // targetPercentage
<< 6 // hoursLeftToTargetTime
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 1 // carPhaseCount
<< true // chargerConnected
<< false // chargerPower
<< "A" // chargerPhases
<< 6 // chargerMaxChargingCurrent
<< 16 //chargerMaxChargingCurrentMaxValue
<< QVariantMap({ {"A", 0000}, // W
{"B", 2000}, // W
{"C", -6000} }) // W
<< 16 // expectedChargerMaxChargingCurrent
<< true; // expectedChargerPower
QTest::newRow("Default: 1 phase, p: 2kW, 7A, ON")
<< 16 // phase limit (A)
<< 0.5 // acquisitionTolerance
<< 100 // targetPercentage
<< 6 // hoursLeftToTargetTime
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 1 // carPhaseCount
<< true // chargerConnected
<< false // chargerPower
<< "A" // chargerPhases
<< 6 // chargerMaxChargingCurrent
<< 16 //chargerMaxChargingCurrentMaxValue
<< QVariantMap({ {"A", 2000}, // W
{"B", -2000}, // W
{"C", -2000} }) // W
<< 7 // expectedChargerMaxChargingCurrent
<< true; // expectedChargerPower
QTest::newRow("Default: 1 phase, p: -6kW, 13A, ON")
<< 16 // phase limit (A)
<< 0.5 // acquisitionTolerance
<< 100 // targetPercentage
<< 6 // hoursLeftToTargetTime
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 1 // carPhaseCount
<< true // chargerConnected
<< false // chargerPower
<< "A" // chargerPhases
<< 6 // chargerMaxChargingCurrent
<< 32 //chargerMaxChargingCurrentMaxValue
<< QVariantMap({ {"A", -3000}, // W
{"B", 3000}, // W
{"C", -3000} }) // W
<< 13 // expectedChargerMaxChargingCurrent
<< true; // expectedChargerPower
QTest::newRow("Default: 2 phase, p: -8kW, 17A, ON")
<< 32 // phase limit (A)
<< 0.5 // acquisitionTolerance
<< 100 // targetPercentage
<< 6 // hoursLeftToTargetTime
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 2 // carPhaseCount
<< true // chargerConnected
<< false // chargerPower
<< "AB" // chargerPhases
<< 6 // chargerMaxChargingCurrent
<< 32 //chargerMaxChargingCurrentMaxValue
<< QVariantMap({ {"A", -4000}, // W
{"B", -5000}, // W
{"C", 1000} }) // W
<< 17 // expectedChargerMaxChargingCurrent
<< true; // expectedChargerPower
QTest::newRow("Default: 3 phase, p: -3kW, 6A, ON")
<< 32 // phase limit (A)
<< 0.5 // acquisitionTolerance
<< 100 // targetPercentage
<< 6 // hoursLeftToTargetTime
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 3 // carPhaseCount
<< true // chargerConnected
<< false // chargerPower
<< "ABC" // chargerPhases
<< 6 // chargerMaxChargingCurrent
<< 32 //chargerMaxChargingCurrentMaxValue
<< QVariantMap({ {"A", -1000}, // W
{"B", -1000}, // W
{"C", -1000} }) // W
<< 6 // expectedChargerMaxChargingCurrent
<< true; // expectedChargerPower
}
void TestCharging::testEcoMode()
{
QFETCH(int, phasePowerLimit);
QFETCH(double, acquisitionTolerance);
QFETCH(int, targetPercentage);
QFETCH(int, hoursLeftToTargetTime);
QFETCH(int, carBatteryLevel);
QFETCH(int, carCapacity);
QFETCH(int, carMinChargingCurrent);
QFETCH(int, carPhaseCount);
QFETCH(bool, chargerConnected);
QFETCH(bool, chargerPower);
QFETCH(QString, chargerPhases);
QFETCH(int, chargerMaxChargingCurrent);
QFETCH(int, chargerMaxChargingCurrentMaxValue);
QFETCH(QVariantMap, meterPhasesPower);
QFETCH(int, expectedChargerMaxChargingCurrent);
QFETCH(bool, expectedChargerPower);
QVariantMap params;
QVariant response, notification;
QNetworkReply *reply = nullptr;
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;
// Set phase power limit
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
// Add mock meter
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid meterThingId = addMeter();
QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
QCOMPARE(notification.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
// Make sure this is our root meter now
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
response = injectAndWait("Energy.GetRootMeter");
QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
notificationSpy.clear();
// Set the meter values first so we have the desired execution o charging info changed, and not an extra iteration with 0 W on root meter
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setMeterStates(meterPhasesPower);
QSignalSpy setStatesReplySpy(reply, &QNetworkReply::finished);
if (setStatesReplySpy.count() == 0) setStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// No evaluation, there is no meter configured yet
// Add the charger
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid evChargerId = addCharger(chargerPhases, chargerMaxChargingCurrentMaxValue);
QVERIFY2(!evChargerId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
checkNotification(notificationSpy, "Integrations.ThingAdded");
// Add the car
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid assignedCarId = addCar();
QVERIFY2(!assignedCarId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
checkNotification(notificationSpy, "Integrations.ThingAdded");
// Set aquisition tolerance
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
params.insert("acquisitionTolerance", acquisitionTolerance);
response = injectAndWait("NymeaEnergy.SetAcquisitionTolerance", params);
verifyEnergyError(response);
// Set states of car
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setCarStates(carBatteryLevel, carCapacity, carMinChargingCurrent, carPhaseCount);
QSignalSpy setCarStatesReplySpy(reply, &QNetworkReply::finished);
if (setCarStatesReplySpy.count() == 0) setCarStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// Set states of the charger
uint effectivePhaseCount = qMin((uint)carPhaseCount, Electricity::getPhaseCount(Electricity::convertPhasesFromString(chargerPhases)));
QString usedPhases;
if (effectivePhaseCount >= 1)
usedPhases.append("A");
if (effectivePhaseCount >= 2)
usedPhases.append("B");
if (effectivePhaseCount >= 3)
usedPhases.append("C");
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setChargerStates(chargerConnected, chargerPower, true, usedPhases, chargerMaxChargingCurrent, chargerMaxChargingCurrentMaxValue);
QSignalSpy setChargerStatesReplySpy(reply, &QNetworkReply::finished);
if (setChargerStatesReplySpy.count() == 0) setChargerStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// Set charging info with our charger and car, this should trigger the evaluation
QVariantMap chargingInfoMap;
chargingInfoMap.insert("evChargerId", evChargerId);
if (!assignedCarId.isNull())
chargingInfoMap.insert("assignedCarId", assignedCarId);
chargingInfoMap.insert("chargingMode", "ChargingModeEcoWithTargetTime");
chargingInfoMap.insert("endDateTime", QDateTime::currentDateTime().addSecs(hoursLeftToTargetTime * 3600).toMSecsSinceEpoch() / 1000);
chargingInfoMap.insert("targetPercentage", targetPercentage);
response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}}));
verifyEnergyError(response);
// Verify if the charger has been set correctly
reply = getActionHistory(m_mockChargerDefaultPort);
QSignalSpy actionHistoryReplySpy(reply, &QNetworkReply::finished);
if (actionHistoryReplySpy.count() == 0) actionHistoryReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
QByteArray data = reply->readAll();
QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
QVariantList actionHistory = jsonDoc.toVariant().toList();
//qCDebug(dcTests()) << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
// Make sure we got the correct actions
if (expectedChargerMaxChargingCurrent != 0)
QVERIFY(verifyActionExecuted(actionHistory, "maxChargingCurrent"));
QVERIFY(verifyActionExecuted(actionHistory, "power"));
if (expectedChargerMaxChargingCurrent != 0)
QCOMPARE(getLastValueFromExecutedAction(actionHistory, "maxChargingCurrent", "maxChargingCurrent"), QVariant(expectedChargerMaxChargingCurrent));
QCOMPARE(getLastValueFromExecutedAction(actionHistory, "power", "power"), QVariant(expectedChargerPower));
removeDevices();
}
void TestCharging::testEcoModePhaseSwitching_data()
{
// Houshold info
QTest::addColumn("phasePowerLimit");
// ChargingInfo
QTest::addColumn("acquisitionTolerance");
QTest::addColumn("targetPercentage");
QTest::addColumn("hoursLeftToTargetTime");
// Car information and states
QTest::addColumn("carBatteryLevel");
QTest::addColumn("carCapacity");
QTest::addColumn("carMinChargingCurrent");
QTest::addColumn("carPhaseCount");
// Charger information and states
QTest::addColumn("chargerConnected");
QTest::addColumn("chargerPower");
QTest::addColumn("chargerPhases");
QTest::addColumn("chargerMaxChargingCurrent");
QTest::addColumn("chargerMaxChargingCurrentMaxValue");
QTest::addColumn("chargerDesiredPhaseCount");
// Current meter power states
QTest::addColumn("meterPhasesPower");
// Desired result on the evCharger
QTest::addColumn("expectedChargerMaxChargingCurrent");
QTest::addColumn("expectedChargerPower");
QTest::addColumn("expectedChargerDesiredPhaseCount");
QTest::newRow("1 phase, p: 3,8kW, 10A, ON, No phase switch, no aquisition")
<< 32 // phase limit (A)
<< 1.0 // acquisitionTolerance
<< 100 // targetPercentage
<< 6 // hoursLeftToTargetTime
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 3 // carPhaseCount
<< true // chargerConnected
<< true // chargerPower
<< "ABC" // chargerPhases
<< 10 // chargerMaxChargingCurrent
<< 16 // chargerMaxChargingCurrentMaxValue
<< 1 // chargerDesiredPhaseCount
// meterPhasesPower [W]
<< QVariantMap({ {"A", 2300},
{"B", -1900},
{"C", -2000} })
<< 16 // expectedChargerMaxChargingCurrent
<< true // expectedChargerPower
<< 1; // expectedChargerDesiredPhaseCount
QTest::newRow("1 phase, p: 3,8kW, 6A, ON, Switch 3 phase with aquisition")
<< 32 // phase limit (A)
<< 0.5 // acquisitionTolerance
<< 100 // targetPercentage
<< 6 // hoursLeftToTargetTime
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 3 // carPhaseCount
<< true // chargerConnected
<< false // chargerPower
<< "ABC" // chargerPhases
<< 6 // chargerMaxChargingCurrent
<< 16 // chargerMaxChargingCurrentMaxValue
<< 1 // chargerDesiredPhaseCount
// meterPhasesPower [W]
<< QVariantMap({ {"A", 0000},
{"B", 2000},
{"C", -5800} })
<< 6 // expectedChargerMaxChargingCurrent
<< true // expectedChargerPower
<< 3; // expectedChargerDesiredPhaseCount
QTest::newRow("3 phase, p: 4kW, 6A, ON, Switch 1 -> 3")
<< 32 // phase limit (A)
<< 0.5 // acquisitionTolerance
<< 100 // targetPercentage
<< 6 // hoursLeftToTargetTime
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 3 // carPhaseCount
<< true // chargerConnected
<< false // chargerPower
<< "ABC" // chargerPhases
<< 6 // chargerMaxChargingCurrent
<< 16 // chargerMaxChargingCurrentMaxValue
<< 1 // chargerDesiredPhaseCount
// meterPhasesPower [W]
<< QVariantMap({ {"A", 0000},
{"B", 2000},
{"C", -6000} })
<< 6 // expectedChargerMaxChargingCurrent
<< true // expectedChargerPower
<< 3; // expectedChargerDesiredPhaseCount
QTest::newRow("3 phase, p: 800W, 6A, ON, Switch 3 -> 1")
<< 32 // phase limit (A)
<< 0.5 // acquisitionTolerance
<< 100 // targetPercentage
<< 6 // hoursLeftToTargetTime
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 3 // carPhaseCount
<< true // chargerConnected
<< false // chargerPower
<< "ABC" // chargerPhases
<< 10 // chargerMaxChargingCurrent
<< 16 // chargerMaxChargingCurrentMaxValue
<< 3 // chargerDesiredPhaseCount
// meterPhasesPower [W]
<< QVariantMap({ {"A", 0000},
{"B", 900},
{"C", -1700} })
<< 6 // expectedChargerMaxChargingCurrent
<< true // expectedChargerPower
<< 1; // expectedChargerDesiredPhaseCount
QTest::newRow("3 phase charger, p: 1,38kW, 6A, ON, Start 1-phase")
<< 32 // phase limit (A)
<< 0.01 // acquisitionTolerance
<< 100 // targetPercentage
<< 6 // hoursLeftToTargetTime
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 3 // carPhaseCount
<< true // chargerConnected
<< false // chargerPower
<< "ABC" // chargerPhases
<< 6 // chargerMaxChargingCurrent
<< 16 // chargerMaxChargingCurrentMaxValue
<< 3 // chargerDesiredPhaseCount
// meterPhasesPower [W]
<< QVariantMap({ {"A", -1400},
{"B", 2000},
{"C", -2000} })
<< 6 // expectedChargerMaxChargingCurrent
<< true // expectedChargerPower
<< 1; // expectedChargerDesiredPhaseCount
QTest::newRow("3 phase charger, p: 200W, 6A, ON, Start 1-phase")
<< 32 // phase limit (A)
<< 0.01 // acquisitionTolerance
<< 100 // targetPercentage
<< 6 // hoursLeftToTargetTime
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 3 // carPhaseCount
<< true // chargerConnected
<< false // chargerPower
<< "ABC" // chargerPhases
<< 6 // chargerMaxChargingCurrent
<< 16 // chargerMaxChargingCurrentMaxValue
<< 3 // chargerDesiredPhaseCount
// meterPhasesPower [W]
<< QVariantMap({ {"A", -200},
{"B", 2000},
{"C", -2000} })
<< 6 // expectedChargerMaxChargingCurrent
<< true // expectedChargerPower
<< 1; // expectedChargerDesiredPhaseCount
QTest::newRow("3 phase charger, p: 3,7kW, 6A, ON, Start 3-phase")
<< 32 // phase limit (A)
<< 0.01 // acquisitionTolerance
<< 100 // targetPercentage
<< 6 // hoursLeftToTargetTime
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 3 // carPhaseCount
<< true // chargerConnected
<< false // chargerPower
<< "ABC" // chargerPhases
<< 6 // chargerMaxChargingCurrent
<< 16 // chargerMaxChargingCurrentMaxValue
<< 3 // chargerDesiredPhaseCount
// meterPhasesPower [W]
<< QVariantMap({ {"A", -3700},
{"B", 2000},
{"C", -2000} })
<< 6 // expectedChargerMaxChargingCurrent
<< true // expectedChargerPower
<< 3; // expectedChargerDesiredPhaseCount
}
void TestCharging::testEcoModePhaseSwitching()
{
QFETCH(int, phasePowerLimit);
QFETCH(double, acquisitionTolerance);
QFETCH(int, targetPercentage);
QFETCH(int, hoursLeftToTargetTime);
QFETCH(int, carBatteryLevel);
QFETCH(int, carCapacity);
QFETCH(int, carMinChargingCurrent);
QFETCH(int, carPhaseCount);
QFETCH(bool, chargerConnected);
QFETCH(bool, chargerPower);
QFETCH(QString, chargerPhases);
QFETCH(int, chargerMaxChargingCurrent);
QFETCH(int, chargerMaxChargingCurrentMaxValue);
QFETCH(int, chargerDesiredPhaseCount);
QFETCH(QVariantMap, meterPhasesPower);
QFETCH(int, expectedChargerMaxChargingCurrent);
QFETCH(bool, expectedChargerPower);
QFETCH(int, expectedChargerDesiredPhaseCount);
Q_UNUSED(expectedChargerDesiredPhaseCount)
QVariantMap params;
QVariant response, notification;
QNetworkReply *reply = nullptr;
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;
// Set phase power limit
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
// Add mock meter
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid meterThingId = addMeter();
QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
QCOMPARE(notification.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
// Make sure this is our root meter now
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
response = injectAndWait("Energy.GetRootMeter");
QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
notificationSpy.clear();
// Set the meter values first so we have the desired execution o charging info changed, and not an extra iteration with 0 W on root meter
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setMeterStates(meterPhasesPower);
QSignalSpy setStatesReplySpy(reply, &QNetworkReply::finished);
if (setStatesReplySpy.count() == 0) setStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// No evaluation, there is no meter configured yet
// Add the charger
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid evChargerId = addChargerWithPhaseCountSwitching(chargerPhases, chargerMaxChargingCurrentMaxValue);
QVERIFY2(!evChargerId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
checkNotification(notificationSpy, "Integrations.ThingAdded");
// Add the car
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid assignedCarId = addCar();
QVERIFY2(!assignedCarId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
checkNotification(notificationSpy, "Integrations.ThingAdded");
// Set aquisition tolerance
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
params.insert("acquisitionTolerance", acquisitionTolerance);
response = injectAndWait("NymeaEnergy.SetAcquisitionTolerance", params);
verifyEnergyError(response);
// Set states of car
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setCarStates(carBatteryLevel, carCapacity, carMinChargingCurrent, carPhaseCount);
QSignalSpy setCarStatesReplySpy(reply, &QNetworkReply::finished);
if (setCarStatesReplySpy.count() == 0) setCarStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// Set states of the charger
uint effectivePhaseCount = qMin((uint)carPhaseCount, Electricity::getPhaseCount(Electricity::convertPhasesFromString(chargerPhases)));
QString usedPhases;
if (effectivePhaseCount >= 1)
usedPhases.append("A");
if (effectivePhaseCount >= 2)
usedPhases.append("B");
if (effectivePhaseCount >= 3)
usedPhases.append("C");
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setChargerWithPhaseCountSwitchingStates(chargerConnected, chargerPower, true, usedPhases, chargerMaxChargingCurrent, chargerMaxChargingCurrentMaxValue, chargerDesiredPhaseCount);
QSignalSpy setChargerStatesReplySpy(reply, &QNetworkReply::finished);
if (setChargerStatesReplySpy.count() == 0) setChargerStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// Set charging info with our charger and car, this should trigger the evaluation
QVariantMap chargingInfoMap;
chargingInfoMap.insert("evChargerId", evChargerId);
if (!assignedCarId.isNull())
chargingInfoMap.insert("assignedCarId", assignedCarId);
chargingInfoMap.insert("chargingMode", "ChargingModeEcoWithTargetTime");
chargingInfoMap.insert("endDateTime", QDateTime::currentDateTime().addSecs(hoursLeftToTargetTime * 3600).toMSecsSinceEpoch() / 1000);
chargingInfoMap.insert("targetPercentage", targetPercentage);
// Trigger evaluation
response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}}));
verifyEnergyError(response);
// Verify if the charger has been set correctly
reply = getActionHistory(m_mockChargerWithPhaseCountSwitchingDefaultPort);
QSignalSpy actionHistoryReplySpy(reply, &QNetworkReply::finished);
if (actionHistoryReplySpy.count() == 0) actionHistoryReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
QByteArray data = reply->readAll();
QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
QVariantList actionHistory = jsonDoc.toVariant().toList();
//qCDebug(dcTests()) << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
// Make sure we got the correct actions
if (expectedChargerMaxChargingCurrent != 0)
QVERIFY(verifyActionExecuted(actionHistory, "maxChargingCurrent"));
QVERIFY(verifyActionExecuted(actionHistory, "power"));
if (expectedChargerMaxChargingCurrent != 0)
QCOMPARE(getLastValueFromExecutedAction(actionHistory, "maxChargingCurrent", "maxChargingCurrent"), QVariant(expectedChargerMaxChargingCurrent));
if (expectedChargerDesiredPhaseCount != chargerDesiredPhaseCount)
QCOMPARE(getLastValueFromExecutedAction(actionHistory, "desiredPhaseCount", "desiredPhaseCount"), QVariant(expectedChargerDesiredPhaseCount));
QCOMPARE(getLastValueFromExecutedAction(actionHistory, "power", "power"), QVariant(expectedChargerPower));
removeDevices();
}
void TestCharging::testBatteryLevelConsideration_data()
{
// Houshold info
QTest::addColumn("phasePowerLimit");
// ChargingInfo
QTest::addColumn("acquisitionTolerance");
QTest::addColumn("batteryLevelConsideration");
// Car information and states
QTest::addColumn("carBatteryLevel");
QTest::addColumn("carCapacity");
QTest::addColumn("carMinChargingCurrent");
QTest::addColumn("carPhaseCount");
// Charger information and states
QTest::addColumn("chargerConnected");
QTest::addColumn("chargerPower");
QTest::addColumn("chargerPhases");
QTest::addColumn("chargerMaxChargingCurrent");
QTest::addColumn("chargerMaxChargingCurrentMaxValue");
QTest::addColumn("chargerDesiredPhaseCount");
// Current meter power states
QTest::addColumn("meterPhasesPower");
// Current energy storage states
QTest::addColumn("energyStorageBatteryLevel");
QTest::addColumn("energyStorageCurrentPower");
// Desired result on the evCharger
QTest::addColumn("expectedChargerMaxChargingCurrent"); // 0 for Unchanged / not executed
QTest::addColumn("expectedChargerPower");
QTest::addColumn("expectedChargerDesiredPhaseCount");
QTest::newRow("Default: 1 phase, battery: 80% 3,6kW -> 16A, ON, 1P")
<< 32 // phase limit (A)
<< 0.5 // acquisitionTolerance
<< 0.8 // batteryLevelConsideration
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 1 // carPhaseCount
<< true // chargerConnected
<< false // chargerPower
<< "A" // chargerPhases
<< 6 // chargerMaxChargingCurrent
<< 16 // chargerMaxChargingCurrentMaxValue
<< 1 // chargerDesiredPhaseCount
// meterPhasesPower [W]
<< QVariantMap({ {"A", 0},
{"B", 0 },
{"C", 0} })
<< 80.0 // energyStorageBatteryLevel
<< 3680.0 // energyStorageCurrentPower
<< 16 // expectedChargerMaxChargingCurrent
<< true // expectedChargerPower
<< 1; // expectedChargerDesiredPhaseCount
QTest::newRow("Default: 1 phase, battery: 79% 3,6kW -> 6A, OFF, 1P")
<< 32 // phase limit (A)
<< 0.5 // acquisitionTolerance
<< 0.8 // batteryLevelConsideration
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 1 // carPhaseCount
<< true // chargerConnected
<< false // chargerPower
<< "A" // chargerPhases
<< 6 // chargerMaxChargingCurrent
<< 16 // chargerMaxChargingCurrentMaxValue
<< 1 // chargerDesiredPhaseCount
// meterPhasesPower [W]
<< QVariantMap({ {"A", 0},
{"B", 0 },
{"C", 0} })
<< 79.0 // energyStorageBatteryLevel
<< 3680.0 // energyStorageCurrentPower
<< 0 // expectedChargerMaxChargingCurrent
<< false // expectedChargerPower
<< 1; // expectedChargerDesiredPhaseCount
QTest::newRow("Default: 1 phase, battery: 81% -600W -> 6A, OFF, 1P")
<< 32 // phase limit (A)
<< 0.5 // acquisitionTolerance
<< 0.8 // batteryLevelConsideration
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 1 // carPhaseCount
<< true // chargerConnected
<< false // chargerPower
<< "A" // chargerPhases
<< 6 // chargerMaxChargingCurrent
<< 16 // chargerMaxChargingCurrentMaxValue
<< 1 // chargerDesiredPhaseCount
// meterPhasesPower [W]
<< QVariantMap({ {"A", -600},
{"B", 0 },
{"C", 0} })
<< 81.0 // energyStorageBatteryLevel
<< -600.0 // energyStorageCurrentPower
<< 0 // expectedChargerMaxChargingCurrent
<< false // expectedChargerPower
<< 1; // expectedChargerDesiredPhaseCount
QTest::newRow("Default: 1 phase, battery: 81% -600W -> 6A, OFF, 1P")
<< 32 // phase limit (A)
<< 0.5 // acquisitionTolerance
<< 0.8 // batteryLevelConsideration
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 1 // carPhaseCount
<< true // chargerConnected
<< false // chargerPower
<< "A" // chargerPhases
<< 6 // chargerMaxChargingCurrent
<< 16 // chargerMaxChargingCurrentMaxValue
<< 1 // chargerDesiredPhaseCount
// meterPhasesPower [W]
<< QVariantMap({ {"A", -600},
{"B", 0 },
{"C", 0} })
<< 81.5 // energyStorageBatteryLevel
<< 100.0 // energyStorageCurrentPower
<< 6 // expectedChargerMaxChargingCurrent
<< true // expectedChargerPower
<< 1; // expectedChargerDesiredPhaseCount
}
void TestCharging::testBatteryLevelConsideration()
{
QFETCH(int, phasePowerLimit);
QFETCH(double, acquisitionTolerance);
QFETCH(double, batteryLevelConsideration);
QFETCH(int, carBatteryLevel);
QFETCH(int, carCapacity);
QFETCH(int, carMinChargingCurrent);
QFETCH(int, carPhaseCount);
QFETCH(bool, chargerConnected);
QFETCH(bool, chargerPower);
QFETCH(QString, chargerPhases);
QFETCH(int, chargerMaxChargingCurrent);
QFETCH(int, chargerMaxChargingCurrentMaxValue);
QFETCH(int, chargerDesiredPhaseCount);
QFETCH(QVariantMap, meterPhasesPower);
QFETCH(double, energyStorageBatteryLevel);
QFETCH(double, energyStorageCurrentPower);
QFETCH(int, expectedChargerMaxChargingCurrent); // 0 for Unchanged / not executed
QFETCH(bool, expectedChargerPower);
QFETCH(int, expectedChargerDesiredPhaseCount);
Q_UNUSED(expectedChargerDesiredPhaseCount)
QVariantMap params;
QVariant response, notification;
QNetworkReply *reply = nullptr;
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;
// Set phase power limit
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
// Add mock meter
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid meterThingId = addMeter();
QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
QCOMPARE(notification.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
// Make sure this is our root meter now
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
response = injectAndWait("Energy.GetRootMeter");
QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
notificationSpy.clear();
// Set the meter values first so we have the desired execution o charging info changed, and not an extra iteration with 0 W on root meter
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setMeterStates(meterPhasesPower);
QSignalSpy setStatesReplySpy(reply, &QNetworkReply::finished);
if (setStatesReplySpy.count() == 0) setStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// No evaluation, there is no meter configured yet
// Add the charger
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid evChargerId = addChargerWithPhaseCountSwitching(chargerPhases, chargerMaxChargingCurrentMaxValue);
QVERIFY2(!evChargerId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
checkNotification(notificationSpy, "Integrations.ThingAdded");
// Add the car
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid assignedCarId = addCar();
QVERIFY2(!assignedCarId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
checkNotification(notificationSpy, "Integrations.ThingAdded");
// Add energy storage
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid energyStorageId = addEnergyStorage();
QVERIFY2(!energyStorageId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
checkNotification(notificationSpy, "Integrations.ThingAdded");
// Set aquisition tolerance
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
params.insert("acquisitionTolerance", acquisitionTolerance);
response = injectAndWait("NymeaEnergy.SetAcquisitionTolerance", params);
verifyEnergyError(response);
// Set battery level consideration
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
params.insert("batteryLevelConsideration", batteryLevelConsideration);
response = injectAndWait("NymeaEnergy.SetBatteryLevelConsideration", params);
verifyEnergyError(response);
// Set states of car
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setCarStates(carBatteryLevel, carCapacity, carMinChargingCurrent, carPhaseCount);
QSignalSpy setCarStatesReplySpy(reply, &QNetworkReply::finished);
if (setCarStatesReplySpy.count() == 0) setCarStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// Set states of the charger
uint effectivePhaseCount = qMin((uint)carPhaseCount, Electricity::getPhaseCount(Electricity::convertPhasesFromString(chargerPhases)));
QString usedPhases;
if (effectivePhaseCount >= 1)
usedPhases.append("A");
if (effectivePhaseCount >= 2)
usedPhases.append("B");
if (effectivePhaseCount >= 3)
usedPhases.append("C");
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setChargerWithPhaseCountSwitchingStates(chargerConnected, chargerPower, true, usedPhases, chargerMaxChargingCurrent, chargerMaxChargingCurrentMaxValue, chargerDesiredPhaseCount);
QSignalSpy setChargerStatesReplySpy(reply, &QNetworkReply::finished);
if (setChargerStatesReplySpy.count() == 0) setChargerStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// Set energy storage
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setEnergyStorageStates(energyStorageBatteryLevel, energyStorageCurrentPower);
QSignalSpy setEnergyStorageStatesReplySpy(reply, &QNetworkReply::finished);
if (setEnergyStorageStatesReplySpy.count() == 0) setEnergyStorageStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// Set charging info with our charger and car, this should trigger the evaluation
QVariantMap chargingInfoMap;
chargingInfoMap.insert("evChargerId", evChargerId);
if (!assignedCarId.isNull())
chargingInfoMap.insert("assignedCarId", assignedCarId);
chargingInfoMap.insert("chargingMode", "ChargingModeEco");
response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}}));
verifyEnergyError(response);
// Verify if the charger has been set correctly
reply = getActionHistory(m_mockChargerWithPhaseCountSwitchingDefaultPort);
QSignalSpy actionHistoryReplySpy(reply, &QNetworkReply::finished);
if (actionHistoryReplySpy.count() == 0) actionHistoryReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
QByteArray data = reply->readAll();
QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
QVariantList actionHistory = jsonDoc.toVariant().toList();
//qCDebug(dcTests()) << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
// Make sure we got the correct actions
if (expectedChargerMaxChargingCurrent != 0)
QVERIFY(verifyActionExecuted(actionHistory, "maxChargingCurrent"));
QVERIFY(verifyActionExecuted(actionHistory, "power"));
if (expectedChargerMaxChargingCurrent != 0)
QCOMPARE(getLastValueFromExecutedAction(actionHistory, "maxChargingCurrent", "maxChargingCurrent"), QVariant(expectedChargerMaxChargingCurrent));
QCOMPARE(getLastValueFromExecutedAction(actionHistory, "power", "power"), QVariant(expectedChargerPower));
removeDevices();
}
void TestCharging::batteryLevelConsiderationStopCharging_data()
{
// Houshold info
QTest::addColumn("phasePowerLimit");
// ChargingConfiguration
QTest::addColumn("acquisitionTolerance");
QTest::addColumn("batteryLevelConsideration");
// Car information and states
QTest::addColumn("carBatteryLevel");
QTest::addColumn("carCapacity");
QTest::addColumn("carMinChargingCurrent");
QTest::addColumn("carPhaseCount");
// Charger information and states
QTest::addColumn("chargerConnected");
QTest::addColumn("chargerPower");
QTest::addColumn("chargerPhases");
QTest::addColumn("chargerMaxChargingCurrent");
QTest::addColumn("chargerMaxChargingCurrentMaxValue");
QTest::addColumn("chargerDesiredPhaseCount");
// Current meter power states
QTest::addColumn("meterPhasesPower");
// Current energy storage states
QTest::addColumn("energyStorageBatteryLevel");
QTest::addColumn("energyStorageCurrentPower");
QTest::newRow("Default:")
<< 32 // phase limit (A)
<< 0.5 // acquisitionTolerance
<< 0.8 // batteryLevelConsideration
<< 80 // carBatteryLevel
<< 48 // carCapacity
<< 6 // carMinChargingCurrent
<< 1 // carPhaseCount
<< true // chargerConnected
<< true // chargerPower
<< "ABC" // chargerPhases
<< 6 // chargerMaxChargingCurrent
<< 16 // chargerMaxChargingCurrentMaxValue
<< 1 // chargerDesiredPhaseCount
// meterPhasesPower [W]
<< QVariantMap({ {"A", 0.0},
{"B", 0 },
{"C", 0} })
<< 20.0 // energyStorageBatteryLevel
<< 0.0; // energyStorageCurrentPower
}
void TestCharging::batteryLevelConsiderationStopCharging()
{
QFETCH(int, phasePowerLimit);
QFETCH(double, acquisitionTolerance);
QFETCH(double, batteryLevelConsideration);
QFETCH(int, carBatteryLevel);
QFETCH(int, carCapacity);
QFETCH(int, carMinChargingCurrent);
QFETCH(int, carPhaseCount);
QFETCH(bool, chargerConnected);
QFETCH(bool, chargerPower);
QFETCH(QString, chargerPhases);
QFETCH(int, chargerMaxChargingCurrent);
QFETCH(int, chargerMaxChargingCurrentMaxValue);
QFETCH(int, chargerDesiredPhaseCount);
QFETCH(QVariantMap, meterPhasesPower);
QFETCH(double, energyStorageBatteryLevel);
QFETCH(double, energyStorageCurrentPower);
QVariantMap params;
QVariant response, notification;
QNetworkReply *reply = nullptr;
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;
// Set phase power limit
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
// Add mock meter
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid meterThingId = addMeter();
QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
QCOMPARE(notification.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
// Make sure this is our root meter now
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
response = injectAndWait("Energy.GetRootMeter");
QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
notificationSpy.clear();
// Set the meter values first so we have the desired execution o charging configuration changed, and not an extra iteration with 0 W on root meter
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setMeterStates(meterPhasesPower);
QSignalSpy setStatesReplySpy(reply, &QNetworkReply::finished);
if (setStatesReplySpy.count() == 0) setStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// No evaluation, there is no meter configured yet
// Add the charger
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid evChargerId = addChargerWithPhaseCountSwitching(chargerPhases, chargerMaxChargingCurrentMaxValue);
QVERIFY2(!evChargerId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
checkNotification(notificationSpy, "Integrations.ThingAdded");
// Add the car
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid assignedCarId = addCar();
QVERIFY2(!assignedCarId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
checkNotification(notificationSpy, "Integrations.ThingAdded");
// Add energy storage
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid energyStorageId = addEnergyStorage();
QVERIFY2(!energyStorageId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
checkNotification(notificationSpy, "Integrations.ThingAdded");
// Set aquisition tolerance
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
params.insert("acquisitionTolerance", acquisitionTolerance);
response = injectAndWait("NymeaEnergy.SetAcquisitionTolerance", params);
verifyEnergyError(response);
// Set battery level consideration
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
params.insert("batteryLevelConsideration", batteryLevelConsideration);
response = injectAndWait("NymeaEnergy.SetBatteryLevelConsideration", params);
verifyEnergyError(response);
// Set states of car
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setCarStates(carBatteryLevel, carCapacity, carMinChargingCurrent, carPhaseCount);
QSignalSpy setCarStatesReplySpy(reply, &QNetworkReply::finished);
if (setCarStatesReplySpy.count() == 0) setCarStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// Set states of the charger
uint effectivePhaseCount = qMin((uint)carPhaseCount, Electricity::getPhaseCount(Electricity::convertPhasesFromString(chargerPhases)));
QString usedPhases;
if (effectivePhaseCount >= 1)
usedPhases.append("A");
if (effectivePhaseCount >= 2)
usedPhases.append("B");
if (effectivePhaseCount >= 3)
usedPhases.append("C");
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setChargerWithPhaseCountSwitchingStates(chargerConnected, chargerPower, true, usedPhases, chargerMaxChargingCurrent, chargerMaxChargingCurrentMaxValue, chargerDesiredPhaseCount);
QSignalSpy setChargerStatesReplySpy(reply, &QNetworkReply::finished);
if (setChargerStatesReplySpy.count() == 0) setChargerStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// Set energy storage
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setEnergyStorageStates(energyStorageBatteryLevel, energyStorageCurrentPower);
QSignalSpy setEnergyStorageStatesReplySpy(reply, &QNetworkReply::finished);
if (setEnergyStorageStatesReplySpy.count() == 0) setEnergyStorageStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// Make sure we are in normal mode
QVariantMap chargingInfoMap;
chargingInfoMap.insert("evChargerId", evChargerId);
chargingInfoMap.insert("assignedCarId", assignedCarId);
chargingInfoMap.insert("chargingMode", "ChargingModeNormal");
response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}}));
verifyEnergyError(response);
// Set charging info with our charger and car, this should trigger the evaluation
chargingInfoMap.clear();
chargingInfoMap.insert("evChargerId", evChargerId);
chargingInfoMap.insert("assignedCarId", assignedCarId);
chargingInfoMap.insert("chargingMode", "ChargingModeEco");
response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}}));
verifyEnergyError(response);
// Verify if the charger has been set correctly
reply = getActionHistory(m_mockChargerWithPhaseCountSwitchingDefaultPort);
QSignalSpy actionHistoryReplySpy(reply, &QNetworkReply::finished);
if (actionHistoryReplySpy.count() == 0) actionHistoryReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
QByteArray data = reply->readAll();
QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
QVariantList actionHistory = jsonDoc.toVariant().toList();
qCDebug(dcTests()) << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
// Make sure we got the correct actions
QVERIFY(verifyActionExecuted(actionHistory, "power"));
QCOMPARE(getLastValueFromExecutedAction(actionHistory, "power", "power"), false);
}
void TestCharging::testOverloadProtectionManualMode_data()
{
// Houshold info
QTest::addColumn("phasePowerLimit");
// Car
QTest::addColumn("carPhaseCount");
// Charger information and states
QTest::addColumn("chargerPower");
QTest::addColumn("chargerPhases");
QTest::addColumn("chargerMaxChargingCurrent");
QTest::addColumn("chargerMaxChargingCurrentMaxValue");
// Current meter power states
QTest::addColumn("meterPhasesPower");
QTest::addColumn("triggerMeterPhasesPower");
// Desired result on the evCharger
QTest::addColumn("expectedChargerMaxChargingCurrent");
QTest::addColumn("expectedChargerPower");
QTest::newRow("Default: 1 phase, 16A -> 10A")
<< 32 // phase limit (A) 7360 W
<< 1 // carPhaseCount
<< true // chargerPower
<< "A" // chargerPhases
<< 16 // chargerMaxChargingCurrent
<< 16 //chargerMaxChargingCurrentMaxValue
<< QVariantMap({{"A", 4680}, // W
{"B", 2000}, // W
{"C", -6000} }) // W
<< QVariantMap({ {"A", 8680}, // W
{"B", 2000}, // W
{"C", -6000}}) // W
<< 10 // expectedChargerMaxChargingCurrent
<< true; // expectedChargerPower
QTest::newRow("Default: 1 phase, 16A -> OFF")
<< 32 // phase limit (A) 7360 W
<< 1 // carPhaseCount
<< true // chargerPower
<< "A" // chargerPhases
<< 16 // chargerMaxChargingCurrent
<< 16 //chargerMaxChargingCurrentMaxValue
<< QVariantMap({{"A", 4680}, // W
{"B", 2000}, // W
{"C", -6000}}) // W
<< QVariantMap({{"A", 9820}, // W
{"B", 2000}, // W
{"C", -6000}}) // W
<< 16 // expectedChargerMaxChargingCurrent
<< false; // expectedChargerPower
QTest::newRow("Default: 3 phase, 16A -> 10A")
<< 32 // phase limit (A) 7360 W
<< 3 // carPhaseCount
<< true // chargerPower
<< "ABC" // chargerPhases
<< 16 // chargerMaxChargingCurrent
<< 16 //chargerMaxChargingCurrentMaxValue
<< QVariantMap({{"A", 4680}, // W
{"B", 2000}, // W
{"C", 3200}}) // W
<< QVariantMap({{"A", 4680}, // W
{"B", 8280}, // W
{"C", 3200}}) // W
<< 12 // expectedChargerMaxChargingCurrent
<< true; // expectedChargerPower
}
void TestCharging::testOverloadProtectionManualMode()
{
QFETCH(int, phasePowerLimit);
QFETCH(int, carPhaseCount);
QFETCH(bool, chargerPower);
QFETCH(QString, chargerPhases);
QFETCH(int, chargerMaxChargingCurrent);
QFETCH(int, chargerMaxChargingCurrentMaxValue);
QFETCH(QVariantMap, meterPhasesPower);
QFETCH(QVariantMap, triggerMeterPhasesPower);
QFETCH(int, expectedChargerMaxChargingCurrent);
QFETCH(bool, expectedChargerPower);
QVariantMap params;
QVariant response, notification;
QNetworkReply *reply = nullptr;
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;
// Set phase power limit
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
// Add mock meter
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid meterThingId = addMeter();
QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
QCOMPARE(notification.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
// Make sure this is our root meter now
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
response = injectAndWait("Energy.GetRootMeter");
QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
notificationSpy.clear();
// Set the meter values first so we have the desired execution o charging info changed, and not an extra iteration with 0 W on root meter
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setMeterStates(meterPhasesPower);
QSignalSpy setStatesReplySpy(reply, &QNetworkReply::finished);
if (setStatesReplySpy.count() == 0) setStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// No evaluation, there is no meter configured yet
// Add the charger
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid evChargerId = addCharger(chargerPhases, chargerMaxChargingCurrentMaxValue);
QVERIFY2(!evChargerId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
checkNotification(notificationSpy, "Integrations.ThingAdded");
// Add the car
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid assignedCarId = addCar();
QVERIFY2(!assignedCarId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
checkNotification(notificationSpy, "Integrations.ThingAdded");
// Set states of the charger
uint effectivePhaseCount = qMin((uint)carPhaseCount, Electricity::getPhaseCount(Electricity::convertPhasesFromString(chargerPhases)));
QString usedPhases;
if (effectivePhaseCount >= 1)
usedPhases.append("A");
if (effectivePhaseCount >= 2)
usedPhases.append("B");
if (effectivePhaseCount >= 3)
usedPhases.append("C");
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setChargerStates(true, chargerPower, true, usedPhases, chargerMaxChargingCurrent, chargerMaxChargingCurrentMaxValue);
QSignalSpy setChargerStatesReplySpy(reply, &QNetworkReply::finished);
if (setChargerStatesReplySpy.count() == 0) setChargerStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// Set charging info with our charger and car, this should trigger the evaluation
QVariantMap chargingInfoMap;
chargingInfoMap.insert("evChargerId", evChargerId);
if (!assignedCarId.isNull())
chargingInfoMap.insert("assignedCarId", assignedCarId);
chargingInfoMap.insert("chargingMode", "ChargingModeNormal");
response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}}));
verifyEnergyError(response);
// Now add the trigger load on the meter to trigger the overload protection
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setMeterStates(triggerMeterPhasesPower);
QSignalSpy setMeterStatesReplySpy(reply, &QNetworkReply::finished);
if (setMeterStatesReplySpy.count() == 0) setMeterStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// Veify overload protection active
// Verify if the charger has been set correctly
reply = getActionHistory(m_mockChargerDefaultPort);
QSignalSpy actionHistoryReplySpy(reply, &QNetworkReply::finished);
if (actionHistoryReplySpy.count() == 0) actionHistoryReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
QByteArray data = reply->readAll();
QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
QVariantList actionHistory = jsonDoc.toVariant().toList();
//qCDebug(dcTests()) << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
// Make sure we got the correct actions
if (expectedChargerMaxChargingCurrent != 0)
QVERIFY(verifyActionExecuted(actionHistory, "maxChargingCurrent"));
QVERIFY(verifyActionExecuted(actionHistory, "power"));
if (expectedChargerMaxChargingCurrent != 0)
QCOMPARE(getLastValueFromExecutedAction(actionHistory, "maxChargingCurrent", "maxChargingCurrent"), QVariant(expectedChargerMaxChargingCurrent));
QCOMPARE(getLastValueFromExecutedAction(actionHistory, "power", "power"), QVariant(expectedChargerPower));
removeDevices();
}
void TestCharging::testOverloadProtectionEcoMode_data()
{
// Houshold info
QTest::addColumn("phasePowerLimit");
// Car
QTest::addColumn("carPhaseCount");
// Charger information and states
QTest::addColumn("chargerPower");
QTest::addColumn("chargerPhases");
QTest::addColumn("chargerMaxChargingCurrent");
QTest::addColumn("chargerMaxChargingCurrentMaxValue");
// Current meter power states
QTest::addColumn("meterPhasesPower");
QTest::addColumn("triggerMeterPhasesPower");
// Desired result on the evCharger
QTest::addColumn("expectedChargerMaxChargingCurrent");
QTest::addColumn("expectedChargerPower");
QTest::newRow("Default: 1 phase, 16A -> 10A")
<< 32 // phase limit (A) 7360 W
<< 3 // carPhaseCount
<< true // chargerPower
<< "ABC" // chargerPhases
<< 16 // chargerMaxChargingCurrent
<< 16 //chargerMaxChargingCurrentMaxValue
<< QVariantMap({{"A", 4680}, // W
{"B", 2000}, // W
{"C", -6000} }) // W
<< QVariantMap({ {"A", 8680}, // W
{"B", 2000}, // W
{"C", -6000}}) // W
<< 6 // expectedChargerMaxChargingCurrent
<< true; // expectedChargerPower
}
void TestCharging::testOverloadProtectionEcoMode()
{
QFETCH(int, phasePowerLimit);
QFETCH(int, carPhaseCount);
QFETCH(bool, chargerPower);
QFETCH(QString, chargerPhases);
QFETCH(int, chargerMaxChargingCurrent);
QFETCH(int, chargerMaxChargingCurrentMaxValue);
QFETCH(QVariantMap, meterPhasesPower);
QFETCH(QVariantMap, triggerMeterPhasesPower);
QFETCH(int, expectedChargerMaxChargingCurrent);
QFETCH(bool, expectedChargerPower);
QVariantMap params;
QVariant response, notification;
QNetworkReply *reply = nullptr;
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;
// Set phase power limit
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}}));
QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError");
// Add mock meter
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid meterThingId = addMeter();
QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
notification = checkNotification(notificationSpy, "Energy.RootMeterChanged");
QCOMPARE(notification.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
// Make sure this is our root meter now
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
response = injectAndWait("Energy.GetRootMeter");
QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId);
notificationSpy.clear();
// Set the meter values first so we have the desired execution o charging info changed, and not an extra iteration with 0 W on root meter
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setMeterStates(meterPhasesPower);
QSignalSpy setStatesReplySpy(reply, &QNetworkReply::finished);
if (setStatesReplySpy.count() == 0) setStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// No evaluation, there is no meter configured yet
// Add the charger
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid evChargerId = addCharger(chargerPhases, chargerMaxChargingCurrentMaxValue);
QVERIFY2(!evChargerId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
checkNotification(notificationSpy, "Integrations.ThingAdded");
// Add the car
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
QUuid assignedCarId = addCar();
QVERIFY2(!assignedCarId.isNull(), "Did not receive valid ThingId");
if (notificationSpy.count() == 0) notificationSpy.wait();
checkNotification(notificationSpy, "Integrations.ThingAdded");
// Set states of the charger
uint effectivePhaseCount = qMin((uint)carPhaseCount, Electricity::getPhaseCount(Electricity::convertPhasesFromString(chargerPhases)));
QString usedPhases;
if (effectivePhaseCount >= 1)
usedPhases.append("A");
if (effectivePhaseCount >= 2)
usedPhases.append("B");
if (effectivePhaseCount >= 3)
usedPhases.append("C");
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setChargerStates(true, chargerPower, true, usedPhases, chargerMaxChargingCurrent, chargerMaxChargingCurrentMaxValue);
QSignalSpy setChargerStatesReplySpy(reply, &QNetworkReply::finished);
if (setChargerStatesReplySpy.count() == 0) setChargerStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// Set charging info with our charger and car, this should trigger the evaluation
QVariantMap chargingInfoMap;
chargingInfoMap.insert("evChargerId", evChargerId);
if (!assignedCarId.isNull())
chargingInfoMap.insert("assignedCarId", assignedCarId);
chargingInfoMap.insert("chargingMode", "ChargingModeEco");
response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}}));
verifyEnergyError(response);
// Now add the trigger load on the meter to trigger the overload protection
QTest::qSleep(1500);
params.clear(); response.clear(); notification.clear(); notificationSpy.clear();
reply = setMeterStates(triggerMeterPhasesPower);
QSignalSpy setMeterStatesReplySpy(reply, &QNetworkReply::finished);
if (setMeterStatesReplySpy.count() == 0) setMeterStatesReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
// Verify overload protection active
// Verify if the charger has been set correctly
reply = getActionHistory(m_mockChargerDefaultPort);
QSignalSpy actionHistoryReplySpy(reply, &QNetworkReply::finished);
if (actionHistoryReplySpy.count() == 0) actionHistoryReplySpy.wait();
QCOMPARE(reply->error(), QNetworkReply::NoError);
QByteArray data = reply->readAll();
QJsonDocument jsonDoc = QJsonDocument::fromJson(data);
QVariantList actionHistory = jsonDoc.toVariant().toList();
//qCDebug(dcTests()) << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Indented));
// Make sure we got the correct actions
if (expectedChargerMaxChargingCurrent != 0)
QVERIFY(verifyActionExecuted(actionHistory, "maxChargingCurrent"));
QVERIFY(verifyActionExecuted(actionHistory, "power"));
if (expectedChargerMaxChargingCurrent != 0)
QCOMPARE(getLastValueFromExecutedAction(actionHistory, "maxChargingCurrent", "maxChargingCurrent"), QVariant(expectedChargerMaxChargingCurrent));
QCOMPARE(getLastValueFromExecutedAction(actionHistory, "power", "power"), QVariant(expectedChargerPower));
removeDevices();
}
QTEST_MAIN(TestCharging)