diff --git a/build-test/energyplugin/Makefile b/build-test/energyplugin/Makefile index 31a284e..794902f 100644 --- a/build-test/energyplugin/Makefile +++ b/build-test/energyplugin/Makefile @@ -65,12 +65,14 @@ SOURCES = ../../energyplugin/energymanagerconfiguration.cpp \ ../../energyplugin/spotmarket/spotmarketmanager.cpp \ ../../energyplugin/schedulingstrategies/rulebasedstrategy.cpp \ ../../energyplugin/schedulingstrategies/aistrategy.cpp \ + ../../energyplugin/schedulingstrategies/manualstrategy.cpp \ ../../energyplugin/types/chargingaction.cpp \ ../../energyplugin/types/charginginfo.cpp \ ../../energyplugin/types/chargingprocessinfo.cpp \ ../../energyplugin/types/chargingschedule.cpp \ ../../energyplugin/types/energytimeslot.cpp \ ../../energyplugin/types/flexibleload.cpp \ + ../../energyplugin/types/manualslotconfig.cpp \ ../../energyplugin/types/schedulerconfig.cpp \ ../../energyplugin/types/scoreentry.cpp \ ../../energyplugin/types/smartchargingstate.cpp \ @@ -88,6 +90,7 @@ SOURCES = ../../energyplugin/energymanagerconfiguration.cpp \ moc_ischedulingstrategy.cpp \ moc_rulebasedstrategy.cpp \ moc_aistrategy.cpp \ + moc_manualstrategy.cpp \ moc_chargingaction.cpp \ moc_charginginfo.cpp \ moc_chargingschedule.cpp \ @@ -107,12 +110,14 @@ OBJECTS = energymanagerconfiguration.o \ spotmarketmanager.o \ rulebasedstrategy.o \ aistrategy.o \ + manualstrategy.o \ chargingaction.o \ charginginfo.o \ chargingprocessinfo.o \ chargingschedule.o \ energytimeslot.o \ flexibleload.o \ + manualslotconfig.o \ schedulerconfig.o \ scoreentry.o \ smartchargingstate.o \ @@ -131,6 +136,7 @@ OBJECTS = energymanagerconfiguration.o \ moc_ischedulingstrategy.o \ moc_rulebasedstrategy.o \ moc_aistrategy.o \ + moc_manualstrategy.o \ moc_chargingaction.o \ moc_charginginfo.o \ moc_chargingschedule.o \ @@ -279,12 +285,14 @@ DIST = /usr/lib/x86_64-linux-gnu/qt6/mkspecs/features/spec_pre.prf \ ../../energyplugin/schedulingstrategies/ischedulingstrategy.h \ ../../energyplugin/schedulingstrategies/rulebasedstrategy.h \ ../../energyplugin/schedulingstrategies/aistrategy.h \ + ../../energyplugin/schedulingstrategies/manualstrategy.h \ ../../energyplugin/types/chargingaction.h \ ../../energyplugin/types/charginginfo.h \ ../../energyplugin/types/chargingprocessinfo.h \ ../../energyplugin/types/chargingschedule.h \ ../../energyplugin/types/energytimeslot.h \ ../../energyplugin/types/flexibleload.h \ + ../../energyplugin/types/manualslotconfig.h \ ../../energyplugin/types/schedulerconfig.h \ ../../energyplugin/types/scoreentry.h \ ../../energyplugin/types/smartchargingstate.h \ @@ -302,12 +310,14 @@ DIST = /usr/lib/x86_64-linux-gnu/qt6/mkspecs/features/spec_pre.prf \ ../../energyplugin/spotmarket/spotmarketmanager.cpp \ ../../energyplugin/schedulingstrategies/rulebasedstrategy.cpp \ ../../energyplugin/schedulingstrategies/aistrategy.cpp \ + ../../energyplugin/schedulingstrategies/manualstrategy.cpp \ ../../energyplugin/types/chargingaction.cpp \ ../../energyplugin/types/charginginfo.cpp \ ../../energyplugin/types/chargingprocessinfo.cpp \ ../../energyplugin/types/chargingschedule.cpp \ ../../energyplugin/types/energytimeslot.cpp \ ../../energyplugin/types/flexibleload.cpp \ + ../../energyplugin/types/manualslotconfig.cpp \ ../../energyplugin/types/schedulerconfig.cpp \ ../../energyplugin/types/scoreentry.cpp \ ../../energyplugin/types/smartchargingstate.cpp \ @@ -608,8 +618,8 @@ distdir: FORCE @test -d $(DISTDIR) || mkdir -p $(DISTDIR) $(COPY_FILE) --parents $(DIST) $(DISTDIR)/ $(COPY_FILE) --parents /usr/lib/x86_64-linux-gnu/qt6/mkspecs/features/data/dummy.cpp $(DISTDIR)/ - $(COPY_FILE) --parents ../../energyplugin/energymanagerconfiguration.h ../../energyplugin/energysettings.h ../../energyplugin/evcharger.h ../../energyplugin/nymeaenergyjsonhandler.h ../../energyplugin/rootmeter.h ../../energyplugin/schedulermanager.h ../../energyplugin/schedulersettings.h ../../energyplugin/smartchargingmanager.h ../../energyplugin/spotmarket/spotmarketdataprovider.h ../../energyplugin/spotmarket/spotmarketdataproviderawattar.h ../../energyplugin/spotmarket/spotmarketmanager.h ../../energyplugin/schedulingstrategies/ischedulingstrategy.h ../../energyplugin/schedulingstrategies/rulebasedstrategy.h ../../energyplugin/schedulingstrategies/aistrategy.h ../../energyplugin/types/chargingaction.h ../../energyplugin/types/charginginfo.h ../../energyplugin/types/chargingprocessinfo.h ../../energyplugin/types/chargingschedule.h ../../energyplugin/types/energytimeslot.h ../../energyplugin/types/flexibleload.h ../../energyplugin/types/schedulerconfig.h ../../energyplugin/types/scoreentry.h ../../energyplugin/types/smartchargingstate.h ../../energyplugin/types/timeframe.h ../../energyplugin/energypluginnymea.h $(DISTDIR)/ - $(COPY_FILE) --parents ../../energyplugin/energymanagerconfiguration.cpp ../../energyplugin/energysettings.cpp ../../energyplugin/evcharger.cpp ../../energyplugin/nymeaenergyjsonhandler.cpp ../../energyplugin/rootmeter.cpp ../../energyplugin/schedulermanager.cpp ../../energyplugin/schedulersettings.cpp ../../energyplugin/smartchargingmanager.cpp ../../energyplugin/spotmarket/spotmarketdataprovider.cpp ../../energyplugin/spotmarket/spotmarketdataproviderawattar.cpp ../../energyplugin/spotmarket/spotmarketmanager.cpp ../../energyplugin/schedulingstrategies/rulebasedstrategy.cpp ../../energyplugin/schedulingstrategies/aistrategy.cpp ../../energyplugin/types/chargingaction.cpp ../../energyplugin/types/charginginfo.cpp ../../energyplugin/types/chargingprocessinfo.cpp ../../energyplugin/types/chargingschedule.cpp ../../energyplugin/types/energytimeslot.cpp ../../energyplugin/types/flexibleload.cpp ../../energyplugin/types/schedulerconfig.cpp ../../energyplugin/types/scoreentry.cpp ../../energyplugin/types/smartchargingstate.cpp ../../energyplugin/types/timeframe.cpp ../../energyplugin/energypluginnymea.cpp $(DISTDIR)/ + $(COPY_FILE) --parents ../../energyplugin/energymanagerconfiguration.h ../../energyplugin/energysettings.h ../../energyplugin/evcharger.h ../../energyplugin/nymeaenergyjsonhandler.h ../../energyplugin/rootmeter.h ../../energyplugin/schedulermanager.h ../../energyplugin/schedulersettings.h ../../energyplugin/smartchargingmanager.h ../../energyplugin/spotmarket/spotmarketdataprovider.h ../../energyplugin/spotmarket/spotmarketdataproviderawattar.h ../../energyplugin/spotmarket/spotmarketmanager.h ../../energyplugin/schedulingstrategies/ischedulingstrategy.h ../../energyplugin/schedulingstrategies/rulebasedstrategy.h ../../energyplugin/schedulingstrategies/aistrategy.h ../../energyplugin/schedulingstrategies/manualstrategy.h ../../energyplugin/types/chargingaction.h ../../energyplugin/types/charginginfo.h ../../energyplugin/types/chargingprocessinfo.h ../../energyplugin/types/chargingschedule.h ../../energyplugin/types/energytimeslot.h ../../energyplugin/types/flexibleload.h ../../energyplugin/types/manualslotconfig.h ../../energyplugin/types/schedulerconfig.h ../../energyplugin/types/scoreentry.h ../../energyplugin/types/smartchargingstate.h ../../energyplugin/types/timeframe.h ../../energyplugin/energypluginnymea.h $(DISTDIR)/ + $(COPY_FILE) --parents ../../energyplugin/energymanagerconfiguration.cpp ../../energyplugin/energysettings.cpp ../../energyplugin/evcharger.cpp ../../energyplugin/nymeaenergyjsonhandler.cpp ../../energyplugin/rootmeter.cpp ../../energyplugin/schedulermanager.cpp ../../energyplugin/schedulersettings.cpp ../../energyplugin/smartchargingmanager.cpp ../../energyplugin/spotmarket/spotmarketdataprovider.cpp ../../energyplugin/spotmarket/spotmarketdataproviderawattar.cpp ../../energyplugin/spotmarket/spotmarketmanager.cpp ../../energyplugin/schedulingstrategies/rulebasedstrategy.cpp ../../energyplugin/schedulingstrategies/aistrategy.cpp ../../energyplugin/schedulingstrategies/manualstrategy.cpp ../../energyplugin/types/chargingaction.cpp ../../energyplugin/types/charginginfo.cpp ../../energyplugin/types/chargingprocessinfo.cpp ../../energyplugin/types/chargingschedule.cpp ../../energyplugin/types/energytimeslot.cpp ../../energyplugin/types/flexibleload.cpp ../../energyplugin/types/manualslotconfig.cpp ../../energyplugin/types/schedulerconfig.cpp ../../energyplugin/types/scoreentry.cpp ../../energyplugin/types/smartchargingstate.cpp ../../energyplugin/types/timeframe.cpp ../../energyplugin/energypluginnymea.cpp $(DISTDIR)/ $(COPY_FILE) --parents /home/etm/Projects/etm-nymea/nymea-energy-plugin-nymea/energyplugin/translations/nymea-energy-plugin-nymea-de.ts /home/etm/Projects/etm-nymea/nymea-energy-plugin-nymea/energyplugin/translations/nymea-energy-plugin-nymea-en_US.ts $(DISTDIR)/ @@ -647,9 +657,9 @@ compiler_moc_predefs_clean: moc_predefs.h: /usr/lib/x86_64-linux-gnu/qt6/mkspecs/features/data/dummy.cpp g++ -pipe -std=c++17 -O2 -std=gnu++1z -Wall -Wextra -dM -E -o moc_predefs.h /usr/lib/x86_64-linux-gnu/qt6/mkspecs/features/data/dummy.cpp -compiler_moc_header_make_all: moc_energymanagerconfiguration.cpp moc_evcharger.cpp moc_nymeaenergyjsonhandler.cpp moc_rootmeter.cpp moc_schedulermanager.cpp moc_schedulersettings.cpp moc_smartchargingmanager.cpp moc_spotmarketdataprovider.cpp moc_spotmarketdataproviderawattar.cpp moc_spotmarketmanager.cpp moc_ischedulingstrategy.cpp moc_rulebasedstrategy.cpp moc_aistrategy.cpp moc_chargingaction.cpp moc_charginginfo.cpp moc_chargingschedule.cpp moc_scoreentry.cpp moc_smartchargingstate.cpp moc_energypluginnymea.cpp +compiler_moc_header_make_all: moc_energymanagerconfiguration.cpp moc_evcharger.cpp moc_nymeaenergyjsonhandler.cpp moc_rootmeter.cpp moc_schedulermanager.cpp moc_schedulersettings.cpp moc_smartchargingmanager.cpp moc_spotmarketdataprovider.cpp moc_spotmarketdataproviderawattar.cpp moc_spotmarketmanager.cpp moc_ischedulingstrategy.cpp moc_rulebasedstrategy.cpp moc_aistrategy.cpp moc_manualstrategy.cpp moc_chargingaction.cpp moc_charginginfo.cpp moc_chargingschedule.cpp moc_scoreentry.cpp moc_smartchargingstate.cpp moc_energypluginnymea.cpp compiler_moc_header_clean: - -$(DEL_FILE) moc_energymanagerconfiguration.cpp moc_evcharger.cpp moc_nymeaenergyjsonhandler.cpp moc_rootmeter.cpp moc_schedulermanager.cpp moc_schedulersettings.cpp moc_smartchargingmanager.cpp moc_spotmarketdataprovider.cpp moc_spotmarketdataproviderawattar.cpp moc_spotmarketmanager.cpp moc_ischedulingstrategy.cpp moc_rulebasedstrategy.cpp moc_aistrategy.cpp moc_chargingaction.cpp moc_charginginfo.cpp moc_chargingschedule.cpp moc_scoreentry.cpp moc_smartchargingstate.cpp moc_energypluginnymea.cpp + -$(DEL_FILE) moc_energymanagerconfiguration.cpp moc_evcharger.cpp moc_nymeaenergyjsonhandler.cpp moc_rootmeter.cpp moc_schedulermanager.cpp moc_schedulersettings.cpp moc_smartchargingmanager.cpp moc_spotmarketdataprovider.cpp moc_spotmarketdataproviderawattar.cpp moc_spotmarketmanager.cpp moc_ischedulingstrategy.cpp moc_rulebasedstrategy.cpp moc_aistrategy.cpp moc_manualstrategy.cpp moc_chargingaction.cpp moc_charginginfo.cpp moc_chargingschedule.cpp moc_scoreentry.cpp moc_smartchargingstate.cpp moc_energypluginnymea.cpp moc_energymanagerconfiguration.cpp: ../../energyplugin/energymanagerconfiguration.h \ moc_predefs.h \ /usr/lib/qt6/libexec/moc @@ -674,6 +684,8 @@ moc_nymeaenergyjsonhandler.cpp: ../../energyplugin/nymeaenergyjsonhandler.h \ ../../energyplugin/types/scoreentry.h \ ../../energyplugin/types/timeframe.h \ ../../energyplugin/types/energytimeslot.h \ + ../../energyplugin/types/manualslotconfig.h \ + ../../energyplugin/types/flexibleload.h \ moc_predefs.h \ /usr/lib/qt6/libexec/moc /usr/lib/qt6/libexec/moc $(DEFINES) --include /home/etm/Projects/etm-nymea/nymea-energy-plugin-nymea/build-test/energyplugin/moc_predefs.h -I/usr/lib/x86_64-linux-gnu/qt6/mkspecs/linux-g++ -I/home/etm/Projects/etm-nymea/nymea-energy-plugin-nymea/energyplugin -I/usr/include/nymea -I/usr/include/nymea-energy -I/usr/include/x86_64-linux-gnu/qt6 -I/usr/include/x86_64-linux-gnu/qt6/QtGui -I/usr/include/x86_64-linux-gnu/qt6/QtNetwork -I/usr/include/x86_64-linux-gnu/qt6/QtCore -I. -I/usr/include/c++/14 -I/usr/include/x86_64-linux-gnu/c++/14 -I/usr/include/c++/14/backward -I/usr/lib/gcc/x86_64-linux-gnu/14/include -I/usr/local/include -I/usr/include/x86_64-linux-gnu -I/usr/include ../../energyplugin/nymeaenergyjsonhandler.h -o moc_nymeaenergyjsonhandler.cpp @@ -697,6 +709,7 @@ moc_schedulermanager.cpp: ../../energyplugin/schedulermanager.h \ ../../energyplugin/types/energytimeslot.h \ ../../energyplugin/types/flexibleload.h \ ../../energyplugin/types/schedulerconfig.h \ + ../../energyplugin/types/manualslotconfig.h \ ../../energyplugin/schedulingstrategies/ischedulingstrategy.h \ moc_predefs.h \ /usr/lib/qt6/libexec/moc @@ -706,6 +719,7 @@ moc_schedulersettings.cpp: ../../energyplugin/schedulersettings.h \ ../../energyplugin/types/schedulerconfig.h \ ../../energyplugin/types/flexibleload.h \ ../../energyplugin/types/energytimeslot.h \ + ../../energyplugin/types/manualslotconfig.h \ moc_predefs.h \ /usr/lib/qt6/libexec/moc /usr/lib/qt6/libexec/moc $(DEFINES) --include /home/etm/Projects/etm-nymea/nymea-energy-plugin-nymea/build-test/energyplugin/moc_predefs.h -I/usr/lib/x86_64-linux-gnu/qt6/mkspecs/linux-g++ -I/home/etm/Projects/etm-nymea/nymea-energy-plugin-nymea/energyplugin -I/usr/include/nymea -I/usr/include/nymea-energy -I/usr/include/x86_64-linux-gnu/qt6 -I/usr/include/x86_64-linux-gnu/qt6/QtGui -I/usr/include/x86_64-linux-gnu/qt6/QtNetwork -I/usr/include/x86_64-linux-gnu/qt6/QtCore -I. -I/usr/include/c++/14 -I/usr/include/x86_64-linux-gnu/c++/14 -I/usr/include/c++/14/backward -I/usr/lib/gcc/x86_64-linux-gnu/14/include -I/usr/local/include -I/usr/include/x86_64-linux-gnu -I/usr/include ../../energyplugin/schedulersettings.h -o moc_schedulersettings.cpp @@ -775,6 +789,16 @@ moc_aistrategy.cpp: ../../energyplugin/schedulingstrategies/aistrategy.h \ /usr/lib/qt6/libexec/moc /usr/lib/qt6/libexec/moc $(DEFINES) --include /home/etm/Projects/etm-nymea/nymea-energy-plugin-nymea/build-test/energyplugin/moc_predefs.h -I/usr/lib/x86_64-linux-gnu/qt6/mkspecs/linux-g++ -I/home/etm/Projects/etm-nymea/nymea-energy-plugin-nymea/energyplugin -I/usr/include/nymea -I/usr/include/nymea-energy -I/usr/include/x86_64-linux-gnu/qt6 -I/usr/include/x86_64-linux-gnu/qt6/QtGui -I/usr/include/x86_64-linux-gnu/qt6/QtNetwork -I/usr/include/x86_64-linux-gnu/qt6/QtCore -I. -I/usr/include/c++/14 -I/usr/include/x86_64-linux-gnu/c++/14 -I/usr/include/c++/14/backward -I/usr/lib/gcc/x86_64-linux-gnu/14/include -I/usr/local/include -I/usr/include/x86_64-linux-gnu -I/usr/include ../../energyplugin/schedulingstrategies/aistrategy.h -o moc_aistrategy.cpp +moc_manualstrategy.cpp: ../../energyplugin/schedulingstrategies/manualstrategy.h \ + ../../energyplugin/schedulingstrategies/ischedulingstrategy.h \ + ../../energyplugin/types/energytimeslot.h \ + ../../energyplugin/types/flexibleload.h \ + ../../energyplugin/types/schedulerconfig.h \ + ../../energyplugin/types/manualslotconfig.h \ + moc_predefs.h \ + /usr/lib/qt6/libexec/moc + /usr/lib/qt6/libexec/moc $(DEFINES) --include /home/etm/Projects/etm-nymea/nymea-energy-plugin-nymea/build-test/energyplugin/moc_predefs.h -I/usr/lib/x86_64-linux-gnu/qt6/mkspecs/linux-g++ -I/home/etm/Projects/etm-nymea/nymea-energy-plugin-nymea/energyplugin -I/usr/include/nymea -I/usr/include/nymea-energy -I/usr/include/x86_64-linux-gnu/qt6 -I/usr/include/x86_64-linux-gnu/qt6/QtGui -I/usr/include/x86_64-linux-gnu/qt6/QtNetwork -I/usr/include/x86_64-linux-gnu/qt6/QtCore -I. -I/usr/include/c++/14 -I/usr/include/x86_64-linux-gnu/c++/14 -I/usr/include/c++/14/backward -I/usr/lib/gcc/x86_64-linux-gnu/14/include -I/usr/local/include -I/usr/include/x86_64-linux-gnu -I/usr/include ../../energyplugin/schedulingstrategies/manualstrategy.h -o moc_manualstrategy.cpp + moc_chargingaction.cpp: ../../energyplugin/types/chargingaction.h \ moc_predefs.h \ /usr/lib/qt6/libexec/moc @@ -845,6 +869,8 @@ nymeaenergyjsonhandler.o: ../../energyplugin/nymeaenergyjsonhandler.cpp ../../en ../../energyplugin/types/scoreentry.h \ ../../energyplugin/types/timeframe.h \ ../../energyplugin/types/energytimeslot.h \ + ../../energyplugin/types/manualslotconfig.h \ + ../../energyplugin/types/flexibleload.h \ ../../energyplugin/types/charginginfo.h \ ../../energyplugin/smartchargingmanager.h \ ../../energyplugin/energymanagerconfiguration.h \ @@ -854,9 +880,9 @@ nymeaenergyjsonhandler.o: ../../energyplugin/nymeaenergyjsonhandler.cpp ../../en ../../energyplugin/spotmarket/spotmarketmanager.h \ ../../energyplugin/spotmarket/spotmarketdataprovider.h \ ../../energyplugin/schedulermanager.h \ - ../../energyplugin/types/flexibleload.h \ ../../energyplugin/types/schedulerconfig.h \ - ../../energyplugin/schedulingstrategies/ischedulingstrategy.h + ../../energyplugin/schedulingstrategies/ischedulingstrategy.h \ + ../../energyplugin/schedulingstrategies/manualstrategy.h $(CXX) -c $(CXXFLAGS) $(INCPATH) -o nymeaenergyjsonhandler.o ../../energyplugin/nymeaenergyjsonhandler.cpp rootmeter.o: ../../energyplugin/rootmeter.cpp ../../energyplugin/rootmeter.h \ @@ -876,9 +902,12 @@ schedulermanager.o: ../../energyplugin/schedulermanager.cpp ../../energyplugin/s ../../energyplugin/types/energytimeslot.h \ ../../energyplugin/types/flexibleload.h \ ../../energyplugin/types/schedulerconfig.h \ + ../../energyplugin/types/manualslotconfig.h \ ../../energyplugin/schedulingstrategies/ischedulingstrategy.h \ ../../energyplugin/schedulingstrategies/rulebasedstrategy.h \ ../../energyplugin/schedulingstrategies/aistrategy.h \ + ../../energyplugin/schedulingstrategies/manualstrategy.h \ + ../../energyplugin/schedulersettings.h \ ../../energyplugin/spotmarket/spotmarketmanager.h \ ../../energyplugin/types/chargingschedule.h \ ../../energyplugin/types/timeframe.h \ @@ -890,7 +919,8 @@ schedulermanager.o: ../../energyplugin/schedulermanager.cpp ../../energyplugin/s schedulersettings.o: ../../energyplugin/schedulersettings.cpp ../../energyplugin/schedulersettings.h \ ../../energyplugin/types/schedulerconfig.h \ ../../energyplugin/types/flexibleload.h \ - ../../energyplugin/types/energytimeslot.h + ../../energyplugin/types/energytimeslot.h \ + ../../energyplugin/types/manualslotconfig.h $(CXX) -c $(CXXFLAGS) $(INCPATH) -o schedulersettings.o ../../energyplugin/schedulersettings.cpp smartchargingmanager.o: ../../energyplugin/smartchargingmanager.cpp ../../energyplugin/smartchargingmanager.h \ @@ -946,6 +976,14 @@ aistrategy.o: ../../energyplugin/schedulingstrategies/aistrategy.cpp ../../energ ../../energyplugin/types/schedulerconfig.h $(CXX) -c $(CXXFLAGS) $(INCPATH) -o aistrategy.o ../../energyplugin/schedulingstrategies/aistrategy.cpp +manualstrategy.o: ../../energyplugin/schedulingstrategies/manualstrategy.cpp ../../energyplugin/schedulingstrategies/manualstrategy.h \ + ../../energyplugin/schedulingstrategies/ischedulingstrategy.h \ + ../../energyplugin/types/energytimeslot.h \ + ../../energyplugin/types/flexibleload.h \ + ../../energyplugin/types/schedulerconfig.h \ + ../../energyplugin/types/manualslotconfig.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o manualstrategy.o ../../energyplugin/schedulingstrategies/manualstrategy.cpp + chargingaction.o: ../../energyplugin/types/chargingaction.cpp ../../energyplugin/types/chargingaction.h $(CXX) -c $(CXXFLAGS) $(INCPATH) -o chargingaction.o ../../energyplugin/types/chargingaction.cpp @@ -969,6 +1007,10 @@ energytimeslot.o: ../../energyplugin/types/energytimeslot.cpp ../../energyplugin flexibleload.o: ../../energyplugin/types/flexibleload.cpp ../../energyplugin/types/flexibleload.h $(CXX) -c $(CXXFLAGS) $(INCPATH) -o flexibleload.o ../../energyplugin/types/flexibleload.cpp +manualslotconfig.o: ../../energyplugin/types/manualslotconfig.cpp ../../energyplugin/types/manualslotconfig.h \ + ../../energyplugin/types/flexibleload.h + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o manualslotconfig.o ../../energyplugin/types/manualslotconfig.cpp + schedulerconfig.o: ../../energyplugin/types/schedulerconfig.cpp ../../energyplugin/types/schedulerconfig.h $(CXX) -c $(CXXFLAGS) $(INCPATH) -o schedulerconfig.o ../../energyplugin/types/schedulerconfig.cpp @@ -997,8 +1039,10 @@ energypluginnymea.o: ../../energyplugin/energypluginnymea.cpp ../../energyplugin ../../energyplugin/types/energytimeslot.h \ ../../energyplugin/types/flexibleload.h \ ../../energyplugin/types/schedulerconfig.h \ + ../../energyplugin/types/manualslotconfig.h \ ../../energyplugin/schedulingstrategies/ischedulingstrategy.h \ ../../energyplugin/nymeaenergyjsonhandler.h \ + ../../energyplugin/schedulingstrategies/manualstrategy.h \ ../../energyplugin/plugininfo.h $(CXX) -c $(CXXFLAGS) $(INCPATH) -o energypluginnymea.o ../../energyplugin/energypluginnymea.cpp @@ -1041,6 +1085,9 @@ moc_rulebasedstrategy.o: moc_rulebasedstrategy.cpp moc_aistrategy.o: moc_aistrategy.cpp $(CXX) -c $(CXXFLAGS) $(INCPATH) -o moc_aistrategy.o moc_aistrategy.cpp +moc_manualstrategy.o: moc_manualstrategy.cpp + $(CXX) -c $(CXXFLAGS) $(INCPATH) -o moc_manualstrategy.o moc_manualstrategy.cpp + moc_chargingaction.o: moc_chargingaction.cpp $(CXX) -c $(CXXFLAGS) $(INCPATH) -o moc_chargingaction.o moc_chargingaction.cpp diff --git a/build-test/energyplugin/energypluginnymea.o b/build-test/energyplugin/energypluginnymea.o index a8716fd..bfdab63 100644 Binary files a/build-test/energyplugin/energypluginnymea.o and b/build-test/energyplugin/energypluginnymea.o differ diff --git a/build-test/energyplugin/libnymea_energypluginnymea.so b/build-test/energyplugin/libnymea_energypluginnymea.so index 5535007..fa2a4a9 100755 Binary files a/build-test/energyplugin/libnymea_energypluginnymea.so and b/build-test/energyplugin/libnymea_energypluginnymea.so differ diff --git a/build-test/energyplugin/manualslotconfig.o b/build-test/energyplugin/manualslotconfig.o new file mode 100644 index 0000000..cb3a1fe Binary files /dev/null and b/build-test/energyplugin/manualslotconfig.o differ diff --git a/build-test/energyplugin/manualstrategy.o b/build-test/energyplugin/manualstrategy.o new file mode 100644 index 0000000..6e34447 Binary files /dev/null and b/build-test/energyplugin/manualstrategy.o differ diff --git a/build-test/energyplugin/moc_manualstrategy.cpp b/build-test/energyplugin/moc_manualstrategy.cpp new file mode 100644 index 0000000..2fb5156 --- /dev/null +++ b/build-test/energyplugin/moc_manualstrategy.cpp @@ -0,0 +1,102 @@ +/**************************************************************************** +** Meta object code from reading C++ file 'manualstrategy.h' +** +** Created by: The Qt Meta Object Compiler version 68 (Qt 6.8.2) +** +** WARNING! All changes made in this file will be lost! +*****************************************************************************/ + +#include "../../energyplugin/schedulingstrategies/manualstrategy.h" +#include + +#include + +#include + + +#include +#if !defined(Q_MOC_OUTPUT_REVISION) +#error "The header file 'manualstrategy.h' doesn't include ." +#elif Q_MOC_OUTPUT_REVISION != 68 +#error "This file was generated using the moc from 6.8.2. It" +#error "cannot be used with the include files from this version of Qt." +#error "(The moc has changed too much.)" +#endif + +#ifndef Q_CONSTINIT +#define Q_CONSTINIT +#endif + +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED +QT_WARNING_DISABLE_GCC("-Wuseless-cast") +namespace { +struct qt_meta_tag_ZN14ManualStrategyE_t {}; +} // unnamed namespace + + +#ifdef QT_MOC_HAS_STRINGDATA +static constexpr auto qt_meta_stringdata_ZN14ManualStrategyE = QtMocHelpers::stringData( + "ManualStrategy" +); +#else // !QT_MOC_HAS_STRINGDATA +#error "qtmochelpers.h not found or too old." +#endif // !QT_MOC_HAS_STRINGDATA + +Q_CONSTINIT static const uint qt_meta_data_ZN14ManualStrategyE[] = { + + // content: + 12, // revision + 0, // classname + 0, 0, // classinfo + 0, 0, // methods + 0, 0, // properties + 0, 0, // enums/sets + 0, 0, // constructors + 0, // flags + 0, // signalCount + + 0 // eod +}; + +Q_CONSTINIT const QMetaObject ManualStrategy::staticMetaObject = { { + QMetaObject::SuperData::link(), + qt_meta_stringdata_ZN14ManualStrategyE.offsetsAndSizes, + qt_meta_data_ZN14ManualStrategyE, + qt_static_metacall, + nullptr, + qt_incomplete_metaTypeArray + >, + nullptr +} }; + +void ManualStrategy::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) +{ + auto *_t = static_cast(_o); + (void)_t; + (void)_c; + (void)_id; + (void)_a; +} + +const QMetaObject *ManualStrategy::metaObject() const +{ + return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject; +} + +void *ManualStrategy::qt_metacast(const char *_clname) +{ + if (!_clname) return nullptr; + if (!strcmp(_clname, qt_meta_stringdata_ZN14ManualStrategyE.stringdata0)) + return static_cast(this); + return ISchedulingStrategy::qt_metacast(_clname); +} + +int ManualStrategy::qt_metacall(QMetaObject::Call _c, int _id, void **_a) +{ + _id = ISchedulingStrategy::qt_metacall(_c, _id, _a); + return _id; +} +QT_WARNING_POP diff --git a/build-test/energyplugin/moc_manualstrategy.o b/build-test/energyplugin/moc_manualstrategy.o new file mode 100644 index 0000000..b0f6e32 Binary files /dev/null and b/build-test/energyplugin/moc_manualstrategy.o differ diff --git a/build-test/energyplugin/moc_nymeaenergyjsonhandler.cpp b/build-test/energyplugin/moc_nymeaenergyjsonhandler.cpp index d873d8c..63bf346 100644 --- a/build-test/energyplugin/moc_nymeaenergyjsonhandler.cpp +++ b/build-test/energyplugin/moc_nymeaenergyjsonhandler.cpp @@ -54,6 +54,7 @@ static constexpr auto qt_meta_stringdata_ZN22NymeaEnergyJsonHandlerE = QtMocHelp "TimelineUpdated", "SlotActivated", "OverrideConflict", + "ManualSlotActivated", "GetPhasePowerLimit", "JsonReply*", "SetPhasePowerLimit", @@ -78,7 +79,11 @@ static constexpr auto qt_meta_stringdata_ZN22NymeaEnergyJsonHandlerE = QtMocHelp "SetSchedulerStrategy", "SetSchedulerConfig", "SetLoadConfig", - "OverrideSlot" + "OverrideSlot", + "GetManualSlots", + "SetManualSlot", + "RemoveManualSlot", + "ClearManualSlots" ); #else // !QT_MOC_HAS_STRINGDATA #error "qtmochelpers.h not found or too old." @@ -90,51 +95,56 @@ Q_CONSTINIT static const uint qt_meta_data_ZN22NymeaEnergyJsonHandlerE[] = { 12, // revision 0, // classname 0, 0, // classinfo - 35, 14, // methods + 40, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags - 13, // signalCount + 14, // signalCount // signals: name, argc, parameters, tag, flags, initial metatype offsets - 1, 1, 224, 2, 0x06, 1 /* Public */, - 5, 1, 227, 2, 0x06, 3 /* Public */, - 6, 1, 230, 2, 0x06, 5 /* Public */, - 7, 1, 233, 2, 0x06, 7 /* Public */, - 8, 1, 236, 2, 0x06, 9 /* Public */, - 9, 1, 239, 2, 0x06, 11 /* Public */, - 10, 1, 242, 2, 0x06, 13 /* Public */, - 11, 1, 245, 2, 0x06, 15 /* Public */, - 12, 1, 248, 2, 0x06, 17 /* Public */, - 13, 1, 251, 2, 0x06, 19 /* Public */, - 14, 1, 254, 2, 0x06, 21 /* Public */, - 15, 1, 257, 2, 0x06, 23 /* Public */, - 16, 1, 260, 2, 0x06, 25 /* Public */, + 1, 1, 254, 2, 0x06, 1 /* Public */, + 5, 1, 257, 2, 0x06, 3 /* Public */, + 6, 1, 260, 2, 0x06, 5 /* Public */, + 7, 1, 263, 2, 0x06, 7 /* Public */, + 8, 1, 266, 2, 0x06, 9 /* Public */, + 9, 1, 269, 2, 0x06, 11 /* Public */, + 10, 1, 272, 2, 0x06, 13 /* Public */, + 11, 1, 275, 2, 0x06, 15 /* Public */, + 12, 1, 278, 2, 0x06, 17 /* Public */, + 13, 1, 281, 2, 0x06, 19 /* Public */, + 14, 1, 284, 2, 0x06, 21 /* Public */, + 15, 1, 287, 2, 0x06, 23 /* Public */, + 16, 1, 290, 2, 0x06, 25 /* Public */, + 17, 1, 293, 2, 0x06, 27 /* Public */, // methods: name, argc, parameters, tag, flags, initial metatype offsets - 17, 1, 263, 2, 0x02, 27 /* Public */, - 19, 1, 266, 2, 0x02, 29 /* Public */, - 20, 1, 269, 2, 0x02, 31 /* Public */, - 21, 1, 272, 2, 0x02, 33 /* Public */, - 22, 1, 275, 2, 0x02, 35 /* Public */, - 23, 1, 278, 2, 0x02, 37 /* Public */, - 24, 1, 281, 2, 0x02, 39 /* Public */, - 25, 2, 284, 2, 0x02, 41 /* Public */, - 28, 1, 289, 2, 0x02, 44 /* Public */, - 29, 1, 292, 2, 0x02, 46 /* Public */, - 30, 1, 295, 2, 0x02, 48 /* Public */, - 31, 1, 298, 2, 0x02, 50 /* Public */, - 32, 1, 301, 2, 0x02, 52 /* Public */, - 33, 1, 304, 2, 0x02, 54 /* Public */, - 34, 1, 307, 2, 0x02, 56 /* Public */, - 35, 1, 310, 2, 0x02, 58 /* Public */, - 36, 1, 313, 2, 0x02, 60 /* Public */, - 37, 1, 316, 2, 0x02, 62 /* Public */, - 38, 1, 319, 2, 0x02, 64 /* Public */, - 39, 1, 322, 2, 0x02, 66 /* Public */, - 40, 1, 325, 2, 0x02, 68 /* Public */, - 41, 1, 328, 2, 0x02, 70 /* Public */, + 18, 1, 296, 2, 0x02, 29 /* Public */, + 20, 1, 299, 2, 0x02, 31 /* Public */, + 21, 1, 302, 2, 0x02, 33 /* Public */, + 22, 1, 305, 2, 0x02, 35 /* Public */, + 23, 1, 308, 2, 0x02, 37 /* Public */, + 24, 1, 311, 2, 0x02, 39 /* Public */, + 25, 1, 314, 2, 0x02, 41 /* Public */, + 26, 2, 317, 2, 0x02, 43 /* Public */, + 29, 1, 322, 2, 0x02, 46 /* Public */, + 30, 1, 325, 2, 0x02, 48 /* Public */, + 31, 1, 328, 2, 0x02, 50 /* Public */, + 32, 1, 331, 2, 0x02, 52 /* Public */, + 33, 1, 334, 2, 0x02, 54 /* Public */, + 34, 1, 337, 2, 0x02, 56 /* Public */, + 35, 1, 340, 2, 0x02, 58 /* Public */, + 36, 1, 343, 2, 0x02, 60 /* Public */, + 37, 1, 346, 2, 0x02, 62 /* Public */, + 38, 1, 349, 2, 0x02, 64 /* Public */, + 39, 1, 352, 2, 0x02, 66 /* Public */, + 40, 1, 355, 2, 0x02, 68 /* Public */, + 41, 1, 358, 2, 0x02, 70 /* Public */, + 42, 1, 361, 2, 0x02, 72 /* Public */, + 43, 1, 364, 2, 0x02, 74 /* Public */, + 44, 1, 367, 2, 0x02, 76 /* Public */, + 45, 1, 370, 2, 0x02, 78 /* Public */, + 46, 1, 373, 2, 0x02, 80 /* Public */, // signals: parameters QMetaType::Void, 0x80000000 | 3, 4, @@ -150,30 +160,35 @@ Q_CONSTINIT static const uint qt_meta_data_ZN22NymeaEnergyJsonHandlerE[] = { QMetaType::Void, 0x80000000 | 3, 4, QMetaType::Void, 0x80000000 | 3, 4, QMetaType::Void, 0x80000000 | 3, 4, + QMetaType::Void, 0x80000000 | 3, 4, // methods: parameters - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 0x80000000 | 26, 4, 27, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, - 0x80000000 | 18, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 0x80000000 | 27, 4, 28, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, + 0x80000000 | 19, 0x80000000 | 3, 4, 0 // eod }; @@ -226,6 +241,9 @@ Q_CONSTINIT const QMetaObject NymeaEnergyJsonHandler::staticMetaObject = { { // method 'OverrideConflict' QtPrivate::TypeAndForceComplete, QtPrivate::TypeAndForceComplete, + // method 'ManualSlotActivated' + QtPrivate::TypeAndForceComplete, + QtPrivate::TypeAndForceComplete, // method 'GetPhasePowerLimit' QtPrivate::TypeAndForceComplete, QtPrivate::TypeAndForceComplete, @@ -292,6 +310,18 @@ Q_CONSTINIT const QMetaObject NymeaEnergyJsonHandler::staticMetaObject = { { QtPrivate::TypeAndForceComplete, // method 'OverrideSlot' QtPrivate::TypeAndForceComplete, + QtPrivate::TypeAndForceComplete, + // method 'GetManualSlots' + QtPrivate::TypeAndForceComplete, + QtPrivate::TypeAndForceComplete, + // method 'SetManualSlot' + QtPrivate::TypeAndForceComplete, + QtPrivate::TypeAndForceComplete, + // method 'RemoveManualSlot' + QtPrivate::TypeAndForceComplete, + QtPrivate::TypeAndForceComplete, + // method 'ClearManualSlots' + QtPrivate::TypeAndForceComplete, QtPrivate::TypeAndForceComplete >, nullptr @@ -315,49 +345,58 @@ void NymeaEnergyJsonHandler::qt_static_metacall(QObject *_o, QMetaObject::Call _ case 10: _t->TimelineUpdated((*reinterpret_cast< std::add_pointer_t>(_a[1]))); break; case 11: _t->SlotActivated((*reinterpret_cast< std::add_pointer_t>(_a[1]))); break; case 12: _t->OverrideConflict((*reinterpret_cast< std::add_pointer_t>(_a[1]))); break; - case 13: { JsonReply* _r = _t->GetPhasePowerLimit((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 13: _t->ManualSlotActivated((*reinterpret_cast< std::add_pointer_t>(_a[1]))); break; + case 14: { JsonReply* _r = _t->GetPhasePowerLimit((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 14: { JsonReply* _r = _t->SetPhasePowerLimit((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 15: { JsonReply* _r = _t->SetPhasePowerLimit((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 15: { JsonReply* _r = _t->GetAcquisitionTolerance((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 16: { JsonReply* _r = _t->GetAcquisitionTolerance((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 16: { JsonReply* _r = _t->SetAcquisitionTolerance((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 17: { JsonReply* _r = _t->SetAcquisitionTolerance((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 17: { JsonReply* _r = _t->GetBatteryLevelConsideration((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 18: { JsonReply* _r = _t->GetBatteryLevelConsideration((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 18: { JsonReply* _r = _t->SetBatteryLevelConsideration((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 19: { JsonReply* _r = _t->SetBatteryLevelConsideration((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 19: { JsonReply* _r = _t->GetChargingInfos((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 20: { JsonReply* _r = _t->GetChargingInfos((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 20: { JsonReply* _r = _t->SetChargingInfo((*reinterpret_cast< std::add_pointer_t>(_a[1])),(*reinterpret_cast< std::add_pointer_t>(_a[2]))); + case 21: { JsonReply* _r = _t->SetChargingInfo((*reinterpret_cast< std::add_pointer_t>(_a[1])),(*reinterpret_cast< std::add_pointer_t>(_a[2]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 21: { JsonReply* _r = _t->GetLockOnUnplug((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 22: { JsonReply* _r = _t->GetLockOnUnplug((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 22: { JsonReply* _r = _t->SetLockOnUnplug((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 23: { JsonReply* _r = _t->SetLockOnUnplug((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 23: { JsonReply* _r = _t->GetAvailableSpotMarketProviders((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 24: { JsonReply* _r = _t->GetAvailableSpotMarketProviders((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 24: { JsonReply* _r = _t->GetSpotMarketConfiguration((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 25: { JsonReply* _r = _t->GetSpotMarketConfiguration((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 25: { JsonReply* _r = _t->SetSpotMarketConfiguration((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 26: { JsonReply* _r = _t->SetSpotMarketConfiguration((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 26: { JsonReply* _r = _t->GetSpotMarketScoreEntries((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 27: { JsonReply* _r = _t->GetSpotMarketScoreEntries((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 27: { JsonReply* _r = _t->GetChargingSchedules((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 28: { JsonReply* _r = _t->GetChargingSchedules((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 28: { JsonReply* _r = _t->GetEnergyTimeline((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 29: { JsonReply* _r = _t->GetEnergyTimeline((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 29: { JsonReply* _r = _t->GetFlexibleLoads((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 30: { JsonReply* _r = _t->GetFlexibleLoads((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 30: { JsonReply* _r = _t->GetSchedulerStatus((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 31: { JsonReply* _r = _t->GetSchedulerStatus((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 31: { JsonReply* _r = _t->SetSchedulerStrategy((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 32: { JsonReply* _r = _t->SetSchedulerStrategy((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 32: { JsonReply* _r = _t->SetSchedulerConfig((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 33: { JsonReply* _r = _t->SetSchedulerConfig((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 33: { JsonReply* _r = _t->SetLoadConfig((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 34: { JsonReply* _r = _t->SetLoadConfig((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; - case 34: { JsonReply* _r = _t->OverrideSlot((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + case 35: { JsonReply* _r = _t->OverrideSlot((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; + case 36: { JsonReply* _r = _t->GetManualSlots((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; + case 37: { JsonReply* _r = _t->SetManualSlot((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; + case 38: { JsonReply* _r = _t->RemoveManualSlot((*reinterpret_cast< std::add_pointer_t>(_a[1]))); + if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; + case 39: { JsonReply* _r = _t->ClearManualSlots((*reinterpret_cast< std::add_pointer_t>(_a[1]))); if (_a[0]) *reinterpret_cast< JsonReply**>(_a[0]) = std::move(_r); } break; default: ; } @@ -455,6 +494,13 @@ void NymeaEnergyJsonHandler::qt_static_metacall(QObject *_o, QMetaObject::Call _ return; } } + { + using _q_method_type = void (NymeaEnergyJsonHandler::*)(const QVariantMap & ); + if (_q_method_type _q_method = &NymeaEnergyJsonHandler::ManualSlotActivated; *reinterpret_cast<_q_method_type *>(_a[1]) == _q_method) { + *result = 13; + return; + } + } } } @@ -477,14 +523,14 @@ int NymeaEnergyJsonHandler::qt_metacall(QMetaObject::Call _c, int _id, void **_a if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { - if (_id < 35) + if (_id < 40) qt_static_metacall(this, _c, _id, _a); - _id -= 35; + _id -= 40; } if (_c == QMetaObject::RegisterMethodArgumentMetaType) { - if (_id < 35) + if (_id < 40) *reinterpret_cast(_a[0]) = QMetaType(); - _id -= 35; + _id -= 40; } return _id; } @@ -579,4 +625,11 @@ void NymeaEnergyJsonHandler::OverrideConflict(const QVariantMap & _t1) void *_a[] = { nullptr, const_cast(reinterpret_cast(std::addressof(_t1))) }; QMetaObject::activate(this, &staticMetaObject, 12, _a); } + +// SIGNAL 13 +void NymeaEnergyJsonHandler::ManualSlotActivated(const QVariantMap & _t1) +{ + void *_a[] = { nullptr, const_cast(reinterpret_cast(std::addressof(_t1))) }; + QMetaObject::activate(this, &staticMetaObject, 13, _a); +} QT_WARNING_POP diff --git a/build-test/energyplugin/moc_nymeaenergyjsonhandler.o b/build-test/energyplugin/moc_nymeaenergyjsonhandler.o index 41dec89..dd1d5f7 100644 Binary files a/build-test/energyplugin/moc_nymeaenergyjsonhandler.o and b/build-test/energyplugin/moc_nymeaenergyjsonhandler.o differ diff --git a/build-test/energyplugin/moc_schedulermanager.cpp b/build-test/energyplugin/moc_schedulermanager.cpp index 2c98d42..0be9643 100644 --- a/build-test/energyplugin/moc_schedulermanager.cpp +++ b/build-test/energyplugin/moc_schedulermanager.cpp @@ -56,6 +56,7 @@ static constexpr auto qt_meta_stringdata_ZN16SchedulerManagerE = QtMocHelpers::s "configChanged", "SchedulerConfig", "config", + "manualSlotsChanged", "onRecomputeTimer", "onSlotExecutionTimer" ); @@ -69,24 +70,25 @@ Q_CONSTINIT static const uint qt_meta_data_ZN16SchedulerManagerE[] = { 12, // revision 0, // classname 0, 0, // classinfo - 8, 14, // methods + 9, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags - 6, // signalCount + 7, // signalCount // signals: name, argc, parameters, tag, flags, initial metatype offsets - 1, 1, 62, 2, 0x06, 1 /* Public */, - 5, 2, 65, 2, 0x06, 3 /* Public */, - 9, 1, 70, 2, 0x06, 6 /* Public */, - 11, 1, 73, 2, 0x06, 8 /* Public */, - 14, 1, 76, 2, 0x06, 10 /* Public */, - 15, 1, 79, 2, 0x06, 12 /* Public */, + 1, 1, 68, 2, 0x06, 1 /* Public */, + 5, 2, 71, 2, 0x06, 3 /* Public */, + 9, 1, 76, 2, 0x06, 6 /* Public */, + 11, 1, 79, 2, 0x06, 8 /* Public */, + 14, 1, 82, 2, 0x06, 10 /* Public */, + 15, 1, 85, 2, 0x06, 12 /* Public */, + 18, 0, 88, 2, 0x06, 14 /* Public */, // slots: name, argc, parameters, tag, flags, initial metatype offsets - 18, 0, 82, 2, 0x08, 14 /* Private */, - 19, 0, 83, 2, 0x08, 15 /* Private */, + 19, 0, 89, 2, 0x08, 15 /* Private */, + 20, 0, 90, 2, 0x08, 16 /* Private */, // signals: parameters QMetaType::Void, 0x80000000 | 3, 4, @@ -95,6 +97,7 @@ Q_CONSTINIT static const uint qt_meta_data_ZN16SchedulerManagerE[] = { QMetaType::Void, 0x80000000 | 12, 13, QMetaType::Void, 0x80000000 | 12, 13, QMetaType::Void, 0x80000000 | 16, 17, + QMetaType::Void, // slots: parameters QMetaType::Void, @@ -131,6 +134,8 @@ Q_CONSTINIT const QMetaObject SchedulerManager::staticMetaObject = { { // method 'configChanged' QtPrivate::TypeAndForceComplete, QtPrivate::TypeAndForceComplete, + // method 'manualSlotsChanged' + QtPrivate::TypeAndForceComplete, // method 'onRecomputeTimer' QtPrivate::TypeAndForceComplete, // method 'onSlotExecutionTimer' @@ -150,8 +155,9 @@ void SchedulerManager::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int case 3: _t->loadRegistered((*reinterpret_cast< std::add_pointer_t>(_a[1]))); break; case 4: _t->loadUpdated((*reinterpret_cast< std::add_pointer_t>(_a[1]))); break; case 5: _t->configChanged((*reinterpret_cast< std::add_pointer_t>(_a[1]))); break; - case 6: _t->onRecomputeTimer(); break; - case 7: _t->onSlotExecutionTimer(); break; + case 6: _t->manualSlotsChanged(); break; + case 7: _t->onRecomputeTimer(); break; + case 8: _t->onSlotExecutionTimer(); break; default: ; } } @@ -232,6 +238,13 @@ void SchedulerManager::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int return; } } + { + using _q_method_type = void (SchedulerManager::*)(); + if (_q_method_type _q_method = &SchedulerManager::manualSlotsChanged; *reinterpret_cast<_q_method_type *>(_a[1]) == _q_method) { + *result = 6; + return; + } + } } } @@ -254,14 +267,14 @@ int SchedulerManager::qt_metacall(QMetaObject::Call _c, int _id, void **_a) if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { - if (_id < 8) + if (_id < 9) qt_static_metacall(this, _c, _id, _a); - _id -= 8; + _id -= 9; } if (_c == QMetaObject::RegisterMethodArgumentMetaType) { - if (_id < 8) + if (_id < 9) qt_static_metacall(this, _c, _id, _a); - _id -= 8; + _id -= 9; } return _id; } @@ -307,4 +320,10 @@ void SchedulerManager::configChanged(const SchedulerConfig & _t1) void *_a[] = { nullptr, const_cast(reinterpret_cast(std::addressof(_t1))) }; QMetaObject::activate(this, &staticMetaObject, 5, _a); } + +// SIGNAL 6 +void SchedulerManager::manualSlotsChanged() +{ + QMetaObject::activate(this, &staticMetaObject, 6, nullptr); +} QT_WARNING_POP diff --git a/build-test/energyplugin/moc_schedulermanager.o b/build-test/energyplugin/moc_schedulermanager.o index 1cccd22..74a165a 100644 Binary files a/build-test/energyplugin/moc_schedulermanager.o and b/build-test/energyplugin/moc_schedulermanager.o differ diff --git a/build-test/energyplugin/moc_schedulersettings.o b/build-test/energyplugin/moc_schedulersettings.o index c7daffc..b18e19a 100644 Binary files a/build-test/energyplugin/moc_schedulersettings.o and b/build-test/energyplugin/moc_schedulersettings.o differ diff --git a/build-test/energyplugin/nymeaenergyjsonhandler.o b/build-test/energyplugin/nymeaenergyjsonhandler.o index bdbb735..e322c3d 100644 Binary files a/build-test/energyplugin/nymeaenergyjsonhandler.o and b/build-test/energyplugin/nymeaenergyjsonhandler.o differ diff --git a/build-test/energyplugin/schedulermanager.o b/build-test/energyplugin/schedulermanager.o index 7a0fc3a..75fafda 100644 Binary files a/build-test/energyplugin/schedulermanager.o and b/build-test/energyplugin/schedulermanager.o differ diff --git a/build-test/energyplugin/schedulersettings.o b/build-test/energyplugin/schedulersettings.o index 9a3adfb..b167969 100644 Binary files a/build-test/energyplugin/schedulersettings.o and b/build-test/energyplugin/schedulersettings.o differ diff --git a/doc.md b/doc.md index c82a54b..4e20b68 100644 --- a/doc.md +++ b/doc.md @@ -1377,3 +1377,150 @@ Toute décision **doit** avoir un `decisionReason` non vide. La méthode --- *Section 10 ajoutée le 2026-02-23 — SchedulerManager Phase 1 (stubs prédiction)* + +--- + +## 11. ManualStrategy — Community Tier + +### 11.1 Vue d'ensemble + +`ManualStrategy` (`strategyId = "manual"`) est la stratégie de niveau Community. +Elle donne à l'utilisateur un **contrôle total** : chaque créneau horaire est piloté +par une `ManualSlotConfig` explicitement définie. Aucune optimisation automatique. + +Cas d'usage typique : utilisateur technique qui sait exactement quand et à quelle +puissance charger son VE, sans déléguer la décision à un algorithme. + +### 11.2 Comportement par cas + +| Situation | Résultat | decisionRules | +|---|---|---| +| Slot dans une config active | Allocations appliquées exactement | `["ManualSlot"]` | +| Slot sans config | Charges inflexibles/critiques uniquement | `["ManualDefault"]` ou `["CriticalHeating"]` | +| Config existante mais expirée | Charges critiques uniquement | `["ExpiredSlot"]` | +| Slot en override manuel | Préservé tel quel | `["ManualOverride"]` | + +**Invariant** : `decisionReason` n'est jamais vide (contrat `ISchedulingStrategy`). + +### 11.3 Type ManualSlotConfig + +```cpp +struct ManualSlotConfig { + QDateTime start; + QDateTime end; + QMap powerAllocations; // "ev"→2000W, "battery"→1000W, ... + QString label; // affiché dans l'UI, ex. "Recharge VE nuit" + bool repeating; // si true : récurrence hebdomadaire (même jour/heure) + QDateTime expiresAt; // optionnel — ignoré après cette date +}; +``` + +Pour les slots **répétables** (`repeating=true`) : la récurrence est calculée en +*minutes-de-semaine* (jour_semaine × 1440 + heure × 60 + minute), ce qui gère +correctement les slots overnight (ex. Lun 22:00 → Mar 06:00). + +### 11.4 JSON-RPC — NymeaEnergy v11 + +#### GetManualSlots + +```json +→ {} +← { "slots": [ { ManualSlotConfig }, ... ] } +``` + +#### SetManualSlot + +```json +→ { + "start": "2026-02-24T22:00:00.000Z", + "end": "2026-02-25T06:00:00.000Z", + "label": "Recharge VE nuit", + "repeating": false, + "expiresAt": "2026-03-01T00:00:00.000Z", + "allocations": { "ev": 2000, "battery": 1000, "heatpump": 0, "dhw": 0 } + } +← { "energyError": "EnergyErrorNoError" } +``` + +#### RemoveManualSlot + +```json +→ { "start": "2026-02-24T22:00:00.000Z" } +← { "energyError": "EnergyErrorNoError" } +``` + +#### ClearManualSlots + +```json +→ {} +← { "energyError": "EnergyErrorNoError" } +``` + +#### ManualSlotActivated (push notification) + +```json +{ + "slot": { /* ManualSlotConfig */ }, + "appliedAllocations": { "ev": 2000, "battery": 1000, "heatpump": 0, "dhw": 0, "feedin": 0 }, + "reason": "Créneau manuel 'Recharge VE nuit' activé" +} +``` + +### 11.5 Persistance + +Les `ManualSlotConfig` sont persistées dans : +``` +NymeaSettings::settingsPath() + "/scheduler.conf" [section: manualSlots] +``` + +- **Chargement** : au démarrage, dans `SchedulerManager::registerStrategy()` lorsque + `ManualStrategy` est enregistrée. Les slots expirés sont ignorés à la lecture. +- **Sauvegarde** : à chaque `SetManualSlot` / `RemoveManualSlot` / `ClearManualSlots`. + +### 11.6 Guide d'intégration — créneau EV hebdomadaire + +**Étape 1** — Activer ManualStrategy : +```json +{ "method": "NymeaEnergy.SetSchedulerStrategy", "params": { "strategyId": "manual" } } +``` + +**Étape 2** — Configurer un créneau VE chaque lundi nuit (22:00→06:00), 2 kW : +```json +{ + "method": "NymeaEnergy.SetManualSlot", + "params": { + "start": "2026-02-23T22:00:00.000Z", + "end": "2026-02-24T06:00:00.000Z", + "label": "Recharge hebdo VE", + "repeating": true, + "allocations": { "ev": 2000 } + } +} +``` + +**Étape 3** — S'abonner à la notification pour confirmation : +```json +{ "method": "JSONRPC.SetNotificationStatus", + "params": { "namespaces": ["NymeaEnergy"] } } +// → ManualSlotActivated émis à chaque lundi 22:00 +``` + +**Étape 4** — Retirer le créneau si besoin : +```json +{ "method": "NymeaEnergy.RemoveManualSlot", + "params": { "start": "2026-02-23T22:00:00.000Z" } } +``` + +### 11.7 Clés d'allocation (JSON) + +| Clé JSON | LoadSource interne | +|---|---| +| `"ev"` | `LoadSource::SmartCharging` | +| `"battery"` | `LoadSource::Battery` | +| `"dhw"` | `LoadSource::DHW` | +| `"heatpump"` | `LoadSource::HeatPump` | +| `"feedin"` | `LoadSource::FeedIn` | + +--- + +*Section 11 ajoutée le 2026-02-24 — ManualStrategy Community Tier* diff --git a/energyplugin/energyplugin.pri b/energyplugin/energyplugin.pri index e107d8e..215790c 100644 --- a/energyplugin/energyplugin.pri +++ b/energyplugin/energyplugin.pri @@ -34,12 +34,14 @@ HEADERS += \ $$PWD/schedulingstrategies/ischedulingstrategy.h \ $$PWD/schedulingstrategies/rulebasedstrategy.h \ $$PWD/schedulingstrategies/aistrategy.h \ + $$PWD/schedulingstrategies/manualstrategy.h \ $$PWD/types/chargingaction.h \ $$PWD/types/charginginfo.h \ $$PWD/types/chargingprocessinfo.h \ $$PWD/types/chargingschedule.h \ $$PWD/types/energytimeslot.h \ $$PWD/types/flexibleload.h \ + $$PWD/types/manualslotconfig.h \ $$PWD/types/schedulerconfig.h \ $$PWD/types/scoreentry.h \ $$PWD/types/smartchargingstate.h \ @@ -59,12 +61,14 @@ SOURCES += \ $$PWD/spotmarket/spotmarketmanager.cpp \ $$PWD/schedulingstrategies/rulebasedstrategy.cpp \ $$PWD/schedulingstrategies/aistrategy.cpp \ + $$PWD/schedulingstrategies/manualstrategy.cpp \ $$PWD/types/chargingaction.cpp \ $$PWD/types/charginginfo.cpp \ $$PWD/types/chargingprocessinfo.cpp \ $$PWD/types/chargingschedule.cpp \ $$PWD/types/energytimeslot.cpp \ $$PWD/types/flexibleload.cpp \ + $$PWD/types/manualslotconfig.cpp \ $$PWD/types/schedulerconfig.cpp \ $$PWD/types/scoreentry.cpp \ $$PWD/types/smartchargingstate.cpp \ diff --git a/energyplugin/energypluginnymea.cpp b/energyplugin/energypluginnymea.cpp index 6af30b1..2e76ae3 100644 --- a/energyplugin/energypluginnymea.cpp +++ b/energyplugin/energypluginnymea.cpp @@ -28,6 +28,7 @@ #include "nymeaenergyjsonhandler.h" #include "energymanagerconfiguration.h" #include "spotmarket/spotmarketmanager.h" +#include "schedulingstrategies/manualstrategy.h" #include "plugininfo.h" @@ -48,7 +49,10 @@ void EnergyPluginNymea::init() SchedulerManager *schedulerManager = new SchedulerManager(spotMarketManager, energyManager(), thingManager(), this); + // Community-tier strategies (always available, no feature flag) + schedulerManager->registerStrategy(new ManualStrategy(this)); + jsonRpcServer()->registerExperienceHandler( new NymeaEnergyJsonHandler(spotMarketManager, chargingManager, schedulerManager, this), - 0, 10); + 0, 11); } diff --git a/energyplugin/nymeaenergyjsonhandler.cpp b/energyplugin/nymeaenergyjsonhandler.cpp index fdbe31c..3defa7f 100644 --- a/energyplugin/nymeaenergyjsonhandler.cpp +++ b/energyplugin/nymeaenergyjsonhandler.cpp @@ -27,6 +27,7 @@ #include "smartchargingmanager.h" #include "spotmarket/spotmarketmanager.h" #include "schedulermanager.h" +#include "schedulingstrategies/manualstrategy.h" #include @@ -376,7 +377,76 @@ NymeaEnergyJsonHandler::NymeaEnergyJsonHandler(SpotMarketManager *spotMarketMana p.insert("slot", slotToVariantMap(slot)); p.insert("appliedCommands", QVariantList()); emit SlotActivated(p); + + // Emit ManualSlotActivated when the executed slot was driven by ManualStrategy + if (!slot.decisionRules.contains(QStringLiteral("ManualSlot"))) + return; + ManualStrategy *ms = manualStrategy(); + ManualSlotConfig matchedConfig; + if (ms) { + foreach (const ManualSlotConfig &cfg, ms->manualSlots()) { + if (cfg.matchesSlot(slot.start)) { + matchedConfig = cfg; + break; + } + } + } + QVariantMap allocs; + allocs.insert("ev", slot.allocatedToEV); + allocs.insert("battery", slot.allocatedToBattery); + allocs.insert("heatpump", slot.allocatedToHP); + allocs.insert("dhw", slot.allocatedToDHW); + allocs.insert("feedin", slot.allocatedToFeedIn); + const QString reason = matchedConfig.label.isEmpty() + ? QStringLiteral("Créneau manuel activé") + : QString("Créneau manuel '%1' activé").arg(matchedConfig.label); + QVariantMap mp; + mp.insert("slot", matchedConfig.toJson()); + mp.insert("appliedAllocations", allocs); + mp.insert("reason", reason); + emit ManualSlotActivated(mp); }); + + // Manual slot methods (v11) + params.clear(); returns.clear(); + description = "Get all manually configured scheduling slots."; + returns.insert("slots", QVariantList()); + registerMethod("GetManualSlots", description, params, returns, + Types::PermissionScopeControlThings); + + params.clear(); returns.clear(); + description = "Create or update a manual scheduling slot. " + "When repeating=true the slot recurs weekly based on day-of-week and time."; + params.insert("start", enumValueName(String)); + params.insert("end", enumValueName(String)); + params.insert("label", enumValueName(String)); + params.insert("repeating", enumValueName(Bool)); + params.insert("o:expiresAt",enumValueName(String)); + params.insert("allocations",QVariantMap()); + returns.insert("energyError", enumRef()); + registerMethod("SetManualSlot", description, params, returns, + Types::PermissionScopeControlThings); + + params.clear(); returns.clear(); + description = "Remove the manual slot that starts at the given UTC timestamp."; + params.insert("start", enumValueName(String)); + returns.insert("energyError", enumRef()); + registerMethod("RemoveManualSlot", description, params, returns, + Types::PermissionScopeControlThings); + + params.clear(); returns.clear(); + description = "Remove all manually configured scheduling slots."; + returns.insert("energyError", enumRef()); + registerMethod("ClearManualSlots", description, params, returns, + Types::PermissionScopeControlThings); + + // ManualSlotActivated push notification + params.clear(); + description = "Emitted when the Scheduler executes a slot driven by ManualStrategy."; + params.insert("slot", QVariantMap()); + params.insert("appliedAllocations", QVariantMap()); + params.insert("reason", enumValueName(String)); + registerNotification("ManualSlotActivated", description, params); } } @@ -723,6 +793,67 @@ JsonReply *NymeaEnergyJsonHandler::OverrideSlot(const QVariantMap ¶ms) return createReply({{"energyError", enumValueName(EnergyManager::EnergyErrorNoError)}}); } +// --------------------------------------------------------------------------- +// Manual slot API — NymeaEnergy v11 +// --------------------------------------------------------------------------- + +JsonReply *NymeaEnergyJsonHandler::GetManualSlots(const QVariantMap ¶ms) +{ + Q_UNUSED(params) + QVariantList configList; + if (m_schedulerManager) { + foreach (const ManualSlotConfig &cfg, m_schedulerManager->manualSlots()) + configList.append(cfg.toJson()); + } + return createReply({{"slots", configList}}); +} + +JsonReply *NymeaEnergyJsonHandler::SetManualSlot(const QVariantMap ¶ms) +{ + if (!m_schedulerManager) + return createReply({{"energyError", enumValueName(EnergyManager::EnergyErrorInvalidParameter)}}); + + ManualSlotConfig cfg = ManualSlotConfig::fromJson(params); + if (!cfg.start.isValid() || !cfg.end.isValid() || cfg.end <= cfg.start) + return createReply({{"energyError", enumValueName(EnergyManager::EnergyErrorInvalidParameter)}}); + + m_schedulerManager->setManualSlot(cfg); + return createReply({{"energyError", enumValueName(EnergyManager::EnergyErrorNoError)}}); +} + +JsonReply *NymeaEnergyJsonHandler::RemoveManualSlot(const QVariantMap ¶ms) +{ + if (!m_schedulerManager) + return createReply({{"energyError", enumValueName(EnergyManager::EnergyErrorInvalidParameter)}}); + + QDateTime start = QDateTime::fromString( + params.value("start").toString(), Qt::ISODateWithMs).toUTC(); + if (!start.isValid()) + return createReply({{"energyError", enumValueName(EnergyManager::EnergyErrorInvalidParameter)}}); + + m_schedulerManager->removeManualSlot(start); + return createReply({{"energyError", enumValueName(EnergyManager::EnergyErrorNoError)}}); +} + +JsonReply *NymeaEnergyJsonHandler::ClearManualSlots(const QVariantMap ¶ms) +{ + Q_UNUSED(params) + if (m_schedulerManager) + m_schedulerManager->clearManualSlots(); + return createReply({{"energyError", enumValueName(EnergyManager::EnergyErrorNoError)}}); +} + +ManualStrategy *NymeaEnergyJsonHandler::manualStrategy() const +{ + if (!m_schedulerManager) + return nullptr; + foreach (ISchedulingStrategy *s, m_schedulerManager->availableStrategies()) { + if (s->strategyId() == QLatin1String("manual")) + return qobject_cast(s); + } + return nullptr; +} + // --------------------------------------------------------------------------- // Private helpers // --------------------------------------------------------------------------- diff --git a/energyplugin/nymeaenergyjsonhandler.h b/energyplugin/nymeaenergyjsonhandler.h index 1df90ef..26eda6b 100644 --- a/energyplugin/nymeaenergyjsonhandler.h +++ b/energyplugin/nymeaenergyjsonhandler.h @@ -31,6 +31,7 @@ #include "types/scoreentry.h" #include "types/energytimeslot.h" +#include "types/manualslotconfig.h" class SmartChargingManager; class SpotMarketManager; @@ -82,6 +83,12 @@ public: Q_INVOKABLE JsonReply *SetLoadConfig(const QVariantMap ¶ms); Q_INVOKABLE JsonReply *OverrideSlot(const QVariantMap ¶ms); + // --- Manual slot API (NymeaEnergy v11) --- + Q_INVOKABLE JsonReply *GetManualSlots(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply *SetManualSlot(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply *RemoveManualSlot(const QVariantMap ¶ms); + Q_INVOKABLE JsonReply *ClearManualSlots(const QVariantMap ¶ms); + signals: void PhasePowerLimitChanged(const QVariantMap ¶ms); void AcquisitionToleranceChanged(const QVariantMap ¶ms); @@ -99,6 +106,9 @@ signals: void SlotActivated(const QVariantMap ¶ms); void OverrideConflict(const QVariantMap ¶ms); + // Manual slot push notification + void ManualSlotActivated(const QVariantMap ¶ms); + private: SpotMarketManager *m_spotMarketManager; SmartChargingManager *m_smartChargingManager = nullptr; @@ -110,6 +120,9 @@ private: // Helper: convert a timeline slot to QVariantMap for JSON-RPC QVariantMap slotToVariantMap(const EnergyTimeSlot &slot) const; + // Helper: access the registered ManualStrategy (null if not registered) + class ManualStrategy *manualStrategy() const; + }; #endif // NYMEAENERGYJSONHANDLER_H diff --git a/energyplugin/schedulermanager.cpp b/energyplugin/schedulermanager.cpp index 3676fe8..2d811c8 100644 --- a/energyplugin/schedulermanager.cpp +++ b/energyplugin/schedulermanager.cpp @@ -25,6 +25,8 @@ #include "schedulermanager.h" #include "schedulingstrategies/rulebasedstrategy.h" #include "schedulingstrategies/aistrategy.h" +#include "schedulingstrategies/manualstrategy.h" +#include "schedulersettings.h" #include "spotmarket/spotmarketmanager.h" #include @@ -40,6 +42,8 @@ SchedulerManager::SchedulerManager( m_energyManager(energyManager), m_thingManager(thingManager) { + m_settings = new SchedulerSettings(this); + // Register built-in strategies RuleBasedStrategy *ruleStrategy = new RuleBasedStrategy(this); AIStrategy *aiStrategy = new AIStrategy(this); @@ -99,6 +103,18 @@ void SchedulerManager::registerStrategy(ISchedulingStrategy *strategy) strategy->setParent(this); m_strategies.append(strategy); qCDebug(dcNymeaEnergy()) << "SchedulerManager: registered strategy" << strategy->strategyId(); + + // Hydrate ManualStrategy with persisted slots as soon as it is registered + if (strategy->strategyId() == QLatin1String("manual") && m_settings) { + ManualStrategy *ms = qobject_cast(strategy); + if (ms) { + foreach (const ManualSlotConfig &cfg, m_settings->manualSlots()) + ms->setManualSlot(cfg); + qCDebug(dcNymeaEnergy()) << "SchedulerManager: loaded" + << m_settings->manualSlots().size() + << "manual slot(s) from settings"; + } + } } // --- Timeline access --- @@ -212,6 +228,55 @@ int SchedulerManager::activeOverridesCount() const return count; } +// --- Manual slot management --- + +QList SchedulerManager::manualSlots() const +{ + ManualStrategy *ms = findManualStrategy(); + if (!ms) + return {}; + return ms->manualSlots(); +} + +void SchedulerManager::setManualSlot(const ManualSlotConfig &config) +{ + ManualStrategy *ms = findManualStrategy(); + if (!ms) { + qCWarning(dcNymeaEnergy()) + << "SchedulerManager::setManualSlot: no ManualStrategy registered"; + return; + } + ms->setManualSlot(config); + if (m_settings) + m_settings->setManualSlot(config); + emit manualSlotsChanged(); + forceRecompute(); +} + +void SchedulerManager::removeManualSlot(const QDateTime &start) +{ + ManualStrategy *ms = findManualStrategy(); + if (!ms) + return; + ms->removeManualSlot(start); + if (m_settings) + m_settings->removeManualSlot(start); + emit manualSlotsChanged(); + forceRecompute(); +} + +void SchedulerManager::clearManualSlots() +{ + ManualStrategy *ms = findManualStrategy(); + if (!ms) + return; + ms->clearAllManualSlots(); + if (m_settings) + m_settings->clearManualSlots(); + emit manualSlotsChanged(); + forceRecompute(); +} + // --- Force recompute --- void SchedulerManager::forceRecompute() @@ -339,6 +404,15 @@ void SchedulerManager::applyCurrentSlot(const EnergyTimeSlot &slot) << "Bat=" << slot.allocatedToBattery << "W"; } +ManualStrategy *SchedulerManager::findManualStrategy() const +{ + foreach (ISchedulingStrategy *s, m_strategies) { + if (s->strategyId() == QLatin1String("manual")) + return qobject_cast(s); + } + return nullptr; +} + void SchedulerManager::scheduleNextSlotTimer() { m_slotTimer.stop(); diff --git a/energyplugin/schedulermanager.h b/energyplugin/schedulermanager.h index 90cb386..c431c99 100644 --- a/energyplugin/schedulermanager.h +++ b/energyplugin/schedulermanager.h @@ -33,6 +33,7 @@ #include "types/energytimeslot.h" #include "types/flexibleload.h" #include "types/schedulerconfig.h" +#include "types/manualslotconfig.h" #include "schedulingstrategies/ischedulingstrategy.h" // from libnymea-energy @@ -99,6 +100,12 @@ public: // --- Force immediate recompute --- void forceRecompute(); + // --- Manual slot management (ManualStrategy) --- + QList manualSlots() const; + void setManualSlot(const ManualSlotConfig &config); + void removeManualSlot(const QDateTime &start); + void clearManualSlots(); + signals: void timelineUpdated(const QList &timeline); void slotExecuted(const EnergyTimeSlot &slot, bool success); @@ -106,6 +113,7 @@ signals: void loadRegistered(const FlexibleLoad &load); void loadUpdated(const FlexibleLoad &load); void configChanged(const SchedulerConfig &config); + void manualSlotsChanged(); private slots: void onRecomputeTimer(); @@ -124,9 +132,13 @@ private: // Schedule next slot execution timer void scheduleNextSlotTimer(); + // Helper: return the registered ManualStrategy, or nullptr if not found + class ManualStrategy *findManualStrategy() const; + SpotMarketManager *m_spotMarketManager = nullptr; EnergyManager *m_energyManager = nullptr; ThingManager *m_thingManager = nullptr; + class SchedulerSettings *m_settings = nullptr; ISchedulingStrategy *m_activeStrategy = nullptr; QList m_strategies; diff --git a/energyplugin/schedulersettings.cpp b/energyplugin/schedulersettings.cpp index fdfe5c3..0277cdc 100644 --- a/energyplugin/schedulersettings.cpp +++ b/energyplugin/schedulersettings.cpp @@ -23,6 +23,7 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "schedulersettings.h" +#include "types/manualslotconfig.h" #include @@ -134,6 +135,47 @@ void SchedulerSettings::clearExpiredOverrides() save(); } +// --- Manual slots --- + +QList SchedulerSettings::manualSlots() const +{ + return m_manualSlots; +} + +void SchedulerSettings::setManualSlot(const ManualSlotConfig &config) +{ + const QString key = config.start.toUTC().toString(Qt::ISODateWithMs); + for (int i = 0; i < m_manualSlots.size(); ++i) { + if (m_manualSlots.at(i).start.toUTC().toString(Qt::ISODateWithMs) == key) { + m_manualSlots[i] = config; + save(); + return; + } + } + m_manualSlots.append(config); + save(); +} + +void SchedulerSettings::removeManualSlot(const QDateTime &start) +{ + const QString key = start.toUTC().toString(Qt::ISODateWithMs); + for (int i = 0; i < m_manualSlots.size(); ++i) { + if (m_manualSlots.at(i).start.toUTC().toString(Qt::ISODateWithMs) == key) { + m_manualSlots.removeAt(i); + save(); + return; + } + } +} + +void SchedulerSettings::clearManualSlots() +{ + if (m_manualSlots.isEmpty()) + return; + m_manualSlots.clear(); + save(); +} + // --- Persistence --- void SchedulerSettings::load() @@ -183,7 +225,44 @@ void SchedulerSettings::load() } s.endArray(); - qCDebug(dcNymeaEnergy()) << "SchedulerSettings: loaded from" << settingsFilePath(); + // Load manual slots + m_manualSlots.clear(); + int manualCount = s.beginReadArray(QStringLiteral("manualSlots")); + for (int i = 0; i < manualCount; ++i) { + s.setArrayIndex(i); + ManualSlotConfig cfg; + cfg.start = QDateTime::fromString(s.value("start").toString(), Qt::ISODateWithMs).toUTC(); + cfg.end = QDateTime::fromString(s.value("end").toString(), Qt::ISODateWithMs).toUTC(); + cfg.label = s.value("label").toString(); + cfg.repeating = s.value("repeating", false).toBool(); + const QString expStr = s.value("expiresAt").toString(); + if (!expStr.isEmpty()) + cfg.expiresAt = QDateTime::fromString(expStr, Qt::ISODateWithMs).toUTC(); + + // Allocations stored as alloc_ev, alloc_battery, … + const QStringList allocKeys = { + QStringLiteral("ev"), QStringLiteral("battery"), + QStringLiteral("dhw"), QStringLiteral("heatpump"), QStringLiteral("feedin") + }; + foreach (const QString &key, allocKeys) { + double val = s.value(QStringLiteral("alloc_") + key, 0.0).toDouble(); + if (val != 0.0) + cfg.powerAllocations.insert(manualSlotSourceFromKey(key), val); + } + + if (!cfg.start.isValid()) continue; + + if (cfg.isExpired()) { + qCDebug(dcNymeaEnergy()) << "SchedulerSettings: discarding expired manual slot" + << cfg.label << "(expired" << cfg.expiresAt << ")"; + continue; + } + m_manualSlots.append(cfg); + } + s.endArray(); + + qCDebug(dcNymeaEnergy()) << "SchedulerSettings: loaded from" << settingsFilePath() + << "—" << m_manualSlots.size() << "manual slot(s)"; } void SchedulerSettings::save() @@ -224,4 +303,23 @@ void SchedulerSettings::save() s.setValue("expiresAt", it.value().expiresAt.toUTC().toString(Qt::ISODateWithMs)); } s.endArray(); + + s.beginWriteArray(QStringLiteral("manualSlots"), m_manualSlots.size()); + idx = 0; + foreach (const ManualSlotConfig &cfg, m_manualSlots) { + s.setArrayIndex(idx++); + s.setValue("start", cfg.start.toUTC().toString(Qt::ISODateWithMs)); + s.setValue("end", cfg.end.toUTC().toString(Qt::ISODateWithMs)); + s.setValue("label", cfg.label); + s.setValue("repeating", cfg.repeating); + if (cfg.expiresAt.isValid()) + s.setValue("expiresAt", cfg.expiresAt.toUTC().toString(Qt::ISODateWithMs)); + // Allocations + for (auto it = cfg.powerAllocations.constBegin(); + it != cfg.powerAllocations.constEnd(); ++it) { + s.setValue(QStringLiteral("alloc_") + manualSlotAllocationKey(it.key()), + it.value()); + } + } + s.endArray(); } diff --git a/energyplugin/schedulersettings.h b/energyplugin/schedulersettings.h index 23c005d..00eb4f5 100644 --- a/energyplugin/schedulersettings.h +++ b/energyplugin/schedulersettings.h @@ -32,6 +32,7 @@ #include "types/schedulerconfig.h" #include "types/flexibleload.h" #include "types/energytimeslot.h" +#include "types/manualslotconfig.h" // Persists SchedulerManager mutable state to: // NymeaSettings::settingsPath() + "/scheduler.conf" (QSettings INI) @@ -80,6 +81,12 @@ public: void removeOverride(const QDateTime &slotStart); void clearExpiredOverrides(); + // Manual slot configurations (used by ManualStrategy) + QList manualSlots() const; + void setManualSlot(const ManualSlotConfig &config); + void removeManualSlot(const QDateTime &start); + void clearManualSlots(); + private: QString settingsFilePath() const; @@ -87,6 +94,7 @@ private: SchedulerConfig m_config; QHash m_loadConfigs; QHash m_overrides; + QList m_manualSlots; void load(); void save(); diff --git a/energyplugin/schedulingstrategies/manualstrategy.cpp b/energyplugin/schedulingstrategies/manualstrategy.cpp new file mode 100644 index 0000000..da5e3ac --- /dev/null +++ b/energyplugin/schedulingstrategies/manualstrategy.cpp @@ -0,0 +1,210 @@ +// 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 "manualstrategy.h" + +#include + +Q_DECLARE_LOGGING_CATEGORY(dcNymeaEnergy) + +ManualStrategy::ManualStrategy(QObject *parent) + : ISchedulingStrategy(parent) +{ +} + +// --------------------------------------------------------------------------- +// computeSchedule +// --------------------------------------------------------------------------- + +QList ManualStrategy::computeSchedule( + const QList &forecast, + const QList &loads, + const SchedulerConfig &config) +{ + Q_UNUSED(config) + + QList timeline = forecast; + + for (int i = 0; i < timeline.size(); ++i) { + EnergyTimeSlot &slot = timeline[i]; + + if (slot.manualOverride) + continue; // respect human decisions + + // Clear previous allocations + slot.allocatedToEV = 0; + slot.allocatedToHP = 0; + slot.allocatedToDHW = 0; + slot.allocatedToBattery = 0; + slot.allocatedToFeedIn = 0; + slot.decisionReason.clear(); + slot.decisionRules.clear(); + + // Look for a matching manual config + const ManualSlotConfig *matched = nullptr; + bool expiredMatch = false; + + foreach (const ManualSlotConfig &cfg, m_manualSlots) { + if (!cfg.matchesSlotIgnoreExpiry(slot.start)) + continue; + + if (cfg.isExpired()) { + expiredMatch = true; + qCDebug(dcNymeaEnergy()) + << "ManualStrategy: expired config" << cfg.label + << "— skipped for slot" << slot.start; + } else { + matched = &cfg; + break; + } + } + + if (matched) { + // Apply exactly the user-defined allocations + slot.allocatedToEV = matched->powerAllocations.value(LoadSource::SmartCharging, 0); + slot.allocatedToHP = matched->powerAllocations.value(LoadSource::HeatPump, 0); + slot.allocatedToDHW = matched->powerAllocations.value(LoadSource::DHW, 0); + slot.allocatedToBattery = matched->powerAllocations.value(LoadSource::Battery, 0); + slot.allocatedToFeedIn = matched->powerAllocations.value(LoadSource::FeedIn, 0); + + if (!matched->label.isEmpty()) + slot.decisionReason = + QString("Créneau manuel '%1' configuré par l'utilisateur") + .arg(matched->label); + else + slot.decisionReason = + QStringLiteral("Créneau manuel configuré par l'utilisateur"); + + slot.decisionRules.append(QStringLiteral("ManualSlot")); + + } else if (expiredMatch) { + // Apply safe fallback first, then override reason to show expiry + applyInflexibleLoads(slot, loads); + slot.decisionReason = QStringLiteral("Créneau expiré — ignoré"); + if (!slot.decisionRules.contains(QStringLiteral("ExpiredSlot"))) + slot.decisionRules.prepend(QStringLiteral("ExpiredSlot")); + + } else { + applyInflexibleLoads(slot, loads); + // applyInflexibleLoads sets reason if critical loads were found; + // fall back to generic message if still empty. + if (slot.decisionReason.isEmpty()) { + slot.decisionReason = + QStringLiteral("Aucune configuration — charges critiques uniquement"); + slot.decisionRules.append(QStringLiteral("ManualDefault")); + } + } + } + + return timeline; +} + +// --------------------------------------------------------------------------- +// explainDecision +// --------------------------------------------------------------------------- + +QString ManualStrategy::explainDecision( + const EnergyTimeSlot &slot, + const FlexibleLoad &load) const +{ + Q_UNUSED(load) + + if (slot.manualOverride) + return QStringLiteral("Décision manuelle : ") + slot.overrideReason; + + if (slot.decisionRules.contains(QStringLiteral("ManualSlot"))) + return slot.decisionReason; + + if (slot.decisionRules.contains(QStringLiteral("ExpiredSlot"))) + return QStringLiteral("La configuration de ce créneau a expiré — " + "seules les charges critiques restent actives."); + + return QStringLiteral("Aucune configuration manuelle pour ce créneau."); +} + +// --------------------------------------------------------------------------- +// Manual slot management +// --------------------------------------------------------------------------- + +void ManualStrategy::setManualSlot(const ManualSlotConfig &slotConfig) +{ + for (int i = 0; i < m_manualSlots.size(); ++i) { + if (m_manualSlots.at(i).start == slotConfig.start) { + m_manualSlots[i] = slotConfig; + return; + } + } + m_manualSlots.append(slotConfig); +} + +void ManualStrategy::removeManualSlot(const QDateTime &slotStart) +{ + for (int i = 0; i < m_manualSlots.size(); ++i) { + if (m_manualSlots.at(i).start == slotStart) { + m_manualSlots.removeAt(i); + return; + } + } +} + +void ManualStrategy::clearAllManualSlots() +{ + m_manualSlots.clear(); +} + +QList ManualStrategy::manualSlots() const +{ + return m_manualSlots; +} + +// --------------------------------------------------------------------------- +// Private helper +// --------------------------------------------------------------------------- + +void ManualStrategy::applyInflexibleLoads(EnergyTimeSlot &slot, + const QList &loads) const +{ + foreach (const FlexibleLoad &load, loads) { + if (load.type != LoadType::Inflexible) + continue; + + if (load.source == LoadSource::HeatPump && load.priority >= 0.9) { + slot.allocatedToHP = qMax(slot.allocatedToHP, load.currentPowerW); + if (slot.decisionReason.isEmpty()) + slot.decisionReason = + QStringLiteral("Chauffage critique — PAC toujours active"); + if (!slot.decisionRules.contains(QStringLiteral("CriticalHeating"))) + slot.decisionRules.append(QStringLiteral("CriticalHeating")); + } + + if (load.source == LoadSource::DHW && load.priority >= 0.9) { + slot.allocatedToDHW = qMax(slot.allocatedToDHW, load.currentPowerW); + if (slot.decisionReason.isEmpty()) + slot.decisionReason = + QStringLiteral("ECS critique (sécurité légionellose)"); + if (!slot.decisionRules.contains(QStringLiteral("CriticalDHW"))) + slot.decisionRules.append(QStringLiteral("CriticalDHW")); + } + } +} diff --git a/energyplugin/schedulingstrategies/manualstrategy.h b/energyplugin/schedulingstrategies/manualstrategy.h new file mode 100644 index 0000000..2d38b82 --- /dev/null +++ b/energyplugin/schedulingstrategies/manualstrategy.h @@ -0,0 +1,80 @@ +// 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 . +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef MANUALSTRATEGY_H +#define MANUALSTRATEGY_H + +#include "ischedulingstrategy.h" +#include "types/manualslotconfig.h" + +// Community-tier scheduling strategy: the user defines all time windows and +// power allocations explicitly. +// +// For each forecast slot: +// - If a ManualSlotConfig covers that slot: apply its allocations exactly. +// - If a matching config exists but is expired: reason = "Créneau expiré — ignoré", +// apply critical/inflexible loads for safety. +// - If no config matches: apply critical/inflexible loads only, reason = +// "Aucune configuration — charges critiques uniquement". +// +// decisionReason is never empty (contract from ISchedulingStrategy). +class ManualStrategy : public ISchedulingStrategy +{ + Q_OBJECT +public: + explicit ManualStrategy(QObject *parent = nullptr); + + QString strategyId() const override { return QStringLiteral("manual"); } + QString displayName() const override { return QStringLiteral("Manuel"); } + QString description() const override { + return QStringLiteral( + "L'utilisateur définit lui-même les créneaux de charge. " + "Aucune automatisation — contrôle total."); + } + + QList computeSchedule( + const QList &forecast, + const QList &loads, + const SchedulerConfig &config) override; + + QString explainDecision( + const EnergyTimeSlot &slot, + const FlexibleLoad &load) const override; + + // Manual slot management + void setManualSlot(const ManualSlotConfig &slotConfig); + void removeManualSlot(const QDateTime &slotStart); + void clearAllManualSlots(); + QList manualSlots() const; + +private: + // Apply critical/inflexible loads to a slot (safety fallback). + // Sets decisionReason if it is still empty after processing. + void applyInflexibleLoads(EnergyTimeSlot &slot, + const QList &loads) const; + + QList m_manualSlots; +}; + +#endif // MANUALSTRATEGY_H diff --git a/energyplugin/types/manualslotconfig.cpp b/energyplugin/types/manualslotconfig.cpp new file mode 100644 index 0000000..ba14703 --- /dev/null +++ b/energyplugin/types/manualslotconfig.cpp @@ -0,0 +1,153 @@ +// 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 "manualslotconfig.h" + +// --------------------------------------------------------------------------- +// Free helpers for powerAllocations map serialisation +// --------------------------------------------------------------------------- + +QString manualSlotAllocationKey(LoadSource source) +{ + switch (source) { + case LoadSource::SmartCharging: return QStringLiteral("ev"); + case LoadSource::Battery: return QStringLiteral("battery"); + case LoadSource::DHW: return QStringLiteral("dhw"); + case LoadSource::HeatPump: return QStringLiteral("heatpump"); + case LoadSource::FeedIn: return QStringLiteral("feedin"); + default: return QStringLiteral("external"); + } +} + +LoadSource manualSlotSourceFromKey(const QString &key) +{ + if (key == QLatin1String("ev")) return LoadSource::SmartCharging; + if (key == QLatin1String("battery")) return LoadSource::Battery; + if (key == QLatin1String("dhw")) return LoadSource::DHW; + if (key == QLatin1String("heatpump")) return LoadSource::HeatPump; + if (key == QLatin1String("feedin")) return LoadSource::FeedIn; + return LoadSource::External; +} + +// --------------------------------------------------------------------------- +// Expiry check +// --------------------------------------------------------------------------- + +bool ManualSlotConfig::isExpired() const +{ + return expiresAt.isValid() + && expiresAt <= QDateTime::currentDateTimeUtc(); +} + +// --------------------------------------------------------------------------- +// Slot matching +// --------------------------------------------------------------------------- + +// Compute "minutes since start of week" for repeating-slot comparison. +// Qt dayOfWeek(): 1=Mon … 7=Sun. We convert to 0-based (Mon=0). +static int minsOfWeek(const QDateTime &dt) +{ + QDateTime utc = dt.toUTC(); + int dayIndex = utc.date().dayOfWeek() - 1; // 0=Mon … 6=Sun + return dayIndex * 24 * 60 + + utc.time().hour() * 60 + + utc.time().minute(); +} + +bool ManualSlotConfig::matchesSlotIgnoreExpiry(const QDateTime &slotStart) const +{ + if (!start.isValid() || !end.isValid()) + return false; + + if (!repeating) + return slotStart >= start && slotStart < end; + + // Repeating: compare minutes-of-week for day-of-week + time-of-day + int slotMins = minsOfWeek(slotStart); + int startMins = minsOfWeek(start); + int endMins = minsOfWeek(end); + + if (startMins <= endMins) { + // Normal case — stays within the same calendar week segment + return slotMins >= startMins && slotMins < endMins; + } else { + // Wrap-around case — e.g. Sun 22:00 → Mon 06:00 + return slotMins >= startMins || slotMins < endMins; + } +} + +bool ManualSlotConfig::matchesSlot(const QDateTime &slotStart) const +{ + if (isExpired()) + return false; + return matchesSlotIgnoreExpiry(slotStart); +} + +// --------------------------------------------------------------------------- +// JSON serialisation +// --------------------------------------------------------------------------- + +QVariantMap ManualSlotConfig::toJson() const +{ + QVariantMap map; + if (start.isValid()) + map.insert("start", start.toUTC().toString(Qt::ISODateWithMs)); + if (end.isValid()) + map.insert("end", end.toUTC().toString(Qt::ISODateWithMs)); + map.insert("label", label); + map.insert("repeating", repeating); + if (expiresAt.isValid()) + map.insert("expiresAt", expiresAt.toUTC().toString(Qt::ISODateWithMs)); + + QVariantMap allocs; + for (auto it = powerAllocations.constBegin(); it != powerAllocations.constEnd(); ++it) + allocs.insert(manualSlotAllocationKey(it.key()), it.value()); + map.insert("allocations", allocs); + + return map; +} + +ManualSlotConfig ManualSlotConfig::fromJson(const QVariantMap &map) +{ + ManualSlotConfig cfg; + cfg.start = QDateTime::fromString( + map.value("start").toString(), Qt::ISODateWithMs).toUTC(); + cfg.end = QDateTime::fromString( + map.value("end").toString(), Qt::ISODateWithMs).toUTC(); + cfg.label = map.value("label").toString(); + cfg.repeating = map.value("repeating", false).toBool(); + + const QString expiresStr = map.value("expiresAt").toString(); + if (!expiresStr.isEmpty()) + cfg.expiresAt = QDateTime::fromString(expiresStr, Qt::ISODateWithMs).toUTC(); + + const QVariantMap allocs = map.value("allocations").toMap(); + for (auto it = allocs.constBegin(); it != allocs.constEnd(); ++it) { + LoadSource src = manualSlotSourceFromKey(it.key()); + if (src != LoadSource::External || it.key() == QLatin1String("external")) + cfg.powerAllocations.insert(src, it.value().toDouble()); + } + + return cfg; +} diff --git a/energyplugin/types/manualslotconfig.h b/energyplugin/types/manualslotconfig.h new file mode 100644 index 0000000..a55c080 --- /dev/null +++ b/energyplugin/types/manualslotconfig.h @@ -0,0 +1,70 @@ +// 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 . +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef MANUALSLOTCONFIG_H +#define MANUALSLOTCONFIG_H + +#include +#include +#include +#include +#include + +#include "flexibleload.h" + +// Configuration for one manually-defined scheduling slot. +// Used exclusively by ManualStrategy. +// +// When repeating = true, the config recurs weekly based on the day-of-week +// and time-of-day derived from start/end. Overnight slots (e.g. Mon 22:00 +// → Tue 06:00) are handled correctly. +struct ManualSlotConfig { + QDateTime start; + QDateTime end; + QMap powerAllocations; // LoadSource → Watts + QString label; // user-visible name, e.g. "Recharge VE nuit" + bool repeating = false; + QDateTime expiresAt; // optional — slot is ignored after this time + + bool isNull() const { return !start.isValid(); } + bool isExpired() const; + + // Returns true if slotStart falls within this config's time window. + // Returns false when isExpired() — use matchesSlotIgnoreExpiry() to detect + // "would have matched but is expired". + bool matchesSlot(const QDateTime &slotStart) const; + bool matchesSlotIgnoreExpiry(const QDateTime &slotStart) const; + + QVariantMap toJson() const; + static ManualSlotConfig fromJson(const QVariantMap &map); +}; + +Q_DECLARE_METATYPE(ManualSlotConfig) + +// Helpers for serialising powerAllocations map. +// Keys used in JSON: "ev", "battery", "dhw", "heatpump", "feedin" +QString manualSlotAllocationKey(LoadSource source); +LoadSource manualSlotSourceFromKey(const QString &key); + +#endif // MANUALSLOTCONFIG_H diff --git a/tests/auto/scheduler/testscheduler.cpp b/tests/auto/scheduler/testscheduler.cpp index 4daa783..c88c4df 100644 --- a/tests/auto/scheduler/testscheduler.cpp +++ b/tests/auto/scheduler/testscheduler.cpp @@ -334,6 +334,221 @@ void TestScheduler::testHotStrategySwap() "AI stub missing AIModelNotLoaded rule tag"); } +// --------------------------------------------------------------------------- +// Test 5 — ManualStrategy: slot configured → allocations applied exactly +// --------------------------------------------------------------------------- +void TestScheduler::testManualStrategy_basicSlot() +{ + // Slot: today 22:00 UTC → tomorrow 06:00 UTC, ev=2000W, battery=1000W + QDate today = QDate::currentDate(); + QDateTime slotStart = QDateTime(today, QTime(22, 0, 0), Qt::UTC); + QDateTime slotEnd = QDateTime(today.addDays(1), QTime(6, 0, 0), Qt::UTC); + + ManualSlotConfig cfg; + cfg.start = slotStart; + cfg.end = slotEnd; + cfg.label = QStringLiteral("Recharge VE nuit"); + cfg.repeating = false; + cfg.powerAllocations.insert(LoadSource::SmartCharging, 2000.0); + cfg.powerAllocations.insert(LoadSource::Battery, 1000.0); + + ManualStrategy strategy; + strategy.setManualSlot(cfg); + + // Build a 2-slot forecast covering 22:00 and 23:00 + QList forecast; + for (int h = 0; h < 2; ++h) { + EnergyTimeSlot s; + s.start = slotStart.addSecs(h * 3600); + s.end = slotStart.addSecs((h + 1) * 3600); + forecast.append(s); + } + + SchedulerConfig config; + QList result = strategy.computeSchedule(forecast, {}, config); + + QCOMPARE(result.size(), 2); + + // Both slots fall within 22:00–06:00 → allocations must match exactly + foreach (const EnergyTimeSlot &slot, result) { + QCOMPARE(slot.allocatedToEV, 2000.0); + QCOMPARE(slot.allocatedToBattery, 1000.0); + QVERIFY2(!slot.decisionReason.isEmpty(), "decisionReason must not be empty"); + QVERIFY2(slot.decisionReason.contains("manuel", Qt::CaseInsensitive) || + slot.decisionReason.contains("Recharge VE nuit"), + "decisionReason should mention manual or label"); + QVERIFY2(slot.decisionRules.contains("ManualSlot"), + "decisionRules must contain ManualSlot"); + } +} + +// --------------------------------------------------------------------------- +// Test 6 — ManualStrategy: no config → fallback with safe reason +// --------------------------------------------------------------------------- +void TestScheduler::testManualStrategy_noConfig_fallback() +{ + // No manual slot configured at all + ManualStrategy strategy; + + QDateTime base = QDateTime(QDate::currentDate(), QTime(10, 0, 0), Qt::UTC); + + QList forecast; + for (int h = 0; h < 3; ++h) { + EnergyTimeSlot s; + s.start = base.addSecs(h * 3600); + s.end = base.addSecs((h + 1) * 3600); + forecast.append(s); + } + + SchedulerConfig config; + QList result = strategy.computeSchedule(forecast, {}, config); + + QCOMPARE(result.size(), 3); + foreach (const EnergyTimeSlot &slot, result) { + QCOMPARE(slot.allocatedToEV, 0.0); + QCOMPARE(slot.allocatedToBattery, 0.0); + QVERIFY2(!slot.decisionReason.isEmpty(), + "decisionReason must never be empty"); + QVERIFY2(slot.decisionReason.contains("Aucune configuration", + Qt::CaseInsensitive) || + slot.decisionRules.contains("ManualDefault"), + "Slot without config must report ManualDefault"); + } +} + +// --------------------------------------------------------------------------- +// Test 7 — ManualStrategy: expired slot is ignored, reason set correctly +// --------------------------------------------------------------------------- +void TestScheduler::testManualStrategy_expiredSlot() +{ + QDate today = QDate::currentDate(); + QDateTime slotStart = QDateTime(today, QTime(14, 0, 0), Qt::UTC); + QDateTime slotEnd = QDateTime(today, QTime(16, 0, 0), Qt::UTC); + + ManualSlotConfig cfg; + cfg.start = slotStart; + cfg.end = slotEnd; + cfg.label = QStringLiteral("Créneau dépassé"); + cfg.repeating = false; + // Expiry = yesterday → already expired + cfg.expiresAt = QDateTime::currentDateTimeUtc().addDays(-1); + cfg.powerAllocations.insert(LoadSource::SmartCharging, 5000.0); + + ManualStrategy strategy; + strategy.setManualSlot(cfg); + + QList forecast; + EnergyTimeSlot s; + s.start = slotStart; + s.end = slotStart.addSecs(3600); + forecast.append(s); + + SchedulerConfig config; + QList result = strategy.computeSchedule(forecast, {}, config); + + QCOMPARE(result.size(), 1); + const EnergyTimeSlot &slot = result.first(); + + // Expired slot: EV must NOT be charged + QCOMPARE(slot.allocatedToEV, 0.0); + + // Reason must clearly state expiry + QVERIFY2(slot.decisionReason.contains("expiré", Qt::CaseInsensitive), + "decisionReason must mention expiry"); + QVERIFY2(slot.decisionRules.contains("ExpiredSlot"), + "decisionRules must contain ExpiredSlot"); +} + +// --------------------------------------------------------------------------- +// Test 8 — ManualStrategy: repeating weekly slot applied to next recurrence +// --------------------------------------------------------------------------- +void TestScheduler::testManualStrategy_repeatingSlot() +{ + // Find the most recent Monday as our anchor + QDate anchor = QDate::currentDate(); + while (anchor.dayOfWeek() != 1) // Qt: 1 = Monday + anchor = anchor.addDays(-1); + + ManualSlotConfig cfg; + cfg.start = QDateTime(anchor, QTime(22, 0, 0), Qt::UTC); + cfg.end = QDateTime(anchor.addDays(1), QTime(6, 0, 0), Qt::UTC); // overnight + cfg.label = QStringLiteral("Recharge hebdomadaire"); + cfg.repeating = true; + // No expiresAt — repeats indefinitely + cfg.powerAllocations.insert(LoadSource::SmartCharging, 3000.0); + + ManualStrategy strategy; + strategy.setManualSlot(cfg); + + // Next Monday 23:00 — should match the repeating window Mon 22:00 → Tue 06:00 + QDate nextMonday = anchor.addDays(7); + QDateTime testSlot = QDateTime(nextMonday, QTime(23, 0, 0), Qt::UTC); + + QList forecast; + EnergyTimeSlot s; + s.start = testSlot; + s.end = testSlot.addSecs(3600); + forecast.append(s); + + SchedulerConfig config; + QList result = strategy.computeSchedule(forecast, {}, config); + + QCOMPARE(result.size(), 1); + QVERIFY2(result.first().allocatedToEV > 0, + "Repeating slot must be applied to next weekly recurrence"); + QCOMPARE(result.first().allocatedToEV, 3000.0); + QVERIFY2(result.first().decisionReason.contains("Recharge hebdomadaire"), + "decisionReason must contain the slot label"); +} + +// --------------------------------------------------------------------------- +// Test 9 — ManualStrategy: JSON round-trip (persistence simulation) +// --------------------------------------------------------------------------- +void TestScheduler::testManualStrategy_persistence() +{ + QDate today = QDate::currentDate(); + + ManualSlotConfig original; + original.start = QDateTime(today, QTime(22, 0, 0), Qt::UTC); + original.end = QDateTime(today.addDays(1), QTime(6, 0, 0), Qt::UTC); + original.label = QStringLiteral("Recharge VE nuit"); + original.repeating = false; + original.expiresAt = QDateTime::currentDateTimeUtc().addDays(7); // future + original.powerAllocations.insert(LoadSource::SmartCharging, 2000.0); + original.powerAllocations.insert(LoadSource::Battery, 1000.0); + + // Serialize → deserialize + QVariantMap json = original.toJson(); + ManualSlotConfig restored = ManualSlotConfig::fromJson(json); + + QCOMPARE(restored.start, original.start); + QCOMPARE(restored.end, original.end); + QCOMPARE(restored.label, original.label); + QCOMPARE(restored.repeating, original.repeating); + QCOMPARE(restored.expiresAt, original.expiresAt); + QCOMPARE(restored.powerAllocations.value(LoadSource::SmartCharging), 2000.0); + QCOMPARE(restored.powerAllocations.value(LoadSource::Battery), 1000.0); + + // Verify the restored config is functional: apply it in a new strategy instance + ManualStrategy strategy; + strategy.setManualSlot(restored); + + QList forecast; + EnergyTimeSlot s; + s.start = original.start; + s.end = original.start.addSecs(3600); + forecast.append(s); + + SchedulerConfig config; + QList result = strategy.computeSchedule(forecast, {}, config); + + QCOMPARE(result.size(), 1); + QCOMPARE(result.first().allocatedToEV, 2000.0); + QCOMPARE(result.first().allocatedToBattery, 1000.0); + QVERIFY2(result.first().decisionRules.contains("ManualSlot"), + "Restored slot must produce ManualSlot rule after round-trip"); +} + // --------------------------------------------------------------------------- // Test runner // --------------------------------------------------------------------------- diff --git a/tests/auto/scheduler/testscheduler.h b/tests/auto/scheduler/testscheduler.h index d17fa1f..4b36466 100644 --- a/tests/auto/scheduler/testscheduler.h +++ b/tests/auto/scheduler/testscheduler.h @@ -35,6 +35,8 @@ #include "types/schedulerconfig.h" #include "schedulingstrategies/rulebasedstrategy.h" #include "schedulingstrategies/aistrategy.h" +#include "schedulingstrategies/manualstrategy.h" +#include "types/manualslotconfig.h" // Unit tests for the scheduling algorithm. // These tests operate on pure algorithm logic (RuleBasedStrategy, AIStrategy) @@ -72,6 +74,21 @@ private slots: // → No crash, plan is valid void testHotStrategySwap(); + // Test 5: ManualStrategy — slot configured, allocations applied exactly + void testManualStrategy_basicSlot(); + + // Test 6: ManualStrategy — no config → fallback (critical loads only) + void testManualStrategy_noConfig_fallback(); + + // Test 7: ManualStrategy — expired slot ignored, reason set correctly + void testManualStrategy_expiredSlot(); + + // Test 8: ManualStrategy — repeating weekly slot applied to next recurrence + void testManualStrategy_repeatingSlot(); + + // Test 9: ManualStrategy — JSON round-trip (persistence simulation) + void testManualStrategy_persistence(); + private: // Build a synthetic 24h forecast starting at a given base time QList buildWinterForecast(const QDateTime &start) const;