openhems 0.2.2__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openhems-0.2.2/src/openhems.egg-info → openhems-0.2.3}/PKG-INFO +1 -1
- {openhems-0.2.2 → openhems-0.2.3}/pyproject.toml +1 -1
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/data/openhems_default.yaml +17 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/contract/generic_contract.py +32 -12
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/contract/rte_contract.py +1 -1
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/__init__.py +2 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/driver/emhass_adapter.py +26 -5
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/emhass_strategy.py +19 -81
- openhems-0.2.3/src/openhems/modules/energy_strategy/energy_strategy.py +273 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/hybridinverter_strategy.py +6 -4
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/offgrid_strategy.py +6 -5
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/offpeak_strategy.py +9 -29
- openhems-0.2.3/src/openhems/modules/energy_strategy/simulated_annealing_algo.py +372 -0
- openhems-0.2.3/src/openhems/modules/energy_strategy/simulated_annealing_strategy.py +119 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/solarbased_strategy.py +24 -16
- openhems-0.2.3/src/openhems/modules/energy_strategy/solarnosell_strategy.py +111 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/sourceinverter_strategy.py +11 -6
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/switchoff_strategy.py +11 -16
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/network/feeder.py +4 -4
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/network/network.py +20 -8
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/network/node.py +110 -15
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/server.py +20 -4
- {openhems-0.2.2 → openhems-0.2.3/src/openhems.egg-info}/PKG-INFO +1 -1
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems.egg-info/SOURCES.txt +3 -2
- openhems-0.2.3/tests/test_annealing_strategy.py +48 -0
- {openhems-0.2.2 → openhems-0.2.3}/tests/test_contract_module.py +2 -1
- openhems-0.2.2/tests/test_server.py → openhems-0.2.3/tests/test_offpeak_strategy.py +27 -31
- openhems-0.2.2/src/openhems/modules/energy_strategy/energy_strategy.py +0 -153
- openhems-0.2.2/src/openhems/modules/energy_strategy/solarnosell_strategy.py +0 -34
- openhems-0.2.2/tests/test_offpeak_strategy.py +0 -24
- openhems-0.2.2/tests/test_openhems.py +0 -36
- {openhems-0.2.2 → openhems-0.2.3}/LICENSE +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/MANIFEST.in +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/emhass/config_emhass.yaml +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/emhass/secrets_emhass.yaml +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/readme.md +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/setup.cfg +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/setup.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/__init__.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/check_homeassistant_api.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/data/keys_en.yaml +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/data/keys_fr.yaml +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/data/openhems_default_docker.yaml +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/data/openhems_tooltips_en.yaml +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/data/openhems_tooltips_fr.yaml +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/data_load_cost_forecast.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/data_load_forecast.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/data_prod_price_forecast.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/data_train_load_clustering.pkl +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/data_train_load_forecast.pkl +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/data_weather_forecast.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/heating_prediction.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/opt_res_latest.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/opt_res_perfect_optim_cost.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/opt_res_perfect_optim_profit.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/opt_res_perfect_optim_self-consumption.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/test_df_final.pkl +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/test_response_get_data_get_method.pbz2 +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/test_response_scrapper_get_method.pbz2 +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/test_response_solarforecast_get_method.pbz2 +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/test_response_solcast_get_method.pbz2 +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/docs/conf.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/options.json +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/load_clustering.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/load_forecast_sklearn.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/optim_results_analysis.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/read_csv_plot_data.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/save_pvlib_module_inverter_database.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/script_debug_forecasts.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/script_debug_optim.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/script_simple_thermal_model.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/script_thermal_model_optim.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/special_config_analysis.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/special_options.json +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/use_cases_analysis.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/__init__.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/command_line.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/data/config_defaults.json +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/forecast.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/machine_learning_forecaster.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/machine_learning_regressor.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/optimization.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/retrieve_hass.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/static/data/param_definitions.json +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/utils.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/web_server.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/tests/__init__.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/tests/test_command_line_utils.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/tests/test_forecast.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/tests/test_machine_learning_forecaster.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/tests/test_machine_learning_regressor.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/tests/test_optimization.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/tests/test_retrieve_hass.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/tests/test_utils.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/main.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/__init__.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/contract/__init__.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/contract/contract.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/network/__init__.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/network/driver/fake_network.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/network/driver/home_assistant_api.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/network/homestate_updater.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/network/network_helper.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/util/__init__.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/util/cast_utility.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/util/configuration_manager.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/util/notification_manager.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/util/time.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/__init__.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/css/openhems.css +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/driver_vpn.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/img/alarm.svg +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/img/correct_32.ico +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/img/delete-20px.png +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/img/delete.png +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/img/favicon.ico +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/img/hourglass.svg +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/img/save_32.ico +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/img/wait.gif +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/js/params.js +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/schedule.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/templates/panel.jinja2 +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/templates/params.framework.jinja2 +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/web.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems/server_vpn.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems.egg-info/dependency_links.txt +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems.egg-info/requires.txt +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/src/openhems.egg-info/top_level.txt +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/tests/test_emhass_adapter.py +0 -0
- {openhems-0.2.2 → openhems-0.2.3}/tests/test_util_module.py +0 -0
|
@@ -51,6 +51,22 @@ default:
|
|
|
51
51
|
reverse: False
|
|
52
52
|
offhours: []
|
|
53
53
|
offconditions: False
|
|
54
|
+
annealing:
|
|
55
|
+
freq: 15
|
|
56
|
+
max_iteration_number: 1000
|
|
57
|
+
initial_temp: 1000
|
|
58
|
+
cooling_factor: 0.95
|
|
59
|
+
min_temp: 0.1
|
|
60
|
+
max_no_improve: 100
|
|
61
|
+
nosell:
|
|
62
|
+
ratio: -1
|
|
63
|
+
margin: 300
|
|
64
|
+
nobuy:
|
|
65
|
+
ratio: 1
|
|
66
|
+
margin: 300
|
|
67
|
+
ratiosellbuy:
|
|
68
|
+
ratio: 0
|
|
69
|
+
margin: 200
|
|
54
70
|
node:
|
|
55
71
|
publicpowergrid:
|
|
56
72
|
currentPower: null
|
|
@@ -80,6 +96,7 @@ default:
|
|
|
80
96
|
hoursRanges: []
|
|
81
97
|
defaultPrice: 0.1
|
|
82
98
|
outRangePrice: 1.0
|
|
99
|
+
sellprice: 0.0
|
|
83
100
|
solarpanel:
|
|
84
101
|
currentPower: None
|
|
85
102
|
maxPower: 500
|
|
@@ -13,16 +13,18 @@ class GenericContract:
|
|
|
13
13
|
"""
|
|
14
14
|
logger = logging.getLogger(__name__)
|
|
15
15
|
|
|
16
|
-
def __init__(self, hoursRanges=None, defaultPrice=0.0, outRangePrice=0.15):
|
|
16
|
+
def __init__(self, hoursRanges=None, defaultPrice=0.0, outRangePrice=0.15, sellPrice=0.0):
|
|
17
17
|
if hoursRanges is None:
|
|
18
18
|
self.hoursRanges = HoursRanges([], outRangeCost=defaultPrice)
|
|
19
19
|
else:
|
|
20
|
-
self.logger.info(
|
|
21
|
-
|
|
20
|
+
self.logger.info(
|
|
21
|
+
"GenericContract(hoursRanges=%s, defaultCost=%s, outRangeCost=%s, sellPrice=%s)",
|
|
22
|
+
str(hoursRanges), defaultPrice, outRangePrice, sellPrice)
|
|
22
23
|
self.hoursRanges = HoursRanges(
|
|
23
24
|
hoursRanges, defaultCost=defaultPrice , outRangeCost=outRangePrice
|
|
24
25
|
)
|
|
25
26
|
self._inOffpeakRange = None
|
|
27
|
+
self.sellPrice = sellPrice
|
|
26
28
|
self.rangeEnd = datetime.datetime.now()
|
|
27
29
|
|
|
28
30
|
@staticmethod
|
|
@@ -30,9 +32,9 @@ class GenericContract:
|
|
|
30
32
|
"""
|
|
31
33
|
Parse a configuration dict to create a GenericContract
|
|
32
34
|
"""
|
|
33
|
-
hoursRanges, defaultPrice, outRangePrice = \
|
|
35
|
+
hoursRanges, defaultPrice, outRangePrice, sellPrice = \
|
|
34
36
|
GenericContract.extractFromDict(dictConf, configuration)
|
|
35
|
-
return GenericContract(hoursRanges, defaultPrice, outRangePrice)
|
|
37
|
+
return GenericContract(hoursRanges, defaultPrice, outRangePrice, sellPrice=sellPrice)
|
|
36
38
|
|
|
37
39
|
@staticmethod
|
|
38
40
|
def extractFromDict(dictConf, configuration):
|
|
@@ -43,8 +45,9 @@ class GenericContract:
|
|
|
43
45
|
keys = (dictConf, configuration, "generic")
|
|
44
46
|
defaultPrice = GenericContract.get("defaultPrice", keys, "float")
|
|
45
47
|
outRangePrice = GenericContract.get("outRangePrice", keys, "float")
|
|
48
|
+
sellPrice = GenericContract.get("sellPrice", keys, "float")
|
|
46
49
|
hoursRanges = GenericContract.get("hoursRanges", keys, "list")
|
|
47
|
-
return (hoursRanges, defaultPrice, outRangePrice)
|
|
50
|
+
return (hoursRanges, defaultPrice, outRangePrice, sellPrice)
|
|
48
51
|
|
|
49
52
|
def getHoursRanges(self, now=None, attime=None):
|
|
50
53
|
"""
|
|
@@ -103,8 +106,19 @@ class GenericContract:
|
|
|
103
106
|
_, _, cost = self.getHoursRanges(now, attime).checkRange(self.getTime(now, attime))
|
|
104
107
|
return cost
|
|
105
108
|
|
|
109
|
+
def getSellPrice(self, now=None, attime=None):
|
|
110
|
+
"""
|
|
111
|
+
now: datetime witch represent current time, default is datetime.now().
|
|
112
|
+
now must never go back further at runtime
|
|
113
|
+
now is used for cached time, usually it's datetime.now() except for test to simulate situations.
|
|
114
|
+
attime: datetime witch represent time to check price. Default is now.
|
|
115
|
+
Return: the Kw sell cost at 'now'.
|
|
116
|
+
"""
|
|
117
|
+
del now, attime
|
|
118
|
+
return self.sellPrice
|
|
119
|
+
|
|
106
120
|
def __str__(self):
|
|
107
|
-
return "GenericContract("+str(self.hoursRanges)+")"
|
|
121
|
+
return "GenericContract("+str(self.hoursRanges)+", sellPrice={self.sellPrice})"
|
|
108
122
|
|
|
109
123
|
def inOffpeakRange(self, now=None, attime=None, useCache=True):
|
|
110
124
|
"""
|
|
@@ -132,16 +146,22 @@ class GenericContract:
|
|
|
132
146
|
value = dictConf.get(key)
|
|
133
147
|
if value is None:
|
|
134
148
|
baseKey = "default.node.publicpowergrid.contract"
|
|
149
|
+
completeKey = baseKey+"."+classname+"."+key
|
|
135
150
|
value = configuration.get(
|
|
136
|
-
|
|
151
|
+
completeKey,
|
|
137
152
|
defaultType
|
|
138
153
|
)
|
|
139
154
|
if value is None:
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
configuration.get(baseKey, deepSearch=True)
|
|
155
|
+
value = configuration.get(
|
|
156
|
+
completeKey.lower(),
|
|
157
|
+
defaultType
|
|
144
158
|
)
|
|
159
|
+
if value is None:
|
|
160
|
+
GenericContract.logger.warning(
|
|
161
|
+
"No default value for '%s.%s.%s'. Availables are %s",
|
|
162
|
+
baseKey, classname, key,
|
|
163
|
+
configuration.get(baseKey, deepSearch=True)
|
|
164
|
+
)
|
|
145
165
|
elif defaultType is not None:
|
|
146
166
|
value = CastUtililty.toType(defaultType, value)
|
|
147
167
|
return value
|
|
@@ -62,7 +62,7 @@ class RTETempoContract(RTEContract):
|
|
|
62
62
|
url = "https://www.api-couleur-tempo.fr/api/jourTempo/"+day
|
|
63
63
|
retVal = None
|
|
64
64
|
for _ in range(3): # Could be usefull for 502 error
|
|
65
|
-
self.logger.debug("Call API %s
|
|
65
|
+
self.logger.debug("Call API %s.", url)
|
|
66
66
|
# User-Agent is mandatory else 502 error
|
|
67
67
|
response = requests.get(url, timeout=10, allow_redirects=False,
|
|
68
68
|
headers={'User-Agent': 'Mozilla/5.0'})
|
|
@@ -9,3 +9,5 @@ from .switchoff_strategy import SwitchoffStrategy
|
|
|
9
9
|
# from .emhass_strategy import EmhassStrategy
|
|
10
10
|
from .solarbased_strategy import SolarBasedStrategy
|
|
11
11
|
from .hybridinverter_strategy import HybridInverterStrategy
|
|
12
|
+
from .simulated_annealing_strategy import SimulatedAnnealingStrategy
|
|
13
|
+
from .solarnosell_strategy import SolarNoSellStrategy
|
{openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/driver/emhass_adapter.py
RENAMED
|
@@ -7,7 +7,7 @@ import os
|
|
|
7
7
|
import logging
|
|
8
8
|
import json
|
|
9
9
|
import sys
|
|
10
|
-
import
|
|
10
|
+
import math
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
import importlib
|
|
13
13
|
# from importlib.metadata import version
|
|
@@ -38,20 +38,41 @@ import emhass.command_line as em
|
|
|
38
38
|
import emhass.utils as em_utils
|
|
39
39
|
|
|
40
40
|
|
|
41
|
-
@dataclasses.dataclass
|
|
42
41
|
class Deferrable:
|
|
43
42
|
"""
|
|
44
43
|
Custom class to simplify emhass module live modifications.
|
|
45
44
|
"""
|
|
46
45
|
power: float # Nominal power
|
|
47
|
-
|
|
48
|
-
node
|
|
46
|
+
_duration: float # Duration in hours? (In emhass granularity)
|
|
47
|
+
node = None
|
|
49
48
|
startTimestep = 0
|
|
50
49
|
endTimestep = 0
|
|
51
50
|
constant = False
|
|
52
51
|
startPenalty: float = 0.0
|
|
53
52
|
asSemiCont: bool = True
|
|
54
53
|
|
|
54
|
+
def __init__(self, power:float, duration:float, node=None):
|
|
55
|
+
self.power = power
|
|
56
|
+
self._duration = duration
|
|
57
|
+
self.node = node
|
|
58
|
+
|
|
59
|
+
def getDuration(self):
|
|
60
|
+
"""
|
|
61
|
+
return: duration in seconds
|
|
62
|
+
"""
|
|
63
|
+
return self._duration
|
|
64
|
+
def getDurationInHours(self):
|
|
65
|
+
"""
|
|
66
|
+
Return duration in Emhass prevision granularity
|
|
67
|
+
"""
|
|
68
|
+
# TODO granularity can be less or more than hour...
|
|
69
|
+
return math.ceil(self._duration / 3600)
|
|
70
|
+
def setDuration(self, duration):
|
|
71
|
+
"""
|
|
72
|
+
param: duration: in seconds
|
|
73
|
+
"""
|
|
74
|
+
self._duration = duration
|
|
75
|
+
|
|
55
76
|
class EmhassAdapter:
|
|
56
77
|
"""
|
|
57
78
|
Class to use easily EMHASS on OpenHEMS.
|
|
@@ -120,7 +141,7 @@ class EmhassAdapter:
|
|
|
120
141
|
optimConf = inputDataDict['opt'].optim_conf
|
|
121
142
|
optimConf['num_def_loads'] = len(self.deferables)
|
|
122
143
|
optimConf['P_deferrable_nom'] = [d.power for d in self.deferables]
|
|
123
|
-
optimConf['def_total_hours'] = [d.
|
|
144
|
+
optimConf['def_total_hours'] = [d.getDurationInHours() for d in self.deferables]
|
|
124
145
|
optimConf['def_start_timestep'] = [d.startTimestep for d in self.deferables]
|
|
125
146
|
optimConf['def_end_timestep'] = [d.endTimestep for d in self.deferables]
|
|
126
147
|
optimConf['set_def_constant'] = [d.constant for d in self.deferables]
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
This strategy use HEMASS to choose what to do.
|
|
3
3
|
This use Artificial Intelligence to guess the futur.
|
|
4
4
|
So this require some more Python packages.
|
|
5
|
+
|
|
6
|
+
TODO : RunOk - InProd
|
|
5
7
|
"""
|
|
6
8
|
|
|
7
|
-
import
|
|
8
|
-
from datetime import datetime, timedelta
|
|
9
|
+
from datetime import datetime
|
|
9
10
|
import logging
|
|
10
11
|
import pytz
|
|
11
12
|
import numpy as np
|
|
@@ -28,21 +29,17 @@ class EmhassStrategy(EnergyStrategy):
|
|
|
28
29
|
def __init__(self, mylogger, network: OpenHEMSNetwork,
|
|
29
30
|
configurationGlobal:ConfigurationManager, configurationEmhass:dict,
|
|
30
31
|
strategyId:str="emhass"):
|
|
31
|
-
|
|
32
|
+
freq = configurationEmhass.get("freq")
|
|
33
|
+
super().__init__(strategyId, network, mylogger, evalFrequency=freq)
|
|
32
34
|
self.logger.info("EmhassStrategy(%s)", configurationEmhass)
|
|
33
35
|
self.adapter = EmhassAdapter.createFromOpenHEMS(
|
|
34
36
|
configurationEmhass=configurationEmhass, configurationGlobal=configurationGlobal,
|
|
35
37
|
network=network)
|
|
36
38
|
self.network = network
|
|
37
|
-
freq = configurationEmhass.get("freq")
|
|
38
|
-
self.emhassEvalFrequence = timedelta(minutes=freq)
|
|
39
39
|
self.timezone = pytz.timezone(configurationGlobal.get("localization.timeZone"))
|
|
40
|
-
self.
|
|
41
|
-
self.deferables = {}
|
|
42
|
-
self.deferablesKeys = []
|
|
43
|
-
self.nextEvalDate = datetime.now(self.timezone) - self.emhassEvalFrequence
|
|
40
|
+
self._data = None
|
|
44
41
|
|
|
45
|
-
def
|
|
42
|
+
def eval(self):
|
|
46
43
|
"""
|
|
47
44
|
Launch EMHASS optimization plan and store result.
|
|
48
45
|
Also update depending attributes.
|
|
@@ -63,61 +60,17 @@ class EmhassStrategy(EnergyStrategy):
|
|
|
63
60
|
self.logger.debug("No deferrables so no EMHASS optimization to do.")
|
|
64
61
|
data = None
|
|
65
62
|
self.deferablesKeys = self.deferables.keys()
|
|
66
|
-
self.
|
|
67
|
-
self.nextEvalDate = datetime.now(self.timezone) + self.emhassEvalFrequence
|
|
63
|
+
self._data = data
|
|
68
64
|
return data
|
|
69
65
|
|
|
70
|
-
def
|
|
66
|
+
def getDeferrables(self, node, durationInSecs):
|
|
71
67
|
"""
|
|
72
|
-
Return
|
|
68
|
+
Return a Deferrable representing the node (adding usefull informations for algo).
|
|
73
69
|
"""
|
|
74
|
-
|
|
75
|
-
return
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"""
|
|
79
|
-
Update scheduled devices according to emhass
|
|
80
|
-
to scheduled devices according to openhems
|
|
81
|
-
Return true if schedule has been updated
|
|
82
|
-
"""
|
|
83
|
-
# print("EmhassStrategy.updateDeferables()")
|
|
84
|
-
update = False
|
|
85
|
-
self.deferables = {}
|
|
86
|
-
for node in self.network.getAll("out"):
|
|
87
|
-
nodeId = node.id
|
|
88
|
-
durationInSecs = node.getSchedule().duration
|
|
89
|
-
deferable = self.deferables.get(nodeId, None)
|
|
90
|
-
durationInHour = self.getDurationInHour(durationInSecs)
|
|
91
|
-
if deferable is None:
|
|
92
|
-
if durationInSecs>0: # Add a new deferrable
|
|
93
|
-
update = True
|
|
94
|
-
power = node.getMaxPower()
|
|
95
|
-
self.deferables[nodeId] = Deferrable(
|
|
96
|
-
power=power, duration=durationInHour, node=node
|
|
97
|
-
)
|
|
98
|
-
else:
|
|
99
|
-
if durationInSecs<=0: # Remove a deferrable
|
|
100
|
-
del self.deferables[nodeId]
|
|
101
|
-
update = True
|
|
102
|
-
elif deferable.duration!=durationInHour: # update a deferrable
|
|
103
|
-
update = True
|
|
104
|
-
deferable.duration = durationInHour
|
|
105
|
-
print("EmhassStrategy.updateDeferables() => ", update, )
|
|
106
|
-
return update
|
|
107
|
-
|
|
108
|
-
def check(self, now=None):
|
|
109
|
-
"""
|
|
110
|
-
Check and eval if necessary
|
|
111
|
-
- EMHASS optimization
|
|
112
|
-
- power margin
|
|
113
|
-
- conformity to EMHASS plan
|
|
114
|
-
"""
|
|
115
|
-
# print("EmhassStrategy.check()")
|
|
116
|
-
if now is None:
|
|
117
|
-
now = datetime.now(self.timezone)
|
|
118
|
-
if self.updateDeferables() or now>self.nextEvalDate:
|
|
119
|
-
# print("EmhassStrategy.check() : emhassEval")
|
|
120
|
-
self.emhassEval()
|
|
70
|
+
power = node.getMaxPower()
|
|
71
|
+
return Deferrable(
|
|
72
|
+
power=power, duration=durationInSecs, node=node
|
|
73
|
+
)
|
|
121
74
|
|
|
122
75
|
def evaluatePertinenceSwitchOn(self, switchOnRate, node):
|
|
123
76
|
"""
|
|
@@ -134,7 +87,7 @@ class EmhassStrategy(EnergyStrategy):
|
|
|
134
87
|
Return a tuple of datetime and rows for datetime = "now",
|
|
135
88
|
with previous (If present) and Next one
|
|
136
89
|
"""
|
|
137
|
-
if self.
|
|
90
|
+
if self._data is None or isinstance(self._data, bool): # Case no deferables or Error
|
|
138
91
|
return ((None, None, None), (None, None, None))
|
|
139
92
|
if now is None:
|
|
140
93
|
now = datetime.now(self.timezone)
|
|
@@ -146,7 +99,7 @@ class EmhassStrategy(EnergyStrategy):
|
|
|
146
99
|
curDT = None
|
|
147
100
|
nextDT = None
|
|
148
101
|
stop = False
|
|
149
|
-
for timestamp, row in self.
|
|
102
|
+
for timestamp, row in self._data.iterrows():
|
|
150
103
|
prevDT = curDT
|
|
151
104
|
curDT = nextDT
|
|
152
105
|
nextDT = timestamp.to_pydatetime()
|
|
@@ -157,14 +110,14 @@ class EmhassStrategy(EnergyStrategy):
|
|
|
157
110
|
return ((prevDT, curDT, nextDT), (prevRow, curRow, nextRow))
|
|
158
111
|
if nextDT>now:
|
|
159
112
|
stop = True
|
|
160
|
-
if not stop: # should be impossible : Relaunch
|
|
113
|
+
if not stop: # should be impossible : Relaunch eval()?
|
|
161
114
|
self.logger.error("No row in data previsions from EMHASS for current datetime.")
|
|
162
115
|
return ((None, None, None), (None, None, None))
|
|
163
116
|
return [[prevDT, curDT, nextDT], [prevRow, curRow, nextRow]]
|
|
164
117
|
|
|
165
|
-
def
|
|
118
|
+
def apply(self, cycleDuration, now=None):
|
|
166
119
|
"""
|
|
167
|
-
This should apply emhass result (
|
|
120
|
+
This should apply emhass result (eval call) : self._data
|
|
168
121
|
"""
|
|
169
122
|
if now is None:
|
|
170
123
|
now = datetime.now(self.timezone)
|
|
@@ -195,18 +148,3 @@ class EmhassStrategy(EnergyStrategy):
|
|
|
195
148
|
doSwitchOn = self.evaluatePertinenceSwitchOn(switchOnRate, deferable.node)
|
|
196
149
|
self.switchSchedulable(deferable.node, cycleDuration, doSwitchOn)
|
|
197
150
|
return True
|
|
198
|
-
|
|
199
|
-
def updateNetwork(self, cycleDuration, allowSleep:bool, now=None):
|
|
200
|
-
"""
|
|
201
|
-
Decide what to do during the cycle:
|
|
202
|
-
IF off-peak : switch on all
|
|
203
|
-
ELSE : Switch off all AND Sleep until off-peak
|
|
204
|
-
Now is used to get a fake
|
|
205
|
-
"""
|
|
206
|
-
if now is None:
|
|
207
|
-
now = datetime.now(self.timezone)
|
|
208
|
-
elif now.tzinfo is None or now.tzinfo!=self.timezone:
|
|
209
|
-
now = now.replace(tzinfo=self.timezone)
|
|
210
|
-
self.check(now)
|
|
211
|
-
self.emhassApply(cycleDuration, now=now)
|
|
212
|
-
return cycleDuration
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Super class for all EnergyStrategy modules
|
|
3
|
+
|
|
4
|
+
List of todo list to integrate a strategy
|
|
5
|
+
- Implemented : Implement main functions inherit from EnergyStrategy:
|
|
6
|
+
at least __init__() and updateNetwork() (or eval and apply)
|
|
7
|
+
- Call : Call constructor in server.py
|
|
8
|
+
- Conf : Configure it in openhems_default.py
|
|
9
|
+
- TestAuto : Add unittest in test_xxx_strategy.py.
|
|
10
|
+
- RunOk : Unitest do not test all, if run ok, all seam really ok.
|
|
11
|
+
- InProd : If it's tested in a real house.
|
|
12
|
+
|
|
13
|
+
#DONE: Implemented - Call - Conf - TestAuto - RunOk - InProd
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import datetime
|
|
18
|
+
from openhems.modules.network.network import OpenHEMSNetwork
|
|
19
|
+
|
|
20
|
+
LOOP_DELAY_VIRTUAL = 0
|
|
21
|
+
|
|
22
|
+
class StrategyNode:
|
|
23
|
+
"""
|
|
24
|
+
Class to manage a node in a strategy: keep track of its state
|
|
25
|
+
"""
|
|
26
|
+
def __init__(self, node, logger):
|
|
27
|
+
self.logger = logger
|
|
28
|
+
self.node = node
|
|
29
|
+
self.isOn = None
|
|
30
|
+
|
|
31
|
+
def changed(self, isOn=None):
|
|
32
|
+
"""
|
|
33
|
+
Change the state of the node
|
|
34
|
+
"""
|
|
35
|
+
wasOn = self.isOn
|
|
36
|
+
if isOn is None:
|
|
37
|
+
isOn = self.node.isOn()
|
|
38
|
+
self.isOn = isOn
|
|
39
|
+
return wasOn!=self.isOn
|
|
40
|
+
|
|
41
|
+
def decreaseTime(self, cycleDuration:int):
|
|
42
|
+
"""
|
|
43
|
+
Decrease the time of the schedule
|
|
44
|
+
"""
|
|
45
|
+
if self.isOn:
|
|
46
|
+
if not self.node.isOn(): # Was successfully switched off at previous cycle
|
|
47
|
+
self.isOn = False
|
|
48
|
+
return False
|
|
49
|
+
schedule = self.node.getSchedule()
|
|
50
|
+
remainingTime = schedule.decreaseTime(cycleDuration)
|
|
51
|
+
if remainingTime==0:
|
|
52
|
+
self.logger.info("Switch off '%s' due to elapsed time.", self.node.id)
|
|
53
|
+
if self.node.switchOn(False):
|
|
54
|
+
self.logger.warning("Fail switch off '%s'.", self.node.id)
|
|
55
|
+
else:
|
|
56
|
+
return False
|
|
57
|
+
return True
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
class DefaultDeferrable:
|
|
61
|
+
"""
|
|
62
|
+
Class to manage a deferrable device
|
|
63
|
+
"""
|
|
64
|
+
def __init__(self, node, durationInSecs:int):
|
|
65
|
+
self.node = node
|
|
66
|
+
self.durationInSecs = durationInSecs
|
|
67
|
+
|
|
68
|
+
def getDuration(self):
|
|
69
|
+
"""
|
|
70
|
+
return the duration of the deferrable device
|
|
71
|
+
"""
|
|
72
|
+
return self.durationInSecs
|
|
73
|
+
|
|
74
|
+
def setDuration(self, durationInSecs:int):
|
|
75
|
+
"""
|
|
76
|
+
Set the duration of the deferrable device
|
|
77
|
+
"""
|
|
78
|
+
self.durationInSecs = durationInSecs
|
|
79
|
+
|
|
80
|
+
class EnergyStrategy:
|
|
81
|
+
"""
|
|
82
|
+
Super class for all EnergyStrategy modules
|
|
83
|
+
"""
|
|
84
|
+
def __init__(self, strategyId:str, network:OpenHEMSNetwork,
|
|
85
|
+
logger=None, evalFrequency:int=60):
|
|
86
|
+
if logger is None:
|
|
87
|
+
logger = logging.getLogger(__name__)
|
|
88
|
+
self.logger = logger
|
|
89
|
+
self.strategyId = strategyId
|
|
90
|
+
self.network = network
|
|
91
|
+
self._nodes = None
|
|
92
|
+
self.evalFrequence = datetime.timedelta(minutes=evalFrequency)
|
|
93
|
+
self.nextEvalDate = datetime.datetime.now() - self.evalFrequence
|
|
94
|
+
self.deferables = {}
|
|
95
|
+
self.deferablesKeys = []
|
|
96
|
+
|
|
97
|
+
def getNodes(self, encapsulated=False):
|
|
98
|
+
"""
|
|
99
|
+
Return nodes concerned by a defined strategy
|
|
100
|
+
"""
|
|
101
|
+
if encapsulated:
|
|
102
|
+
if self._nodes is None:
|
|
103
|
+
self._nodes = [
|
|
104
|
+
StrategyNode(node, self.logger)
|
|
105
|
+
for node in self.network.getNodesForStrategy(self.strategyId)
|
|
106
|
+
]
|
|
107
|
+
return self._nodes
|
|
108
|
+
return self.network.getNodesForStrategy(self.strategyId)
|
|
109
|
+
|
|
110
|
+
def _switchSchedulableWitchIsOff(self, node, cycleDuration, doSwitchOn):
|
|
111
|
+
"""
|
|
112
|
+
Like switchSchedulable() but for node off and switchable
|
|
113
|
+
"""
|
|
114
|
+
del cycleDuration
|
|
115
|
+
if doSwitchOn and node.getSchedule().duration>0:
|
|
116
|
+
marginPower = node.network.getMarginPowerOn()
|
|
117
|
+
if node.getMaxPower()>marginPower:
|
|
118
|
+
self.logger.info(
|
|
119
|
+
"Not enough power margin (%d) to switch on '%s' witch need %d Kw.",
|
|
120
|
+
marginPower, node.id, node.getMaxPower())
|
|
121
|
+
return False
|
|
122
|
+
if not node.isActivate():
|
|
123
|
+
self.logger.info(
|
|
124
|
+
"Can't switch on '%s' due to deactivation for margin power security.",
|
|
125
|
+
node.id)
|
|
126
|
+
return False
|
|
127
|
+
if node.switchOn(True):
|
|
128
|
+
self.logger.info("Switch on '%s' successfully.", node.id)
|
|
129
|
+
return True
|
|
130
|
+
self.logger.warning("Fail switch on '%s'.", node.id)
|
|
131
|
+
else:
|
|
132
|
+
self.logger.debug("Node '%s' is off.",
|
|
133
|
+
node.id)
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
def switchSchedulable(self, node, cycleDuration, doSwitchOn):
|
|
137
|
+
"""
|
|
138
|
+
param node: Node to switch on
|
|
139
|
+
param doSwitchOn: Set if we want to switch on or off
|
|
140
|
+
return: True if node is on
|
|
141
|
+
"""
|
|
142
|
+
if node.isSwitchable:
|
|
143
|
+
if node.isOn():
|
|
144
|
+
remainingTime = node.getSchedule().decreaseTime(cycleDuration)
|
|
145
|
+
if remainingTime==0 or not doSwitchOn:
|
|
146
|
+
self.logger.info("Switch off '%s' due to %s.",
|
|
147
|
+
node.id, "elapsed time" if remainingTime==0 else "strategy")
|
|
148
|
+
if node.switchOn(False):
|
|
149
|
+
self.logger.warning("Fail switch off '%s'.", node.id)
|
|
150
|
+
return True
|
|
151
|
+
else:
|
|
152
|
+
self.logger.debug("Node %s isOn for %s more seconds", \
|
|
153
|
+
node.id, remainingTime)
|
|
154
|
+
return True
|
|
155
|
+
else:
|
|
156
|
+
return self._switchSchedulableWitchIsOff(node, cycleDuration, doSwitchOn)
|
|
157
|
+
else:
|
|
158
|
+
self.logger.debug("switchOn() : Node is not switchable : %s.", node.id)
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
def switchOffAll(self, cycleDuration=1):
|
|
162
|
+
"""
|
|
163
|
+
Switch of all connected devices with this strategy.
|
|
164
|
+
"""
|
|
165
|
+
self.logger.debug("EnergyStrategy.switchOffAll(%s)", self.strategyId)
|
|
166
|
+
ok = True
|
|
167
|
+
for elem in self.getNodes():
|
|
168
|
+
# self.logger.debug("Switch off '%s'", elem.id)
|
|
169
|
+
if elem.isSwitchable and self.switchSchedulable(elem, cycleDuration, False):
|
|
170
|
+
self.logger.warning("Fail to switch off '%s'",elem.id)
|
|
171
|
+
ok = False
|
|
172
|
+
return ok
|
|
173
|
+
|
|
174
|
+
def getSchedulableNodes(self):
|
|
175
|
+
"""
|
|
176
|
+
Return the list of nodes that should be scheduled by the user, using the HTTP UI
|
|
177
|
+
to start (or stop) the device
|
|
178
|
+
"""
|
|
179
|
+
return self.getNodes()
|
|
180
|
+
|
|
181
|
+
def getDeferrable(self, node, durationInSecs:int):
|
|
182
|
+
"""
|
|
183
|
+
return: a Deferable device witch can be encapsulated if usefull
|
|
184
|
+
This function can be overload.
|
|
185
|
+
"""
|
|
186
|
+
return DefaultDeferrable(node, durationInSecs)
|
|
187
|
+
|
|
188
|
+
def updateDeferables(self):
|
|
189
|
+
"""
|
|
190
|
+
Update scheduled devices according to emhass
|
|
191
|
+
to scheduled devices according to openhems
|
|
192
|
+
Return true if schedule has been updated
|
|
193
|
+
"""
|
|
194
|
+
# self.logger.debug("EnergyStrategy.updateDeferables()")
|
|
195
|
+
update = False
|
|
196
|
+
self.deferables = {}
|
|
197
|
+
for node in self.getNodes():
|
|
198
|
+
nodeId = node.id
|
|
199
|
+
durationInSecs = node.getSchedule().duration
|
|
200
|
+
deferable = self.deferables.get(nodeId, None)
|
|
201
|
+
if deferable is None:
|
|
202
|
+
if durationInSecs>0: # Add a new deferrable
|
|
203
|
+
update = True
|
|
204
|
+
self.deferables[nodeId] = self.getDeferrable(node, durationInSecs)
|
|
205
|
+
else:
|
|
206
|
+
if durationInSecs<=0: # Remove a deferrable
|
|
207
|
+
del self.deferables[nodeId]
|
|
208
|
+
update = True
|
|
209
|
+
elif deferable.getDuration()!=durationInSecs: # update a deferrable
|
|
210
|
+
update = True
|
|
211
|
+
deferable.setDuration(durationInSecs)
|
|
212
|
+
self.logger.debug("EnergyStrategy.updateDeferables() => %s : %s", update, self.deferables)
|
|
213
|
+
return update
|
|
214
|
+
|
|
215
|
+
def check(self, now=None):
|
|
216
|
+
"""
|
|
217
|
+
Check and eval if necessary
|
|
218
|
+
- EMHASS optimization
|
|
219
|
+
- power margin
|
|
220
|
+
- conformity to EMHASS plan
|
|
221
|
+
"""
|
|
222
|
+
# self.logger.debug("EnergyStrategy.check()")
|
|
223
|
+
if now is None:
|
|
224
|
+
now = datetime.datetime.now()
|
|
225
|
+
if self.updateDeferables() or now>self.nextEvalDate:
|
|
226
|
+
# self.logger.debug("EnergyStrategy.check() : eval")
|
|
227
|
+
self.eval()
|
|
228
|
+
self.nextEvalDate = datetime.datetime.now() + self.evalFrequence
|
|
229
|
+
|
|
230
|
+
def eval(self):
|
|
231
|
+
"""
|
|
232
|
+
This function must be overload
|
|
233
|
+
"""
|
|
234
|
+
self.logger.debug("EnergyStrategy.eval() must be overload")
|
|
235
|
+
|
|
236
|
+
def apply(self, cycleDuration, now):
|
|
237
|
+
"""
|
|
238
|
+
This function must be overload
|
|
239
|
+
"""
|
|
240
|
+
del cycleDuration, now
|
|
241
|
+
self.logger.debug("EnergyStrategy.apply() must be overload")
|
|
242
|
+
|
|
243
|
+
def updateNetwork(self, cycleDuration, now=None):
|
|
244
|
+
"""
|
|
245
|
+
Generic function to updateNetwork base on algorythm
|
|
246
|
+
In that case, sub-strategy must implement :
|
|
247
|
+
- eval()
|
|
248
|
+
- apply(cycleDuration, now)
|
|
249
|
+
"""
|
|
250
|
+
if now is None:
|
|
251
|
+
now = datetime.datetime.now()
|
|
252
|
+
self.check(now)
|
|
253
|
+
self.apply(cycleDuration, now=now)
|
|
254
|
+
return cycleDuration
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def switchOnMax(self, cycleDuration):
|
|
258
|
+
"""
|
|
259
|
+
Switch on nodes, but
|
|
260
|
+
- If there is no margin to switch on, do nothing.
|
|
261
|
+
- Only one (To be sure to not switch on to much devices)
|
|
262
|
+
"""
|
|
263
|
+
self.logger.info("%s.switchOnMax()", self.strategyId)
|
|
264
|
+
marginPower = self.network.getMarginPowerOn()
|
|
265
|
+
if marginPower<=0:
|
|
266
|
+
self.logger.info("Can't switch on devices: not enough power margin : %s", marginPower)
|
|
267
|
+
return True
|
|
268
|
+
for elem in self.getNodes(True):
|
|
269
|
+
switchOn = self.switchSchedulable(elem.node, cycleDuration, True)
|
|
270
|
+
if switchOn and elem.changed(switchOn):
|
|
271
|
+
self.logger.info("Switch on just one device at each loop to ensure Network constraint.")
|
|
272
|
+
return True
|
|
273
|
+
return False
|
{openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/hybridinverter_strategy.py
RENAMED
|
@@ -6,6 +6,8 @@ Case dual-source managed by controlled "hybrid inverter with security mode" :
|
|
|
6
6
|
Strategy is to minimize load/unload battery and minimize grid consumption
|
|
7
7
|
Advantages :
|
|
8
8
|
Disadvantages :
|
|
9
|
+
|
|
10
|
+
TODO : Implemented - Call - Conf - TestAuto - RunOk - InProd : 6/6
|
|
9
11
|
"""
|
|
10
12
|
|
|
11
13
|
import logging
|
|
@@ -23,14 +25,14 @@ class HybridInverterStrategy(SolarBasedStrategy):
|
|
|
23
25
|
Disadvantages :
|
|
24
26
|
"""
|
|
25
27
|
|
|
26
|
-
def __init__(self, network:
|
|
27
|
-
|
|
28
|
-
super().__init__(network, geoposition)
|
|
28
|
+
def __init__(self, strategyId:str, network:OpenHEMSNetwork, geoposition:GeoPosition):
|
|
29
|
+
super().__init__(strategyId, network, geoposition)
|
|
29
30
|
logging.getLogger("HybridInverterStrategy")\
|
|
30
31
|
.error("SolarOnlyProductionStrategy() : TODO")
|
|
31
32
|
# TODO
|
|
32
33
|
|
|
33
|
-
def updateNetwork(self, cycleDuration:int,
|
|
34
|
+
def updateNetwork(self, cycleDuration:int, now=None):
|
|
35
|
+
del cycleDuration, now
|
|
34
36
|
logging.getLogger("HybridInverterStrategy")\
|
|
35
37
|
.error("SolarOnlyProductionStrategy.updateNetwork() : TODO")
|
|
36
38
|
# TODO
|