// 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)