feat: ManualStrategy — community tier scheduling with full manual control
- Add ManualSlotConfig type with weekly repeating support and auto-expiry - Add ManualStrategy (strategyId="manual"): applies user-defined allocations exactly; expired slots logged and skipped; inflexible/critical loads always applied as safety fallback; decisionReason never empty - Extend SchedulerSettings with manualSlots persistence section (INI array) - Extend SchedulerManager with setManualSlot/removeManualSlot/clearManualSlots methods; hydrates ManualStrategy from settings on registerStrategy() - Add JSON-RPC v11 methods: GetManualSlots, SetManualSlot, RemoveManualSlot, ClearManualSlots + ManualSlotActivated push notification - Register ManualStrategy in energypluginnymea.cpp::init() (no feature flag) - Add 5 unit tests: basicSlot, noConfig_fallback, expiredSlot, repeatingSlot, persistence (JSON round-trip) - Update doc.md section 11 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
253c5f487a
commit
0797f37c78
@ -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
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
build-test/energyplugin/manualslotconfig.o
Normal file
BIN
build-test/energyplugin/manualslotconfig.o
Normal file
Binary file not shown.
BIN
build-test/energyplugin/manualstrategy.o
Normal file
BIN
build-test/energyplugin/manualstrategy.o
Normal file
Binary file not shown.
102
build-test/energyplugin/moc_manualstrategy.cpp
Normal file
102
build-test/energyplugin/moc_manualstrategy.cpp
Normal file
@ -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 <QtCore/qmetatype.h>
|
||||
|
||||
#include <QtCore/qtmochelpers.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
|
||||
#include <QtCore/qxptype_traits.h>
|
||||
#if !defined(Q_MOC_OUTPUT_REVISION)
|
||||
#error "The header file 'manualstrategy.h' doesn't include <QObject>."
|
||||
#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<ISchedulingStrategy::staticMetaObject>(),
|
||||
qt_meta_stringdata_ZN14ManualStrategyE.offsetsAndSizes,
|
||||
qt_meta_data_ZN14ManualStrategyE,
|
||||
qt_static_metacall,
|
||||
nullptr,
|
||||
qt_incomplete_metaTypeArray<qt_meta_tag_ZN14ManualStrategyE_t,
|
||||
// Q_OBJECT / Q_GADGET
|
||||
QtPrivate::TypeAndForceComplete<ManualStrategy, std::true_type>
|
||||
>,
|
||||
nullptr
|
||||
} };
|
||||
|
||||
void ManualStrategy::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
|
||||
{
|
||||
auto *_t = static_cast<ManualStrategy *>(_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<void*>(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
|
||||
BIN
build-test/energyplugin/moc_manualstrategy.o
Normal file
BIN
build-test/energyplugin/moc_manualstrategy.o
Normal file
Binary file not shown.
@ -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<void, std::false_type>,
|
||||
QtPrivate::TypeAndForceComplete<const QVariantMap &, std::false_type>,
|
||||
// method 'ManualSlotActivated'
|
||||
QtPrivate::TypeAndForceComplete<void, std::false_type>,
|
||||
QtPrivate::TypeAndForceComplete<const QVariantMap &, std::false_type>,
|
||||
// method 'GetPhasePowerLimit'
|
||||
QtPrivate::TypeAndForceComplete<JsonReply *, std::false_type>,
|
||||
QtPrivate::TypeAndForceComplete<const QVariantMap &, std::false_type>,
|
||||
@ -292,6 +310,18 @@ Q_CONSTINIT const QMetaObject NymeaEnergyJsonHandler::staticMetaObject = { {
|
||||
QtPrivate::TypeAndForceComplete<const QVariantMap &, std::false_type>,
|
||||
// method 'OverrideSlot'
|
||||
QtPrivate::TypeAndForceComplete<JsonReply *, std::false_type>,
|
||||
QtPrivate::TypeAndForceComplete<const QVariantMap &, std::false_type>,
|
||||
// method 'GetManualSlots'
|
||||
QtPrivate::TypeAndForceComplete<JsonReply *, std::false_type>,
|
||||
QtPrivate::TypeAndForceComplete<const QVariantMap &, std::false_type>,
|
||||
// method 'SetManualSlot'
|
||||
QtPrivate::TypeAndForceComplete<JsonReply *, std::false_type>,
|
||||
QtPrivate::TypeAndForceComplete<const QVariantMap &, std::false_type>,
|
||||
// method 'RemoveManualSlot'
|
||||
QtPrivate::TypeAndForceComplete<JsonReply *, std::false_type>,
|
||||
QtPrivate::TypeAndForceComplete<const QVariantMap &, std::false_type>,
|
||||
// method 'ClearManualSlots'
|
||||
QtPrivate::TypeAndForceComplete<JsonReply *, std::false_type>,
|
||||
QtPrivate::TypeAndForceComplete<const QVariantMap &, std::false_type>
|
||||
>,
|
||||
nullptr
|
||||
@ -315,49 +345,58 @@ void NymeaEnergyJsonHandler::qt_static_metacall(QObject *_o, QMetaObject::Call _
|
||||
case 10: _t->TimelineUpdated((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_a[1]))); break;
|
||||
case 11: _t->SlotActivated((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_a[1]))); break;
|
||||
case 12: _t->OverrideConflict((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_a[1]))); break;
|
||||
case 13: { JsonReply* _r = _t->GetPhasePowerLimit((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_a[1])));
|
||||
case 13: _t->ManualSlotActivated((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_a[1]))); break;
|
||||
case 14: { JsonReply* _r = _t->GetPhasePowerLimit((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 15: { JsonReply* _r = _t->SetPhasePowerLimit((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 16: { JsonReply* _r = _t->GetAcquisitionTolerance((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 17: { JsonReply* _r = _t->SetAcquisitionTolerance((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 18: { JsonReply* _r = _t->GetBatteryLevelConsideration((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 19: { JsonReply* _r = _t->SetBatteryLevelConsideration((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 20: { JsonReply* _r = _t->GetChargingInfos((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])),(*reinterpret_cast< std::add_pointer_t<JsonContext>>(_a[2])));
|
||||
case 21: { JsonReply* _r = _t->SetChargingInfo((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_a[1])),(*reinterpret_cast< std::add_pointer_t<JsonContext>>(_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<QVariantMap>>(_a[1])));
|
||||
case 22: { JsonReply* _r = _t->GetLockOnUnplug((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 23: { JsonReply* _r = _t->SetLockOnUnplug((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 24: { JsonReply* _r = _t->GetAvailableSpotMarketProviders((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 25: { JsonReply* _r = _t->GetSpotMarketConfiguration((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 26: { JsonReply* _r = _t->SetSpotMarketConfiguration((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 27: { JsonReply* _r = _t->GetSpotMarketScoreEntries((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 28: { JsonReply* _r = _t->GetChargingSchedules((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 29: { JsonReply* _r = _t->GetEnergyTimeline((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 30: { JsonReply* _r = _t->GetFlexibleLoads((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 31: { JsonReply* _r = _t->GetSchedulerStatus((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 32: { JsonReply* _r = _t->SetSchedulerStrategy((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 33: { JsonReply* _r = _t->SetSchedulerConfig((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 34: { JsonReply* _r = _t->SetLoadConfig((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_a[1])));
|
||||
case 35: { JsonReply* _r = _t->OverrideSlot((*reinterpret_cast< std::add_pointer_t<QVariantMap>>(_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<QVariantMap>>(_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<QVariantMap>>(_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<QVariantMap>>(_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<QVariantMap>>(_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<QMetaType *>(_a[0]) = QMetaType();
|
||||
_id -= 35;
|
||||
_id -= 40;
|
||||
}
|
||||
return _id;
|
||||
}
|
||||
@ -579,4 +625,11 @@ void NymeaEnergyJsonHandler::OverrideConflict(const QVariantMap & _t1)
|
||||
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
|
||||
QMetaObject::activate(this, &staticMetaObject, 12, _a);
|
||||
}
|
||||
|
||||
// SIGNAL 13
|
||||
void NymeaEnergyJsonHandler::ManualSlotActivated(const QVariantMap & _t1)
|
||||
{
|
||||
void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
|
||||
QMetaObject::activate(this, &staticMetaObject, 13, _a);
|
||||
}
|
||||
QT_WARNING_POP
|
||||
|
||||
Binary file not shown.
@ -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<void, std::false_type>,
|
||||
QtPrivate::TypeAndForceComplete<const SchedulerConfig &, std::false_type>,
|
||||
// method 'manualSlotsChanged'
|
||||
QtPrivate::TypeAndForceComplete<void, std::false_type>,
|
||||
// method 'onRecomputeTimer'
|
||||
QtPrivate::TypeAndForceComplete<void, std::false_type>,
|
||||
// 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<FlexibleLoad>>(_a[1]))); break;
|
||||
case 4: _t->loadUpdated((*reinterpret_cast< std::add_pointer_t<FlexibleLoad>>(_a[1]))); break;
|
||||
case 5: _t->configChanged((*reinterpret_cast< std::add_pointer_t<SchedulerConfig>>(_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<void*>(reinterpret_cast<const void*>(std::addressof(_t1))) };
|
||||
QMetaObject::activate(this, &staticMetaObject, 5, _a);
|
||||
}
|
||||
|
||||
// SIGNAL 6
|
||||
void SchedulerManager::manualSlotsChanged()
|
||||
{
|
||||
QMetaObject::activate(this, &staticMetaObject, 6, nullptr);
|
||||
}
|
||||
QT_WARNING_POP
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
147
doc.md
147
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<LoadSource, double> 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*
|
||||
|
||||
@ -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 \
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
#include "smartchargingmanager.h"
|
||||
#include "spotmarket/spotmarketmanager.h"
|
||||
#include "schedulermanager.h"
|
||||
#include "schedulingstrategies/manualstrategy.h"
|
||||
|
||||
#include <energymanager.h>
|
||||
|
||||
@ -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<EnergyManager::EnergyError>());
|
||||
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<EnergyManager::EnergyError>());
|
||||
registerMethod("RemoveManualSlot", description, params, returns,
|
||||
Types::PermissionScopeControlThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Remove all manually configured scheduling slots.";
|
||||
returns.insert("energyError", enumRef<EnergyManager::EnergyError>());
|
||||
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<ManualStrategy *>(s);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 <QLoggingCategory>
|
||||
@ -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<ManualStrategy *>(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<ManualSlotConfig> 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<ManualStrategy *>(s);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SchedulerManager::scheduleNextSlotTimer()
|
||||
{
|
||||
m_slotTimer.stop();
|
||||
|
||||
@ -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<ManualSlotConfig> manualSlots() const;
|
||||
void setManualSlot(const ManualSlotConfig &config);
|
||||
void removeManualSlot(const QDateTime &start);
|
||||
void clearManualSlots();
|
||||
|
||||
signals:
|
||||
void timelineUpdated(const QList<EnergyTimeSlot> &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<ISchedulingStrategy *> m_strategies;
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "schedulersettings.h"
|
||||
#include "types/manualslotconfig.h"
|
||||
|
||||
#include <nymeasettings.h>
|
||||
|
||||
@ -134,6 +135,47 @@ void SchedulerSettings::clearExpiredOverrides()
|
||||
save();
|
||||
}
|
||||
|
||||
// --- Manual slots ---
|
||||
|
||||
QList<ManualSlotConfig> 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();
|
||||
}
|
||||
|
||||
@ -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<ManualSlotConfig> 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<QString, LoadConfig> m_loadConfigs;
|
||||
QHash<QString, OverrideEntry> m_overrides;
|
||||
QList<ManualSlotConfig> m_manualSlots;
|
||||
|
||||
void load();
|
||||
void save();
|
||||
|
||||
210
energyplugin/schedulingstrategies/manualstrategy.cpp
Normal file
210
energyplugin/schedulingstrategies/manualstrategy.cpp
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "manualstrategy.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(dcNymeaEnergy)
|
||||
|
||||
ManualStrategy::ManualStrategy(QObject *parent)
|
||||
: ISchedulingStrategy(parent)
|
||||
{
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// computeSchedule
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
QList<EnergyTimeSlot> ManualStrategy::computeSchedule(
|
||||
const QList<EnergyTimeSlot> &forecast,
|
||||
const QList<FlexibleLoad> &loads,
|
||||
const SchedulerConfig &config)
|
||||
{
|
||||
Q_UNUSED(config)
|
||||
|
||||
QList<EnergyTimeSlot> 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<ManualSlotConfig> ManualStrategy::manualSlots() const
|
||||
{
|
||||
return m_manualSlots;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private helper
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void ManualStrategy::applyInflexibleLoads(EnergyTimeSlot &slot,
|
||||
const QList<FlexibleLoad> &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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
80
energyplugin/schedulingstrategies/manualstrategy.h
Normal file
80
energyplugin/schedulingstrategies/manualstrategy.h
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#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<EnergyTimeSlot> computeSchedule(
|
||||
const QList<EnergyTimeSlot> &forecast,
|
||||
const QList<FlexibleLoad> &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<ManualSlotConfig> 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<FlexibleLoad> &loads) const;
|
||||
|
||||
QList<ManualSlotConfig> m_manualSlots;
|
||||
};
|
||||
|
||||
#endif // MANUALSTRATEGY_H
|
||||
153
energyplugin/types/manualslotconfig.cpp
Normal file
153
energyplugin/types/manualslotconfig.cpp
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "manualslotconfig.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Free helpers for powerAllocations map serialisation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
QString manualSlotAllocationKey(LoadSource source)
|
||||
{
|
||||
switch (source) {
|
||||
case LoadSource::SmartCharging: return QStringLiteral("ev");
|
||||
case LoadSource::Battery: return QStringLiteral("battery");
|
||||
case LoadSource::DHW: return QStringLiteral("dhw");
|
||||
case LoadSource::HeatPump: return QStringLiteral("heatpump");
|
||||
case LoadSource::FeedIn: return QStringLiteral("feedin");
|
||||
default: return QStringLiteral("external");
|
||||
}
|
||||
}
|
||||
|
||||
LoadSource manualSlotSourceFromKey(const QString &key)
|
||||
{
|
||||
if (key == QLatin1String("ev")) return LoadSource::SmartCharging;
|
||||
if (key == QLatin1String("battery")) return LoadSource::Battery;
|
||||
if (key == QLatin1String("dhw")) return LoadSource::DHW;
|
||||
if (key == QLatin1String("heatpump")) return LoadSource::HeatPump;
|
||||
if (key == QLatin1String("feedin")) return LoadSource::FeedIn;
|
||||
return LoadSource::External;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Expiry check
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool ManualSlotConfig::isExpired() const
|
||||
{
|
||||
return expiresAt.isValid()
|
||||
&& expiresAt <= QDateTime::currentDateTimeUtc();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Slot matching
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Compute "minutes since start of week" for repeating-slot comparison.
|
||||
// Qt dayOfWeek(): 1=Mon … 7=Sun. We convert to 0-based (Mon=0).
|
||||
static int minsOfWeek(const QDateTime &dt)
|
||||
{
|
||||
QDateTime utc = dt.toUTC();
|
||||
int dayIndex = utc.date().dayOfWeek() - 1; // 0=Mon … 6=Sun
|
||||
return dayIndex * 24 * 60
|
||||
+ utc.time().hour() * 60
|
||||
+ utc.time().minute();
|
||||
}
|
||||
|
||||
bool ManualSlotConfig::matchesSlotIgnoreExpiry(const QDateTime &slotStart) const
|
||||
{
|
||||
if (!start.isValid() || !end.isValid())
|
||||
return false;
|
||||
|
||||
if (!repeating)
|
||||
return slotStart >= start && slotStart < end;
|
||||
|
||||
// Repeating: compare minutes-of-week for day-of-week + time-of-day
|
||||
int slotMins = minsOfWeek(slotStart);
|
||||
int startMins = minsOfWeek(start);
|
||||
int endMins = minsOfWeek(end);
|
||||
|
||||
if (startMins <= endMins) {
|
||||
// Normal case — stays within the same calendar week segment
|
||||
return slotMins >= startMins && slotMins < endMins;
|
||||
} else {
|
||||
// Wrap-around case — e.g. Sun 22:00 → Mon 06:00
|
||||
return slotMins >= startMins || slotMins < endMins;
|
||||
}
|
||||
}
|
||||
|
||||
bool ManualSlotConfig::matchesSlot(const QDateTime &slotStart) const
|
||||
{
|
||||
if (isExpired())
|
||||
return false;
|
||||
return matchesSlotIgnoreExpiry(slotStart);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// JSON serialisation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
QVariantMap ManualSlotConfig::toJson() const
|
||||
{
|
||||
QVariantMap map;
|
||||
if (start.isValid())
|
||||
map.insert("start", start.toUTC().toString(Qt::ISODateWithMs));
|
||||
if (end.isValid())
|
||||
map.insert("end", end.toUTC().toString(Qt::ISODateWithMs));
|
||||
map.insert("label", label);
|
||||
map.insert("repeating", repeating);
|
||||
if (expiresAt.isValid())
|
||||
map.insert("expiresAt", expiresAt.toUTC().toString(Qt::ISODateWithMs));
|
||||
|
||||
QVariantMap allocs;
|
||||
for (auto it = powerAllocations.constBegin(); it != powerAllocations.constEnd(); ++it)
|
||||
allocs.insert(manualSlotAllocationKey(it.key()), it.value());
|
||||
map.insert("allocations", allocs);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
ManualSlotConfig ManualSlotConfig::fromJson(const QVariantMap &map)
|
||||
{
|
||||
ManualSlotConfig cfg;
|
||||
cfg.start = QDateTime::fromString(
|
||||
map.value("start").toString(), Qt::ISODateWithMs).toUTC();
|
||||
cfg.end = QDateTime::fromString(
|
||||
map.value("end").toString(), Qt::ISODateWithMs).toUTC();
|
||||
cfg.label = map.value("label").toString();
|
||||
cfg.repeating = map.value("repeating", false).toBool();
|
||||
|
||||
const QString expiresStr = map.value("expiresAt").toString();
|
||||
if (!expiresStr.isEmpty())
|
||||
cfg.expiresAt = QDateTime::fromString(expiresStr, Qt::ISODateWithMs).toUTC();
|
||||
|
||||
const QVariantMap allocs = map.value("allocations").toMap();
|
||||
for (auto it = allocs.constBegin(); it != allocs.constEnd(); ++it) {
|
||||
LoadSource src = manualSlotSourceFromKey(it.key());
|
||||
if (src != LoadSource::External || it.key() == QLatin1String("external"))
|
||||
cfg.powerAllocations.insert(src, it.value().toDouble());
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
70
energyplugin/types/manualslotconfig.h
Normal file
70
energyplugin/types/manualslotconfig.h
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef MANUALSLOTCONFIG_H
|
||||
#define MANUALSLOTCONFIG_H
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
#include <QMetaType>
|
||||
|
||||
#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<LoadSource, double> 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
|
||||
@ -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<EnergyTimeSlot> 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<EnergyTimeSlot> 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<EnergyTimeSlot> 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<EnergyTimeSlot> 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<EnergyTimeSlot> forecast;
|
||||
EnergyTimeSlot s;
|
||||
s.start = slotStart;
|
||||
s.end = slotStart.addSecs(3600);
|
||||
forecast.append(s);
|
||||
|
||||
SchedulerConfig config;
|
||||
QList<EnergyTimeSlot> 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<EnergyTimeSlot> forecast;
|
||||
EnergyTimeSlot s;
|
||||
s.start = testSlot;
|
||||
s.end = testSlot.addSecs(3600);
|
||||
forecast.append(s);
|
||||
|
||||
SchedulerConfig config;
|
||||
QList<EnergyTimeSlot> 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<EnergyTimeSlot> forecast;
|
||||
EnergyTimeSlot s;
|
||||
s.start = original.start;
|
||||
s.end = original.start.addSecs(3600);
|
||||
forecast.append(s);
|
||||
|
||||
SchedulerConfig config;
|
||||
QList<EnergyTimeSlot> 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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@ -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<EnergyTimeSlot> buildWinterForecast(const QDateTime &start) const;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user