// 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 "simulation.h" #include #include #include #include using namespace nymeaserver; #include "../../../energyplugin/smartchargingmanager.h" #include "../../mocks/spotmarketprovider/spotmarketdataprovidermock.h" #include #include #include #include #include #include #include #include #include "simulationtestpoint.h" void Simulation::run_data() { // Simulation infos QTest::addColumn("simulationName"); QTest::addColumn("simulationTitle"); QTest::addColumn("databaseName"); QTest::addColumn("simulationStart"); QTest::addColumn("plugEvents"); QTest::addColumn("simulationHours"); QTest::addColumn("sampleRate"); QTest::addColumn("productionScaling"); QTest::addColumn("detailsStepStart"); QTest::addColumn("detailsStepStop"); QTest::addColumn("detailsStepList"); // Houshold info QTest::addColumn("phasePowerLimit"); QTest::addColumn("spotMarketEnabled"); QTest::addColumn("spotMarketResourceData"); QTest::addColumn("acquisitionTolerance"); QTest::addColumn("batteryLevelConsideration"); // ChargingInfo QTest::addColumn("targetPercentage"); QTest::addColumn("targetDateTime"); QTest::addColumn("chargingMode"); QTest::addColumn("carBatteryLevel"); QTest::addColumn("dailySpotMarketPercentage"); // Car information and states QTest::addColumn("carCapacity"); QTest::addColumn("carMinChargingCurrent"); QTest::addColumn("carPhaseCount"); // Energy storage information and states QTest::addColumn("energyStorageAvailable"); QTest::addColumn("energyStorageCapacity"); QTest::addColumn("energyStorageMaxChargingPower"); QTest::addColumn("energyStorageMaxDischargingPower"); QTest::addColumn("energyStorageInitialBatteyLevel"); // Charger initial states QTest::addColumn("chargerConnected"); QTest::addColumn("chargerPower"); QTest::addColumn("chargerPhases"); QTest::addColumn("canSwitchPhaseCount"); QTest::addColumn("chargerMaxChargingCurrent"); QTest::addColumn("chargerMaxChargingCurrentMaxValue"); QTest::addColumn("iterationTest"); bool runAllSimulations = true; bool runSpotmarketSimulation = runAllSimulations; bool run1PhaseSimulations = runAllSimulations; bool run2PhaseSimulations = runAllSimulations; bool run3PhaseSimulations = runAllSimulations; bool runPhaseSwitchingSimulations = runAllSimulations; // Simulations if (runSpotmarketSimulation) QTest::newRow("Spotmarket only") /* Simulation info */ << "simulation-spotmarket-only-1-phase-16A" // simulationName << "Simulation (1 phase, charger 16A max, only spot market)" // simulationTitle << ":/databases/2022-08-12-kostal-energylogs.sqlite" // databaseName << EnergyTestBase::utcDateTime(QDate(2022, 8, 14), QTime(0,0,0)) // simulationStart (UTC) << ChargerPlugEvents( { ChargerPlugEvent(EnergyTestBase::utcDateTime(QDate(2022, 8, 14), QTime(7,0,0)), false), ChargerPlugEvent(EnergyTestBase::utcDateTime(QDate(2022, 8, 14), QTime(17,30,0)), true), ChargerPlugEvent(EnergyTestBase::utcDateTime(QDate(2022, 8, 15), QTime(7,0,0)), false), ChargerPlugEvent(EnergyTestBase::utcDateTime(QDate(2022, 8, 15), QTime(17,0,0)), true) }) // pluggedInTime (UTC) << 48 // simulationHours << EnergyLogs::SampleRate1Min << 0.0 // productionScaling << 0 // detailsStepStart << 0 // detailsStepStop << DetailsStepList() // detailsStepList /* Houshold info */ << 32 // phase limit (A) << true // spotMarketEnabled << ":/resources/dataset-1.json" // spotMarketResourceData << 0.5 // acquisitionTolerance << 0.9 // batteryLevelConsideration /* Charging Info */ << 100.0 // targetPercentage << EnergyTestBase::utcDateTime(QDate(2022, 8, 15), QTime(22,0,0)) // targetDateTime << "ChargingModeEco" // chargingMode << 20 // carBatteryLevel << 20 // dailySpotMarketPercentage /* Car settings */ << 50 // carCapacity << 6 // carMinChargingCurrent << 1 // carPhaseCount /* Energy storage */ << false // energyStorageAvailable << 0 // energyStorageCapacity << 0.0 // energyStorageMaxChargingPower << 0.0 // energyStorageMaxDischargingPower << 50.0 // energyStorageInitialBatteyLevel << true // chargerConnected << false // chargerPower << "ABC" // chargerPhases << false // canSwitchPhaseCount << 6 // chargerMaxChargingCurrent << 16 //chargerMaxChargingCurrentMaxValue << SimulationIterationTest ( { { 0, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 20) } }, { 250, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 16), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), } }, { 500, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), } }, { 1440, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 40) // Should have charged 20% in one day } }, { 2800, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 16), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), } }, { 2880, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 60) // Should have charged 20% in one day } } } ); if (runSpotmarketSimulation) QTest::newRow("Spotmarket and PV") /* Simulation info */ << "simulation-spotmarket-and-pv-1-phase-16A" // simulationName << "Simulation (1 phase, charger 16A max, spot market and PV)" // simulationTitle << ":/databases/2022-08-12-kostal-energylogs.sqlite" // databaseName << EnergyTestBase::utcDateTime(QDate(2022, 8, 14), QTime(0,0,0)) // simulationStart (UTC) << ChargerPlugEvents() << 48 // simulationHours << EnergyLogs::SampleRate1Min << 0.35 // productionScaling << 0 // detailsStepStart << 0 // detailsStepStop << DetailsStepList() // detailsStepList /* Houshold info */ << 32 // phase limit (A) << true // spotMarketEnabled << ":/resources/dataset-1.json" // spotMarketResourceData << 0.5 // acquisitionTolerance << 0.9 // batteryLevelConsideration /* Charging Info */ << 100.0 // targetPercentage << EnergyTestBase::utcDateTime(QDate(2022, 8, 15), QTime(22,0,0)) // targetDateTime << "ChargingModeEco" // chargingMode << 20 // carBatteryLevel << 20 // dailySpotMarketPercentage /* Car settings */ << 50 // carCapacity << 6 // carMinChargingCurrent << 1 // carPhaseCount /* Energy storage */ << false // energyStorageAvailable << 0 // energyStorageCapacity << 0.0 // energyStorageMaxChargingPower << 0.0 // energyStorageMaxDischargingPower << 50.0 // energyStorageInitialBatteyLevel << true // chargerConnected << false // chargerPower << "ABC" // chargerPhases << false // canSwitchPhaseCount << 6 // chargerMaxChargingCurrent << 16 //chargerMaxChargingCurrentMaxValue << SimulationIterationTest ( { { 0, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 20) } }, { 250, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 16), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 500, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false) } }, { 580, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 750, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 780, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false) } }, { 810, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 1400, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false) } }, { 2000, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 46) // Should be at least 20% more than the day before... } }, { 2250, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 16), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), } }, { 2750, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 16), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), } } } ); if (runSpotmarketSimulation) QTest::newRow("Spotmarket with target time") /* Simulation info */ << "simulation-spotmarket-only-with-targettime-1-phase-16A" // simulationName << "Simulation (1 phase, charger 16A max, only spot market with target time)" // simulationTitle << ":/databases/2022-08-12-kostal-energylogs.sqlite" // databaseName << EnergyTestBase::utcDateTime(QDate(2022, 8, 14), QTime(0,0,0)) // simulationStart (UTC) << ChargerPlugEvents() // Car plug events << 48 // simulationHours << EnergyLogs::SampleRate1Min << 0.0 // productionScaling << 0 // detailsStepStart << 0 // detailsStepStop << DetailsStepList() // detailsStepList /* Houshold info */ << 32 // phase limit (A) << true // spotMarketEnabled << ":/resources/dataset-1.json" // spotMarketResourceData << 0.5 // acquisitionTolerance << 0.9 // batteryLevelConsideration /* Charging Info */ << 100.0 // targetPercentage << EnergyTestBase::utcDateTime(QDate(2022, 8, 15), QTime(22,0,0)) // targetDateTime << "ChargingModeEcoWithTargetTime" // chargingMode << 20 // carBatteryLevel << 0 // dailySpotMarketPercentage /* Car settings */ << 50 // carCapacity << 6 // carMinChargingCurrent << 1 // carPhaseCount /* Energy storage */ << false // energyStorageAvailable << 0 // energyStorageCapacity << 0.0 // energyStorageMaxChargingPower << 0.0 // energyStorageMaxDischargingPower << 50.0 // energyStorageInitialBatteyLevel << true // chargerConnected << false // chargerPower << "ABC" // chargerPhases << false // canSwitchPhaseCount << 6 // chargerMaxChargingCurrent << 16 //chargerMaxChargingCurrentMaxValue << SimulationIterationTest ( { { 0, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 20) } }, { 700, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 16), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 1200, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false) } }, { 1400, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 16), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 1550, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false) } }, { 1700, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 16), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 2760, { // 22:00 SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 100) } } } ); if (runSpotmarketSimulation) QTest::newRow("Spotmarket only with target time") /* Simulation info */ << "simulation-spotmarket-only-with-targettime-1-day-1-phase-16A" // simulationName << "Simulation (1 phase, charger 16A max, only spot market with target time single day)" // simulationTitle << ":/databases/2022-08-12-kostal-energylogs.sqlite" // databaseName << EnergyTestBase::utcDateTime(QDate(2022, 8, 14), QTime(0, 0, 0)) // simulationStart (UTC) << ChargerPlugEvents() << 32 // simulationHours << EnergyLogs::SampleRate1Min << 0.0 // productionScaling << 0 // detailsStepStart << 0 // detailsStepStop << DetailsStepList() // detailsStepList /* Houshold info */ << 32 // phase limit (A) << true // spotMarketEnabled << ":/resources/dataset-1.json" // spotMarketResourceData << 0.5 // acquisitionTolerance << 0.9 // batteryLevelConsideration /* Charging Info */ << 100.0 // targetPercentage << EnergyTestBase::utcDateTime(QDate(2022, 8, 15), QTime(07, 0, 0)) // targetDateTime << "ChargingModeEcoWithTargetTime" // chargingMode << 50 // carBatteryLevel << 0 // dailySpotMarketPercentage /* Car settings */ << 50 // carCapacity << 6 // carMinChargingCurrent << 1 // carPhaseCount /* Energy storage */ << false // energyStorageAvailable << 0 // energyStorageCapacity << 0.0 // energyStorageMaxChargingPower << 0.0 // energyStorageMaxDischargingPower << 50.0 // energyStorageInitialBatteyLevel << true // chargerConnected << false // chargerPower << "ABC" // chargerPhases << false // canSwitchPhaseCount << 6 // chargerMaxChargingCurrent << 16 //chargerMaxChargingCurrentMaxValue << SimulationIterationTest ( { { 0, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 50) } }, { 250, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 16), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 61) } }, { 500, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), } }, { 1400, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 16), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), } } }); if (run1PhaseSimulations) QTest::newRow("Default") /* Simulation info */ << "simulation-1-phase-32A" // simulationName << "Simulation (1 phase, charger 32A max, target 22:00 100%" // simulationTitle << ":/databases/2022-06-28-energylogs-micha.sqlite" // databaseName << EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(6,0,0)) // simulationStart (UTC) << ChargerPlugEvents() // pluggedInTime (UTC) << 18 // simulationHours << EnergyLogs::SampleRate1Min << 20.0 // productionScaling << 0 // detailsStepStart << 0 // detailsStepStop << DetailsStepList() // detailsStepList /* Houshold info */ << 32 // phase limit (A) << false // spotMarketEnabled << ":/resources/dataset-1.json" // spotMarketResourceData << 0.5 // acquisitionTolerance << 0.9 // batteryLevelConsideration /* Charging Info */ << 100.0 // targetPercentage << EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(22,0,0)) // targetDateTime << "ChargingModeEcoWithTargetTime" // chargingMode << 40 // carBatteryLevel << 0 // dailySpotMarketPercentage /* Car settings */ << 50 // carCapacity << 6 // carMinChargingCurrent << 1 // carPhaseCount /* Energy storage */ << false // energyStorageAvailable << 0 // energyStorageCapacity << 0.0 // energyStorageMaxChargingPower << 0.0 // energyStorageMaxDischargingPower << 50.0 // energyStorageInitialBatteyLevel << true // chargerConnected << false // chargerPower << "ABC" // chargerPhases << false // canSwitchPhaseCount << 6 // chargerMaxChargingCurrent << 32 //chargerMaxChargingCurrentMaxValue << SimulationIterationTest ( { { 0, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 40) } }, { 163, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 170, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 9), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 444, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 7) , SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false) } }, { 520, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6) , SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 80) } }, { 700, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6) , SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 85) } }, { 900, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 30) , SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 88) } }, { 960, { // 22:00 SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 30) , SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 100) } } } ); if (run1PhaseSimulations) QTest::newRow("Default") /* Simulation info */ << "simulation-1-phase-16A" // simulationName << "Simulation (1 phase, charger 16A max, target 22:00 100%)" // simulationTitle << ":/databases/2022-06-28-energylogs-micha.sqlite" // databaseName << EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(0,0,0)) // simulationStart (UTC) << ChargerPlugEvents( { ChargerPlugEvent(EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(6,0,0)), true) }) // pluggedInTime (UTC) << 24 // simulationHours << EnergyLogs::SampleRate1Min << 20.0 // productionScaling << 0 // detailsStepStart << 0 // detailsStepStop << DetailsStepList() // detailsStepList /* Houshold info */ << 32 // phase limit (A) << false // spotMarketEnabled << ":/resources/dataset-1.json" // spotMarketResourceData << 0.5 // acquisitionTolerance << 0.9 // batteryLevelConsideration /* Charging Info */ << 100.0 // targetPercentage << EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(22,0,0)) // targetDateTime << "ChargingModeEcoWithTargetTime" // chargingMode << 50 // carBatteryLevel << 0 // dailySpotMarketPercentage /* Car settings */ << 50 // carCapacity << 6 // carMinChargingCurrent << 1 // carPhaseCount /* Energy storage */ << false // energyStorageAvailable << 0 // energyStorageCapacity << 0.0 // energyStorageMaxChargingPower << 0.0 // energyStorageMaxDischargingPower << 50.0 // energyStorageInitialBatteyLevel << true // chargerConnected << false // chargerPower << "ABC" // chargerPhases << false // canSwitchPhaseCount << 6 // chargerMaxChargingCurrent << 16 //chargerMaxChargingCurrentMaxValue << SimulationIterationTest ( { { 0, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 50) } }, { 452, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 50) } }, { 467, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 9), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 51) } }, { 750, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 16), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 830, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false) } }, { 841, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 1000, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false) } }, { 1300, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 16), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 1440, { // 22:00 SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 100) } } } ); if (run2PhaseSimulations) QTest::newRow("Kostal") /* Simulation info */ << "simulation-kostal-2-phase-16A" // simulationName << "Simulation (2 phase, charger 16A max, target 22:00 100%)" // simulationTitle << ":/databases/2022-08-12-kostal-energylogs.sqlite" // databaseName << EnergyTestBase::utcDateTime(QDate(2022, 8, 14), QTime(0,0,0)) // simulationStart (UTC) << ChargerPlugEvents( { ChargerPlugEvent(EnergyTestBase::utcDateTime(QDate(2022, 8, 14), QTime(8,0,0)), true) }) // pluggedInTime (UTC) << 24 // simulationHours << EnergyLogs::SampleRate1Min << 1.0 // productionScaling << 0 // detailsStepStart << 0 // detailsStepStop << DetailsStepList() // detailsStepList /* Houshold info */ << 32 // phase limit (A) << false // spotMarketEnabled << ":/resources/dataset-1.json" // spotMarketResourceData << 0.5 // acquisitionTolerance << 0.9 // batteryLevelConsideration /* Charging Info */ << 100.0 // targetPercentage << EnergyTestBase::utcDateTime(QDate(2022, 8, 14), QTime(22,0,0)) // targetDateTime << "ChargingModeEcoWithTargetTime" // chargingMode << 40 // carBatteryLevel << 0 // dailySpotMarketPercentage /* Car settings */ << 50 // carCapacity << 6 // carMinChargingCurrent << 2 // carPhaseCount /* Energy storage */ << false // energyStorageAvailable << 0 // energyStorageCapacity << 0.0 // energyStorageMaxChargingPower << 0.0 // energyStorageMaxDischargingPower << 50.0 // energyStorageInitialBatteyLevel << true // chargerConnected << false // chargerPower << "ABC" // chargerPhases << false // canSwitchPhaseCount << 6 // chargerMaxChargingCurrent << 16 //chargerMaxChargingCurrentMaxValue << SimulationIterationTest ( { { 0, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 40) } }, { 550, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 600, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 9), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 48) } }, { 820, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false) } }, { 823, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false) } }, { 847, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 16), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 80) } }, { 1050, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 100) } } } ); if (run2PhaseSimulations) QTest::newRow("Kostal") /* Simulation info */ << "simulation-kostal-2-phase-16A-away-2-hours" // simulationName << "Simulation (2 phase, charger 16A max, target 22:00 100%)" // simulationTitle << ":/databases/2022-08-12-kostal-energylogs.sqlite" // databaseName << EnergyTestBase::utcDateTime(QDate(2022, 8, 14), QTime(0,0,0)) // simulationStart (UTC) << ChargerPlugEvents( { ChargerPlugEvent(EnergyTestBase::utcDateTime(QDate(2022, 8, 14), QTime(12,0,0)), false), ChargerPlugEvent(EnergyTestBase::utcDateTime(QDate(2022, 8, 14), QTime(14,00,0)), true, 10) }) // pluggedInTime (UTC) << 24 // simulationHours << EnergyLogs::SampleRate1Min << 1.0 // productionScaling << 0 // detailsStepStart << 0 // detailsStepStop << DetailsStepList() // detailsStepList /* Houshold info */ << 32 // phase limit (A) << false // spotMarketEnabled << ":/resources/dataset-1.json" // spotMarketResourceData << 0.5 // acquisitionTolerance << 0.9 // batteryLevelConsideration /* Charging Info */ << 100.0 // targetPercentage << EnergyTestBase::utcDateTime(QDate(2022, 8, 14), QTime(22,0,0)) // targetDateTime << "ChargingModeEcoWithTargetTime" // chargingMode << 40 // carBatteryLevel << 0 // dailySpotMarketPercentage /* Car settings */ << 50 // carCapacity << 6 // carMinChargingCurrent << 2 // carPhaseCount /* Energy storage */ << false // energyStorageAvailable << 0 // energyStorageCapacity << 0.0 // energyStorageMaxChargingPower << 0.0 // energyStorageMaxDischargingPower << 50.0 // energyStorageInitialBatteyLevel << true // chargerConnected << false // chargerPower << "ABC" // chargerPhases << false // canSwitchPhaseCount << 6 // chargerMaxChargingCurrent << 16 //chargerMaxChargingCurrentMaxValue << SimulationIterationTest ( { { 0, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 40) } }, { 550, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 600, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 9), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 48) } }, { 1300, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 16), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 1440, { // 22:00 SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 100) } } } ); if (run2PhaseSimulations) QTest::newRow("Default") /* Simulation info */ << "simulation-2-phase-16A" // simulationName << "Simulation (2 phase, charger 16A max, target 22:00 100%)" // simulationTitle << ":/databases/2022-06-28-energylogs-micha.sqlite" // databaseName << EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(6,0,0)) // simulationStart (UTC) << ChargerPlugEvents() // pluggedInTime (UTC) << 18 // simulationHours << EnergyLogs::SampleRate1Min << 20.0 // productionScaling << 0 // detailsStepStart << 0 // detailsStepStop << DetailsStepList() // detailsStepList /* Houshold info */ << 32 // phase limit (A) << false // spotMarketEnabled << ":/resources/dataset-1.json" // spotMarketResourceData << 0.5 // acquisitionTolerance << 0.9 // batteryLevelConsideration /* Charging Info */ << 100.0 // targetPercentage << EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(22,0,0)) // targetDateTime << "ChargingModeEcoWithTargetTime" // chargingMode << 40 // carBatteryLevel << 0 // dailySpotMarketPercentage /* Car settings */ << 50 // carCapacity << 6 // carMinChargingCurrent << 2 // carPhaseCount /* Energy storage */ << false // energyStorageAvailable << 0 // energyStorageCapacity << 0.0 // energyStorageMaxChargingPower << 0.0 // energyStorageMaxDischargingPower << 50.0 // energyStorageInitialBatteyLevel << true // chargerConnected << false // chargerPower << "ABC" // chargerPhases << false // canSwitchPhaseCount << 6 // chargerMaxChargingCurrent << 16 //chargerMaxChargingCurrentMaxValue << SimulationIterationTest ( { { 0, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 40) } }, { 850, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false) } }, { 900, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 16), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 960, { // 22:00 SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 100) } } } ); if (run2PhaseSimulations) QTest::newRow("Default") /* Simulation info */ << "simulation-2-phase-32A" // simulationName << "Simulation (2 phase, charger 32A max, target 22:00 100%)" // simulationTitle << ":/databases/2022-06-28-energylogs-micha.sqlite" // databaseName << EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(6,0,0)) // simulationStart (UTC) << ChargerPlugEvents() // pluggedInTime (UTC) << 18 // simulationHours << EnergyLogs::SampleRate1Min << 20.0 // productionScaling << 0 // detailsStepStart << 0 // detailsStepStop << DetailsStepList() // detailsStepList /* Houshold info */ << 32 // phase limit (A) << false // spotMarketEnabled << ":/resources/dataset-1.json" // spotMarketResourceData << 0.5 // acquisitionTolerance << 0.9 // batteryLevelConsideration /* Charging Info */ << 100.0 // targetPercentage << EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(22,0,0)) // targetDateTime << "ChargingModeEcoWithTargetTime" // chargingMode << 40 // carBatteryLevel << 0 // dailySpotMarketPercentage /* Car settings */ << 50 // carCapacity << 6 // carMinChargingCurrent << 2 // carPhaseCount /* Energy storage */ << false // energyStorageAvailable << 0 // energyStorageCapacity << 0.0 // energyStorageMaxChargingPower << 0.0 // energyStorageMaxDischargingPower << 50.0 // energyStorageInitialBatteyLevel << true // chargerConnected << false // chargerPower << "ABC" // chargerPhases << false // canSwitchPhaseCount << 6 // chargerMaxChargingCurrent << 32 //chargerMaxChargingCurrentMaxValue << SimulationIterationTest ( { { 0, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 40) } }, { 100, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 300, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 9), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 61) } }, { 470, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false) } }, { 586, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false) } }, { 800, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false) } }, { 950, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 30), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 99) } }, { 960, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 100) } } } ); if (run3PhaseSimulations) QTest::newRow("Default") /* Simulation info */ << "simulation-3-phase-16A" // simulationName << "Simulation (3 phase, charger 16A max, target 22:00 100%)" // simulationTitle << ":/databases/2022-06-28-energylogs-micha.sqlite" // databaseName << EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(6,0,0)) // simulationStart (UTC) << ChargerPlugEvents() // pluggedInTime (UTC) << 18 // simulationHours << EnergyLogs::SampleRate1Min << 40.0 // productionScaling << 0 // detailsStepStart << 0 // detailsStepStop << DetailsStepList({200, 300, 400, 500}) // detailsStepList /* Houshold info */ << 32 // phase limit (A) << false // spotMarketEnabled << ":/resources/dataset-1.json" // spotMarketResourceData << 0.5 // acquisitionTolerance << 0.9 // batteryLevelConsideration /* Charging Info */ << 100.0 // targetPercentage << EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(22,0,0)) // targetDateTime << "ChargingModeEcoWithTargetTime" // chargingMode << 40 // carBatteryLevel << 0 // dailySpotMarketPercentage /* Car settings */ << 50 // carCapacity << 6 // carMinChargingCurrent << 3 // carPhaseCount /* Energy storage */ << false // energyStorageAvailable << 0 // energyStorageCapacity << 0.0 // energyStorageMaxChargingPower << 0.0 // energyStorageMaxDischargingPower << 50.0 // energyStorageInitialBatteyLevel << true // chargerConnected << false // chargerPower << "ABC" // chargerPhases << false // canSwitchPhaseCount << 6 // chargerMaxChargingCurrent << 16 //chargerMaxChargingCurrentMaxValue << SimulationIterationTest ( { { 0, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 40) } }, { 200, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 11), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 57) } }, { 300, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 13), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 83) } }, { 400, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 11), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 100) } }, { 700, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 100) } } } ); if (run3PhaseSimulations) QTest::newRow("Default") /* Simulation info */ << "simulation-3-phase-32A" // simulationName << "Simulation (3 phase, charger 32A max, target 22:00 100%)" // simulationTitle << ":/databases/2022-06-28-energylogs-micha.sqlite" // databaseName << EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(6,0,0)) // simulationStart (UTC) << ChargerPlugEvents() // pluggedInTime (UTC) << 18 // simulationHours << EnergyLogs::SampleRate1Min << 20.0 // productionScaling << 0 // detailsStepStart << 0 // detailsStepStop << DetailsStepList({ 480, 500, 600, 950, 960 }) // detailsStepList /* Houshold info */ << 32 // phase limit (A) << false // spotMarketEnabled << ":/resources/dataset-1.json" // spotMarketResourceData << 0.5 // acquisitionTolerance << 0.9 // batteryLevelConsideration /* Charging Info */ << 100.0 // targetPercentage << EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(22,0,0)) // targetDateTime << "ChargingModeEcoWithTargetTime" // chargingMode << 40 // carBatteryLevel << 0 // dailySpotMarketPercentage /* Car settings */ << 50 // carCapacity << 6 // carMinChargingCurrent << 3 // carPhaseCount /* Energy storage */ << false // energyStorageAvailable << 0 // energyStorageCapacity << 0.0 // energyStorageMaxChargingPower << 0.0 // energyStorageMaxDischargingPower << 50.0 // energyStorageInitialBatteyLevel << true // chargerConnected << false // chargerPower << "ABC" // chargerPhases << false // canSwitchPhaseCount << 6 // chargerMaxChargingCurrent << 32 //chargerMaxChargingCurrentMaxValue << SimulationIterationTest ( { { 100, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 40) } }, { 200, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 300, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 400, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true) } }, { 480, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false) } }, { 500, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 82) } }, { 600, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false) } }, { 950, { SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 98) // Finish 10 min early } }, { 960, { // 22:00 SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 100) } } } ); if (runPhaseSwitchingSimulations) QTest::newRow("Default") /* Simulation info */ << "simulation-phase-switching-16A" // simulationName << "Simulation (phase switching, charger 16A max, surplus only)" // simulationTitle << ":/databases/2022-06-28-energylogs-micha.sqlite" // databaseName << EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(6,0,0)) // simulationStart (UTC) << ChargerPlugEvents() // pluggedInTime (UTC) << 18 // simulationHours << EnergyLogs::SampleRate1Min << 45.0 // productionScaling << 0 // detailsStepStart << 0 // detailsStepStop << DetailsStepList({80, 150, 310, 400, 470}) // detailsStepList /* Houshold info */ << 32 // phase limit (A) << false // spotMarketEnabled << ":/resources/dataset-1.json" // spotMarketResourceData << 0.5 // acquisitionTolerance << 0.9 // batteryLevelConsideration /* Charging Info */ << 100.0 // targetPercentage << EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(22,0,0)) // targetDateTime << "ChargingModeEco" // chargingMode << 40 // carBatteryLevel << 0 // dailySpotMarketPercentage /* Car settings */ << 50 // carCapacity << 6 // carMinChargingCurrent << 3 // carPhaseCount /* Energy storage */ << false // energyStorageAvailable << 0 // energyStorageCapacity << 0.0 // energyStorageMaxChargingPower << 0.0 // energyStorageMaxDischargingPower << 50.0 // energyStorageInitialBatteyLevel << true // chargerConnected << false // chargerPower << "ABC" // chargerPhases << true // canSwitchPhaseCount << 6 // chargerMaxChargingCurrent << 16 //chargerMaxChargingCurrentMaxValue << SimulationIterationTest ( { { 0, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 40) } }, { 80, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 40) } }, { 150, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 10), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 50) } }, { 310, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 16), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 91) } }, { 400, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 13), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 100) } }, { 470, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 9), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, true), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 100) } }, } ); if (runPhaseSwitchingSimulations) QTest::newRow("Default") /* Simulation info */ << "simulation-energy-storage-phase-switching" // simulationName << "Simulation energy storage, phase switching" // simulationTitle << ":/databases/2022-06-28-energylogs-micha.sqlite" // databaseName << EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(0,0,0)) // simulationStart (UTC) << ChargerPlugEvents() // pluggedInTime (UTC) << 36 // simulationHours << EnergyLogs::SampleRate1Min << 45.0 // productionScaling << 0 // detailsStepStart << 10 // detailsStepStop << DetailsStepList({}) // detailsStepList /* Houshold info */ << 32 // phase limit (A) << false // spotMarketEnabled << ":/resources/dataset-1.json" // spotMarketResourceData << 0.5 // acquisitionTolerance << 0.9 // batteryLevelConsideration /* Charging Info */ << 100.0 // targetPercentage << EnergyTestBase::utcDateTime(QDate(2022, 6, 27), QTime(22,0,0)) // targetDateTime << "ChargingModeEco" // chargingMode << 40 // carBatteryLevel << 0 // dailySpotMarketPercentage /* Car settings */ << 50 // carCapacity << 6 // carMinChargingCurrent << 3 // carPhaseCount /* Energy storage */ << true // energyStorageAvailable << 12 // energyStorageCapacity << 5000.0 // energyStorageMaxChargingPower << 5000.0 // energyStorageMaxDischargingPower << 10.0 // energyStorageInitialBatteyLevel << true // chargerConnected << false // chargerPower << "ABC" // chargerPhases << true // canSwitchPhaseCount << 6 // chargerMaxChargingCurrent << 16 //chargerMaxChargingCurrentMaxValue << SimulationIterationTest ( { { 0, { SimulationTestPoint(SimulationTestPoint::TestTypeMaxChargingCurrent, 6), SimulationTestPoint(SimulationTestPoint::TestTypeCharging, false), SimulationTestPoint(SimulationTestPoint::TestTypeStateOfCharge, 40) } } } ); } void Simulation::run() { QFETCH(QString, simulationName); QFETCH(QString, simulationTitle); QFETCH(QString, databaseName); QFETCH(QDateTime, simulationStart); QFETCH(ChargerPlugEvents, plugEvents); QFETCH(int, simulationHours); QFETCH(EnergyLogs::SampleRate, sampleRate); QFETCH(double, productionScaling); QFETCH(int, detailsStepStart); QFETCH(int, detailsStepStop); QFETCH(DetailsStepList, detailsStepList); QFETCH(int, phasePowerLimit); QFETCH(bool, spotMarketEnabled); QFETCH(QString, spotMarketResourceData); QFETCH(double, acquisitionTolerance); QFETCH(double, batteryLevelConsideration); QFETCH(double, targetPercentage); QFETCH(QDateTime, targetDateTime); QFETCH(QString, chargingMode); QFETCH(int, carBatteryLevel); QFETCH(int, dailySpotMarketPercentage); QFETCH(int, carCapacity); QFETCH(int, carMinChargingCurrent); QFETCH(int, carPhaseCount); QFETCH(bool, energyStorageAvailable); QFETCH(int, energyStorageCapacity); QFETCH(double, energyStorageMaxChargingPower); QFETCH(double, energyStorageMaxDischargingPower); QFETCH(double, energyStorageInitialBatteyLevel); QFETCH(bool, chargerConnected); QFETCH(bool, chargerPower); QFETCH(QString, chargerPhases); QFETCH(bool, canSwitchPhaseCount); QFETCH(int, chargerMaxChargingCurrent); QFETCH(int, chargerMaxChargingCurrentMaxValue); QFETCH(SimulationIterationTest, iterationTest); QStringList loggingDefaultList = { "*.debug=false", "Application.debug=true", "LogEngine.info=false", "Simulation.debug=true", "Experiences.debug=false", "NymeaEnergy.debug=false", "EnergyMocks.debug=false", "DBus.warning=false", }; QString loggingRulesDefault = loggingDefaultList.join("\n"); QStringList loggingDetailsList = { "*.debug=false", "Application.debug=true", "LogEngine.info=false", "Simulation.debug=true", "Experiences.debug=false", "NymeaEnergy.debug=true", "EnergyMocks.debug=false", "DBus.warning=false", }; QString loggingRulesDetails = loggingDetailsList.join("\n"); QStringList availableChargingModes; availableChargingModes << "ChargingModeNormal"; availableChargingModes << "ChargingModeEco"; availableChargingModes << "ChargingModeEcoWithTargetTime"; QVERIFY2(availableChargingModes.contains(chargingMode), "Unknown charging mode passed to the simulation. Please compair the list with the ChargingMode enum."); if (canSwitchPhaseCount) QVERIFY2(chargerPhases == "ABC", "If the charger supports phase count switching all 3 phases must be connected."); cleanupTestCase(); m_energyLogDbFilePath = databaseName; initTestCase(loggingRulesDefault); // Print simulation init details QLoggingCategory::setFilterRules(loggingRulesDefault); QVariant response; QVariantMap params; QNetworkReply *reply = nullptr; QSignalSpy packetSpy(m_mockTcpServer, &MockTcpServer::outgoingData);; Electricity::Phases chargerPhasesConverted = Electricity::convertPhasesFromString(chargerPhases); // Set phase power limit response = injectAndWait("NymeaEnergy.SetPhasePowerLimit", QVariantMap({{"phasePowerLimit", phasePowerLimit}})); QVERIFY(response.toMap().value("params").toMap().value("energyError").toString() == "EnergyErrorNoError"); // Add mock spotmarket provider SpotMarketManager *spotMarketManager = m_experiencePlugin->spotMarketManager(); SpotMarketDataProviderMock *mockProvider = new SpotMarketDataProviderMock(nullptr, this); QVERIFY(mockProvider->prepareResourceData(spotMarketResourceData, simulationStart.toUTC())); QVERIFY(spotMarketManager->registerProvider(mockProvider)); QVERIFY(spotMarketManager->changeProvider(mockProvider->providerId())); // Enabke/disable spot market response = injectAndWait("NymeaEnergy.SetSpotMarketConfiguration", QVariantMap({ {"enabled", spotMarketEnabled }, {"providerId", mockProvider->providerId()} })); QCOMPARE(response.toMap().value("params").toMap().value("energyError").toString(), "EnergyErrorNoError"); QCOMPARE(m_experiencePlugin->spotMarketManager()->enabled(), spotMarketEnabled); // Set initial acquisition tolerance params.clear(); response.clear(); params.insert("acquisitionTolerance", acquisitionTolerance); response = injectAndWait("NymeaEnergy.SetAcquisitionTolerance", params); verifyEnergyError(response); // Set battery level consideration params.clear(); response.clear(); params.insert("batteryLevelConsideration", batteryLevelConsideration); response = injectAndWait("NymeaEnergy.SetBatteryLevelConsideration", params); verifyEnergyError(response); // Add mock meter QUuid meterThingId = addMeter(); QVERIFY2(!meterThingId.isNull(), "Did not receive valid ThingId"); if (packetSpy.count() == 0) packetSpy.wait(); checkNotification(packetSpy, "Integrations.ThingAdded"); packetSpy.clear(); // Set it as root meter m_experiencePlugin->energyManager()->setRootMeter(meterThingId); // Make sure this is our root meter now response = injectAndWait("Energy.GetRootMeter"); QCOMPARE(response.toMap().value("params").toMap().value("rootMeterThingId").toUuid(), meterThingId); packetSpy.clear(); // Add the charger QUuid evChargerId; if (canSwitchPhaseCount) { evChargerId = addChargerWithPhaseCountSwitching(chargerPhases, chargerMaxChargingCurrentMaxValue); } else { evChargerId = addCharger(chargerPhases, chargerMaxChargingCurrentMaxValue); } QVERIFY2(!evChargerId.isNull(), "Did not receive valid ThingId"); if (packetSpy.count() == 0) packetSpy.wait(); checkNotification(packetSpy, "Integrations.ThingAdded"); // Add the car QUuid carThingId = addCar(); QVERIFY2(!carThingId.isNull(), "Did not receive valid ThingId"); if (packetSpy.count() == 0) packetSpy.wait(); checkNotification(packetSpy, "Integrations.ThingAdded"); // Add energy storage if available QUuid energyStorageThingId; if (energyStorageAvailable) { energyStorageThingId = addEnergyStorage(energyStorageCapacity, energyStorageMaxChargingPower, energyStorageMaxDischargingPower); QVERIFY2(!energyStorageThingId.isNull(), "Did not receive valid ThingId"); if (packetSpy.count() == 0) packetSpy.wait(); checkNotification(packetSpy, "Integrations.ThingAdded"); } // ============================================================================== // Set states of car and charger Thing *carThing = NymeaCore::instance()->thingManager()->findConfiguredThing(carThingId); QVERIFY2(carThing != nullptr, "Failed to find car thing"); carThing->setSettingValue(carThing->thingClass().settingsTypes().findByName("phaseCount").id(), carPhaseCount); carThing->setSettingValue(carThing->thingClass().settingsTypes().findByName("capacity").id(), carCapacity); carThing->setSettingValue(carThing->thingClass().settingsTypes().findByName("minChargingCurrent").id(), carMinChargingCurrent); carThing->setStateValue("batteryLevel", carBatteryLevel); Thing *chargerThing = NymeaCore::instance()->thingManager()->findConfiguredThing(evChargerId); QVERIFY2(chargerThing != nullptr, "Failed to find charger thing"); chargerThing->setStateValue("connected", chargerConnected); chargerThing->setStateValue("power", chargerPower); chargerThing->setStateValue("maxChargingCurrent", chargerMaxChargingCurrent); chargerThing->setStateValue("maxChargingCurrentMaxValue", chargerMaxChargingCurrentMaxValue); chargerThing->setStateValue("pluggedIn", true); // Initially always plugged in, the rest can be handeld using the plug events // This will update all internal states not set directly updateChargerMeter(chargerThing); Thing *meterThing = NymeaCore::instance()->thingManager()->findConfiguredThing(meterThingId); QVERIFY2(meterThing != nullptr, "Failed to find meter thing"); meterThing->setStateValue("connected", true); // Energy storage states Thing *energyStorageThing = nullptr; if (energyStorageAvailable) { energyStorageThing = NymeaCore::instance()->thingManager()->findConfiguredThing(energyStorageThingId); energyStorageThing->setStateValue("currentPower", 0); energyStorageThing->setStateValue("batteryLevel", energyStorageInitialBatteyLevel); energyStorageThing->setProperty("preciseBatteryLevel", energyStorageInitialBatteyLevel * 1.0); // For precise runtime calculations printStates(energyStorageThing); } // printStates(chargerThing); // printStates(carThing); // printStates(meterThing); // Set charging info with our charger and car, this should trigger the evaluation QVariantMap chargingInfoMap; chargingInfoMap.insert("evChargerId", evChargerId); chargingInfoMap.insert("assignedCarId", carThingId); chargingInfoMap.insert("chargingMode", chargingMode); chargingInfoMap.insert("endDateTime", targetDateTime.toMSecsSinceEpoch() / 1000); chargingInfoMap.insert("targetPercentage", targetPercentage); chargingInfoMap.insert("spotMarketChargingEnabled", spotMarketEnabled); chargingInfoMap.insert("dailySpotMarketPercentage", dailySpotMarketPercentage); response = injectAndWait("NymeaEnergy.SetChargingInfo", QVariantMap({{"chargingInfo", chargingInfoMap}})); QCOMPARE(response.toMap().value("status").toString(), QString("success")); QCOMPARE(response.toMap().value("params").toMap().value("energyError").toString(), QString("EnergyErrorNoError")); uint effectivePhaseCount; if (canSwitchPhaseCount) { effectivePhaseCount = chargerThing->stateValue("phaseCount").toUInt(); } else { effectivePhaseCount = qMin((uint)carPhaseCount, Electricity::getPhaseCount(chargerPhasesConverted)); } QString usedPhases; if (effectivePhaseCount >= 1) usedPhases.append("A"); if (effectivePhaseCount >= 2) usedPhases.append("B"); if (effectivePhaseCount >= 3) usedPhases.append("C"); chargerThing->setStateValue("usedPhases", usedPhases); // Note: we want to have the limit negative, so we see better where the limit is and why the chaging stopped double aquisitionToleranceLimit = -(effectivePhaseCount * carMinChargingCurrent * 230.0 * acquisitionTolerance); // Simulation output QDir simulationBaseDir = QDir(QDir::currentPath() + QDir::separator() + "simulations"); QDir workspaceDir = QDir(simulationBaseDir.absolutePath() + QDir::separator() + "workspace"); if (!workspaceDir.exists()) { //QVERIFY2(outputDir.removeRecursively(), "Failed to cleanup output dir"); QVERIFY2(workspaceDir.mkpath(workspaceDir.path()), "Failed to create results dir"); } QDir outputDir = QDir(workspaceDir.absolutePath() + QDir::separator() + simulationName); if (!outputDir.exists()) { // QVERIFY2(outputDir.removeRecursively(), "Failed to cleanup output dir"); QVERIFY2(outputDir.mkpath(outputDir.path()), "Failed to create output dir"); } QDir resultsDir = QDir(simulationBaseDir.absolutePath() + QDir::separator() + "results"); if (!resultsDir.exists()) { //QVERIFY2(outputDir.removeRecursively(), "Failed to cleanup output dir"); QVERIFY2(resultsDir.mkpath(resultsDir.path()), "Failed to create results dir"); } QFile gnuplotLogFile(outputDir.path() + QDir::separator() + "simulation.csv"); QVERIFY2(gnuplotLogFile.open(QIODevice::ReadWrite | QIODevice::Truncate), QString("Failed to open logfile for gnuplot" + gnuplotLogFile.fileName() + ": " + gnuplotLogFile.errorString()).toUtf8()); QTextStream gnuplotLogFileStream(&gnuplotLogFile); QFile gnuplotOriginalLogFile(outputDir.path() + QDir::separator() + "original.csv"); QVERIFY2(gnuplotOriginalLogFile.open(QIODevice::ReadWrite | QIODevice::Truncate), "Failed to open logfile for gnuplot"); QTextStream gnuplotOriginalLogFileStream(&gnuplotOriginalLogFile); // ============================================================================== // All set up, lets start simulating QDateTime simulationEnd = simulationStart.addSecs(simulationHours * 3600); PowerBalanceLogEntries powerBalanceLogs = m_experiencePlugin->energyManager()->logs()->powerBalanceLogs(sampleRate, simulationStart, simulationEnd); qCDebug(dcSimulation()) << "Simulation start" << simulationStart; qCDebug(dcSimulation()) << "Simulation end" << simulationEnd; qCDebug(dcSimulation()) << "Loaded" << powerBalanceLogs.count() << "log entries for that day"; // Simulation helpers int simulationProgress = 0; // Disable init details QLoggingCategory::setFilterRules(loggingRulesDefault); // Run the simulation for (int i = 0; i < powerBalanceLogs.count(); i++) { bool debugEnabled = (i >= detailsStepStart && i <= detailsStepStop) || detailsStepList.contains(i); const PowerBalanceLogEntry entry = powerBalanceLogs.at(i); QDateTime currentDateTime = entry.timestamp().toUTC(); // Calculate progress if (debugEnabled) { qCDebug(dcSimulation()) << "###############################################################################################################"; qCDebug(dcSimulation()) << "Step" << i << ":" << currentDateTime.toUTC().toString("yyyy.MM.dd hh:mm"); } effectivePhaseCount = chargerThing->stateValue("phaseCount").toUInt(); usedPhases = chargerThing->stateValue("usedPhases").toString(); // Update mocked spotmarket data provider mockProvider->setCurrentDataTime(currentDateTime.toUTC()); // Print simulation progress double simulationProgressPrecise = i * 100 / powerBalanceLogs.count(); if (simulationProgress != qRound(simulationProgressPrecise)) { simulationProgress = qRound(simulationProgressPrecise); if (simulationProgress % 10 == 0) { qCDebug(dcSimulation()) << simulationName << currentDateTime.toUTC().toString("hh:mm") << simulationProgress << "% (" << i << ")"; } } // Enable logs in the interesting simulation steps if (debugEnabled) { QLoggingCategory::setFilterRules(loggingRulesDetails); } else { QLoggingCategory::setFilterRules(loggingRulesDefault); } // Get the current situation befor running the simulation step double totalProduction = entry.production() * productionScaling; double totalProductionDifference = totalProduction - entry.production(); double scaledCurrentPower = entry.acquisition() + totalProductionDifference; //qCDebug(dcSimulation()) << "Scale production using" << productionScaling << entry.production() << "-->" << totalProduction << totalProductionDifference; //qCDebug(dcSimulation()) << "Scale aquisition using" << entry.acquisition() << "-->" << scaledCurrentPower; // Distribute the load before battery and charger on 3 phases double phaseLoad = scaledCurrentPower / 3; // -------------------- Charger // Handle plug events foreach(const ChargerPlugEvent &plugEvent, plugEvents) { if (plugEvent.dateTime.date() == currentDateTime.toUTC().date() && plugEvent.dateTime.time().hour() == currentDateTime.toUTC().time().hour() && plugEvent.dateTime.time().minute() == currentDateTime.toUTC().time().minute()) { // Plug event chargerThing->setStateValue("pluggedIn", plugEvent.pluggedIn); if (plugEvent.percentageUsed != 0) { double batteryLevel = carThing->stateValue("batteryLevel").toDouble(); batteryLevel -= plugEvent.percentageUsed; if (batteryLevel < 0) { batteryLevel = 0; } qCDebug(dcSimulation()) << "-->" << currentDateTime.toUTC().toString("hh:mm") << "New car battery level" << batteryLevel << "(old:" << carThing->stateValue("batteryLevel").toDouble() << ")"; carThing->setStateValue("batteryLevel", batteryLevel); } qCDebug(dcSimulation()) << "-->" << currentDateTime.toUTC().toString("hh:mm") << "Car has been" << (plugEvent.pluggedIn ? "plugged in" : "unplugged"); } } // Let the charger set all power, voltage etc.... updateChargerMeter(chargerThing); double chargerCurrentPower = chargerThing->stateValue("currentPower").toDouble(); double totalConsumption = chargerCurrentPower + entry.consumption(); // Totals before battery double totalCurrentPower = totalConsumption + totalProduction; // All producers and all consumers have been summed up, charge / discharge the battery and create a final total balance // -------------------- Energy storage double energyStorageCurrentPower = 0; uint energyStorageBatteryLevel = 0; // All consumers should have what they get, put the rest into or from the storage within limits if (energyStorageAvailable) { // Calculate the new battery level depending on the previouse step. double esCurrentPower = energyStorageThing->stateValue("currentPower").toDouble(); double esBatteryLevel = energyStorageThing->property("preciseBatteryLevel").toDouble(); double esCapacity = energyStorageThing->stateValue("capacity").toDouble(); // Let's caclulate the new percentage depending on the rate of the last minute... // We charged/discharged the last minute with energyStorageCurrentPower W double energyChargedDischargedkWh = esCurrentPower * 60 / 60 / 60 / 1000; double addedPercentage = energyChargedDischargedkWh * 100.0 / esCapacity; double newBatteryLevel = esBatteryLevel += addedPercentage; if (totalCurrentPower < 0 && newBatteryLevel < 100) { energyStorageCurrentPower = qMin(energyStorageMaxChargingPower, -totalCurrentPower); energyStorageBatteryLevel = newBatteryLevel; energyStorageThing->setProperty("preciseBatteryLevel", newBatteryLevel); setEnergyStorageStates(energyStorageBatteryLevel, energyStorageCurrentPower); } else if (totalCurrentPower > 0 && newBatteryLevel > 0) { energyStorageCurrentPower = - qMin(energyStorageMaxDischargingPower, totalCurrentPower); energyStorageBatteryLevel = newBatteryLevel; energyStorageThing->setProperty("preciseBatteryLevel", newBatteryLevel); setEnergyStorageStates(energyStorageBatteryLevel, energyStorageCurrentPower); } else { energyStorageBatteryLevel = newBatteryLevel; energyStorageThing->setProperty("preciseBatteryLevel", newBatteryLevel); setEnergyStorageStates(energyStorageBatteryLevel, energyStorageCurrentPower); } //qCDebug(dcSimulation()) << "Energy storage charged with" << energyStorageCurrentPower << "W" << addedPercentage << "% added to total" << energyStorageBatteryLevel << "%"; } // -------------------- Meter totalCurrentPower += energyStorageCurrentPower; phaseLoad += energyStorageCurrentPower / 3; // Add the charger power in the aproperiate phase QVariantMap phases = QVariantMap({ {"A", phaseLoad + chargerThing->stateValue("currentPowerPhaseA").toDouble()}, {"B", phaseLoad + chargerThing->stateValue("currentPowerPhaseB").toDouble()}, {"C", phaseLoad + chargerThing->stateValue("currentPowerPhaseC").toDouble()} }); reply = setMeterStates(phases, true); QSignalSpy setMeterStatesReplySpy(reply, &QNetworkReply::finished); if (setMeterStatesReplySpy.count() == 0) setMeterStatesReplySpy.wait(); QCOMPARE(reply->error(), QNetworkReply::NoError); // -------------------- Run charging manager update // Set charger information and pass them to the logic ThingPowerLogEntry chargerPowerEntry(currentDateTime.toUTC(), chargerThing->id(), chargerThing->stateValue("currentPower").toDouble(), 0, 0); m_experiencePlugin->smartChargingManager()->simulationCallUpdateManualSoCsWithMeter(sampleRate, chargerPowerEntry); // Update smart charging manager with the current root meter and charger situation m_experiencePlugin->smartChargingManager()->simulationCallUpdate(currentDateTime.toUTC()); // Fetch information after simulation iteration double carBatteryPercentage = carThing->stateValue("batteryLevel").toDouble(); int maxChargingCurrent = chargerThing->stateValue("maxChargingCurrent").toInt(); chargerPower = chargerThing->stateValue("power").toBool(); chargerCurrentPower = chargerThing->stateValue("currentPower").toDouble(); if (debugEnabled | iterationTest.contains(i)) { qCDebug(dcSimulation()) << "Step" << i; qCDebug(dcSimulation()) << "- Total power:" << totalCurrentPower << "Production:" << totalProduction << "Consumption:" << totalConsumption; if (energyStorageAvailable) { qCDebug(dcSimulation()) << "- Energy storage:" << energyStorageCurrentPower << energyStorageThing->property("preciseBatteryLevel").toDouble() << "%"; } qCDebug(dcSimulation()) << "- Meter phases: A:" << meterThing->stateValue("currentPowerPhaseA").toDouble() << "W | B:" << meterThing->stateValue("currentPowerPhaseB").toDouble() << "W | C:" << meterThing->stateValue("currentPowerPhaseC").toDouble() << "W"; qCDebug(dcSimulation()) << "- Charger:" << chargerCurrentPower << "[W] (" << maxChargingCurrent << "[A]" << (chargerCurrentPower ? "On)" : "Off )") << effectivePhaseCount << usedPhases; qCDebug(dcSimulation()) << "- Charger phases: A:" << chargerThing->stateValue("currentPowerPhaseA").toDouble() << "W | B:" << chargerThing->stateValue("currentPowerPhaseB").toDouble() << "W | C:" << chargerThing->stateValue("currentPowerPhaseC").toDouble() << "W"; qCDebug(dcSimulation()) << "- Car battery:" << carBatteryPercentage; qCDebug(dcSimulation()) << "--------------------------------"; // printStates(meterThing); // printStates(chargerThing); // printStates(carThing); } // Verify test points foreach(const SimulationTestPoint &testPoint, iterationTest.value(i)) { switch(testPoint.testType()) { case SimulationTestPoint::TestTypeCharging: QVERIFY2(chargerPower == testPoint.expectedValue().toBool(), qPrintable(QString("Simulation: %1 - %2 Step: %3 expected \"%4\" from the testpoint but is actually \"%5\"") .arg(simulationName) .arg(simulationTitle) .arg(i) .arg(testPoint.expectedValue().toBool() ? "true" : "false") .arg(chargerPower ? "true" : "false"))); break; case SimulationTestPoint::TestTypeMaxChargingCurrent: QVERIFY2(maxChargingCurrent == testPoint.expectedValue().toInt(), qPrintable(QString("Simulation: %1 - %2 Step: %3 expected \"%4\" from the testpoint but is actually \"%5\"") .arg(simulationName) .arg(simulationTitle) .arg(i) .arg(testPoint.expectedValue().toInt()) .arg(maxChargingCurrent))); break; case SimulationTestPoint::TestTypeStateOfCharge: QVERIFY2(qFuzzyCompare(carBatteryPercentage, testPoint.expectedValue().toDouble()), qPrintable(QString("Simulation: %1 - %2 Step: %3 expected \"%4\" from the testpoint but is actually \"%5\"") .arg(simulationName) .arg(simulationTitle) .arg(i) .arg(testPoint.expectedValue().toDouble()) .arg(carBatteryPercentage))); break; } } // -------------------- Data logging // Log the simulation data gnuplotLogFileStream << currentDateTime.toMSecsSinceEpoch() / 1000 << ", " << // 1 totalCurrentPower << ", " << // 2 totalProduction << ", " << // 3 totalConsumption << ", " << // 4 chargerCurrentPower << ", " << // 5 maxChargingCurrent << ", " << // 6 (chargerPower ? "1" : "0") << ", " << // 7 chargerThing->state("maxChargingCurrent").minValue().toDouble() * 230 * effectivePhaseCount << ", " << // 8 chargerThing->state("maxChargingCurrent").maxValue().toDouble() * 230 * effectivePhaseCount << ", " << // 9 phasePowerLimit * 230 * effectivePhaseCount << ", " << // 10 carBatteryPercentage << ", " << // 11 i << ", " << // 12 aquisitionToleranceLimit << ", " << // 13 (chargerThing->stateValue("pluggedIn").toBool() ? 10 : 0 ) << ", "; // 14 if (spotMarketEnabled) { const ScoreEntries weightedEntries = spotMarketManager->weightedScoreEntries(currentDateTime.date()); const ScoreEntry currentScore = weightedEntries.getScoreEntry(currentDateTime.toUTC()); QVERIFY(!currentScore.isNull()); gnuplotLogFileStream << currentScore.weighting() * 100 << ", "; // 15 gnuplotLogFileStream << currentScore.value() / 10.0 << ", "; // 16 Price } else { gnuplotLogFileStream << 0 << ", "; // 15 gnuplotLogFileStream << 0 << ", "; // 16 } if (energyStorageAvailable) { gnuplotLogFileStream << energyStorageCurrentPower << ", "; // 17 gnuplotLogFileStream << energyStorageBatteryLevel << ", "; // 18 } else { gnuplotLogFileStream << 0 << ", "; // 17 gnuplotLogFileStream << 0 << ", "; // 18 } gnuplotLogFileStream << "\n"; // Log Unchanged for raw data analysis gnuplotOriginalLogFileStream << currentDateTime.toMSecsSinceEpoch() / 1000 << ", " << // 1 scaledCurrentPower << ", " << // 2 totalProduction << ", " << // 3 entry.consumption() << ", " << // 4 phasePowerLimit * 230 * effectivePhaseCount << ", " << // 5 i << ", " << // 6 "\n"; } gnuplotLogFile.close(); gnuplotOriginalLogFile.close(); // Draw original data QStringList scriptLines; QStringList plotLines; // Plot with: 1h = 200 px int height = 800; int width = simulationHours * 200; QString originalImageName = simulationName + "-00-original.png"; QString simulationImageName = simulationName + "-01.png"; scriptLines.append("set term png size " + QString::number(width) + "," + QString::number(height)); scriptLines.append("set output '" + originalImageName + "'"); scriptLines.append("set datafile separator ','"); scriptLines.append(plotOriginalData(powerBalanceLogs.count())); if (spotMarketEnabled) { scriptLines.append("set term png size " + QString::number(width) + "," + QString::number(height * 2)); scriptLines.append("set output '" + simulationImageName + "'"); scriptLines.append("set datafile separator ','"); scriptLines.append("set multiplot layout 2,1"); scriptLines.append("set size 1,0.8"); scriptLines.append("set origin 0,0.2"); scriptLines.append(plotSimulation(simulationTitle, powerBalanceLogs.count())); scriptLines.append("set size 1,0.2"); scriptLines.append("set origin 0,0"); scriptLines.append(plotSpotMarketData(powerBalanceLogs.count())); scriptLines.append("unset multiplot"); } else { scriptLines.append("set term png size " + QString::number(width) + "," + QString::number(height)); scriptLines.append("set output '" + simulationImageName + "'"); scriptLines.append("set datafile separator ','"); scriptLines.append(plotSimulation(simulationTitle, powerBalanceLogs.count())); } // Write the gnuplot script QFile gnuplotScript(outputDir.path() + QDir::separator() + "script.gnuplot"); QVERIFY2(gnuplotScript.open(QIODevice::ReadWrite | QIODevice::Truncate), QString("Failed to open script file for gnuplot" + gnuplotScript.fileName() + ": " + gnuplotScript.errorString()).toUtf8()); QTextStream scriptStream(&gnuplotScript); foreach (const QString &line, scriptLines) scriptStream << line << "\n"; gnuplotScript.close(); // Write the executable gnuplot script scriptLines.clear(); scriptLines.append("set terminal wxt 1 persist"); scriptLines.append("set datafile separator ','"); if (spotMarketEnabled) { scriptLines.append("set multiplot layout 2,1"); scriptLines.append("set size 1,0.8"); scriptLines.append("set origin 0,0.2"); scriptLines.append(plotSimulation(simulationTitle, powerBalanceLogs.count())); scriptLines.append("set size 1,0.2"); scriptLines.append("set origin 0,0"); scriptLines.append(plotSpotMarketData(powerBalanceLogs.count())); scriptLines.append("unset multiplot"); } else { scriptLines.append(plotSimulation(simulationTitle, powerBalanceLogs.count())); } QString executableScriptName = simulationName + ".gnuplot"; QFile executableGnuplotScript(outputDir.path() + QDir::separator() + executableScriptName); QVERIFY2(executableGnuplotScript.open(QIODevice::ReadWrite | QIODevice::Truncate), QString("Failed to open logfile for gnuplot" + executableGnuplotScript.fileName() + ": " + executableGnuplotScript.errorString()).toUtf8()); QTextStream executableScriptStream(&executableGnuplotScript); foreach (const QString &line, scriptLines) executableScriptStream << line << "\n"; executableGnuplotScript.close(); QProcess gnuplotProcess; //gnuplotProcess.setEnvironment(QProcessEnvironment::systemEnvironment().toStringList()); gnuplotProcess.setProcessChannelMode(QProcess::MergedChannels); gnuplotProcess.setWorkingDirectory(outputDir.path()); gnuplotProcess.start("gnuplot", { "-c", "script.gnuplot"}); gnuplotProcess.waitForFinished(); qCDebug(dcSimulation()) << "gnuplot finished" << gnuplotProcess.arguments() << gnuplotProcess.workingDirectory() << gnuplotProcess.exitCode() << gnuplotProcess.exitStatus(); if (gnuplotProcess.exitCode() != 0) { qCDebug(dcSimulation()) << "error plotting data:\n" << qUtf8Printable(gnuplotProcess.readAll()); QVERIFY2(false, "plot process finished with error"); } // Copy resulting images to the simulations QFile::copy(outputDir.path() + QDir::separator() + originalImageName, resultsDir.path() + QDir::separator() + originalImageName); QFile::copy(outputDir.path() + QDir::separator() + simulationImageName, resultsDir.path() + QDir::separator() + simulationImageName); } void Simulation::printStates(Thing *thing) { qCDebug(dcSimulation()) << "Thing states for" << thing->name(); foreach (const StateType &stateType, thing->thingClass().stateTypes()) { qCDebug(dcSimulation()) << "-->" << stateType.name() << thing->stateValue(stateType.id()); } } void Simulation::updateChargerMeter(Thing *thing) { Action updateChargerAction(thing->thingClass().actionTypes().findByName("update").id(), thing->id()); NymeaCore::instance()->thingManager()->executeAction(updateChargerAction); } QStringList Simulation::plotOriginalData(int powerBalanceCount) { QStringList scriptLines; scriptLines.append("set title 'Original energy data'"); scriptLines.append("set grid"); scriptLines.append("set timefmt '%s'"); scriptLines.append("set xdata time"); scriptLines.append("set xtics 3600"); scriptLines.append("set format x '%H:%M'"); scriptLines.append("set xlabel 'Time'"); scriptLines.append("set ylabel '[W]'"); scriptLines.append("set style fill transparent solid 0.3"); scriptLines.append("set x2tics 100"); scriptLines.append("set xtics nomirror"); scriptLines.append("set x2label 'iterations'"); scriptLines.append("set x2range [0:" + QString::number(powerBalanceCount) + "]"); QStringList plotLines; plotLines.append("'original.csv' using 1:3 with boxes lt rgb '#7F75C23A' title 'Production'"); plotLines.append("'original.csv' using 1:4 with boxes lt rgb '#7F3590F3' title 'Consumption'"); plotLines.append("'original.csv' using 1:5 with line lt rgb 'orange' title 'House limit'"); plotLines.append("'original.csv' using 1:2 with line lt rgb 'red' title 'Meter'"); scriptLines.append("plot \\\n" + plotLines.join(", \\\n")); scriptLines.append(""); return scriptLines; } QStringList Simulation::plotSimulation(const QString &title, int powerBalanceCount) { QStringList scriptLines; scriptLines.append("set title '" + title + "'"); scriptLines.append("set grid"); scriptLines.append("set timefmt '%s'"); scriptLines.append("set xdata time"); scriptLines.append("set format x '%H:%M'"); scriptLines.append("set xtics 3600"); scriptLines.append("set xlabel 'time'"); scriptLines.append("set ylabel '[W]'"); scriptLines.append("set x2tics 100"); scriptLines.append("set xtics nomirror"); scriptLines.append("set x2label 'iterations'"); scriptLines.append("set x2range [0:" + QString::number(powerBalanceCount) + "]"); scriptLines.append("set style fill transparent solid 0.3"); scriptLines.append("set y2tics 10"); scriptLines.append("set ytics nomirror"); scriptLines.append("set y2label '[\%]'"); scriptLines.append("set y2range [0:100]"); QStringList plotLines; plotLines.append("'simulation.csv' using 1:9 title 'Charger range' w filledcurves x1 lc rgb '#fff0f0f0'"); plotLines.append("'simulation.csv' using 1:8 notitle w filledcurves x1 lc rgb '#ffffffff'"); plotLines.append("'simulation.csv' using 1:3 with boxes lt rgb '#0A75C23A' title 'Production'"); plotLines.append("'simulation.csv' using 1:4 with boxes lt rgb '#7F3590F3' title 'Consumption'"); plotLines.append("'simulation.csv' using 1:17 with boxes lt rgb '#7FA020F0' title 'Energy storage'"); plotLines.append("'simulation.csv' using 1:5 with boxes lt rgb '#7FF3DE8A' title 'Charger'"); // plotLines.append("'simulation.csv' using 1:9 with line lt rgb '#EABC01' title 'Charger max'"); // plotLines.append("'simulation.csv' using 1:8 with line lt rgb '#F6CAAF' title 'Charger min'"); plotLines.append("'simulation.csv' using 1:13 with line lt rgb 'green' title 'Acquisition Limit'"); plotLines.append("'simulation.csv' using 1:11 with line lt rgb 'black' axes x1y2 title 'Battery [\%]'"); plotLines.append("'simulation.csv' using 1:18 with line lt rgb 'purple ' axes x1y2 title 'Energy storage [\%]'"); plotLines.append("'simulation.csv' using 1:14 with line lt rgb 'purple' axes x1y2 title 'Car plugged in into charger'"); plotLines.append("'simulation.csv' using 1:10 with line lt rgb 'orange' title 'House limit'"); plotLines.append("'simulation.csv' using 1:2 with line lt rgb 'red' title 'Meter'"); scriptLines.append("plot \\\n" + plotLines.join(", \\\n")); scriptLines.append(""); return scriptLines; } QStringList Simulation::plotSpotMarketData(int powerBalanceCount) { QStringList scriptLines; scriptLines.append("set title 'Spot maket data'"); scriptLines.append("set grid"); scriptLines.append("set timefmt '%s'"); scriptLines.append("set xdata time"); scriptLines.append("set format x '%H:%M'"); scriptLines.append("set xtics 3600"); scriptLines.append("set xlabel 'Time'"); scriptLines.append("set ylabel 'Price [Cent/kWh]'"); scriptLines.append("set x2tics 100"); scriptLines.append("set xtics nomirror"); scriptLines.append("set x2label 'iterations'"); scriptLines.append("set x2range [0:" + QString::number(powerBalanceCount) + "]"); scriptLines.append("set y2tics"); scriptLines.append("set ytics nomirror"); scriptLines.append("set y2label '[\%]'"); scriptLines.append("set y2range [0:100]"); QStringList plotLines; plotLines.append("'simulation.csv' using 1:15 with boxes fs solid lt rgb '#fff0f0f0' axes x1y2 title 'Spotmarket scoring [%]'"); plotLines.append("'simulation.csv' using 1:16 with line lt rgb 'black' axes x1y1 title 'Price [Cent/kWh]'"); scriptLines.append("plot \\\n" + plotLines.join(", \\\n")); scriptLines.append(""); return scriptLines; } QTEST_MAIN(Simulation)