openhems 0.2.2__tar.gz → 0.2.4__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.4}/PKG-INFO +1 -1
- {openhems-0.2.2 → openhems-0.2.4}/pyproject.toml +1 -1
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/data/openhems_default.yaml +19 -2
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/data/openhems_tooltips_en.yaml +57 -15
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/data/openhems_tooltips_fr.yaml +3 -2
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/main.py +17 -12
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/contract/generic_contract.py +32 -12
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/contract/rte_contract.py +6 -2
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/__init__.py +2 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/driver/emhass_adapter.py +26 -5
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/emhass_strategy.py +19 -81
- openhems-0.2.4/src/openhems/modules/energy_strategy/energy_strategy.py +273 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/hybridinverter_strategy.py +6 -4
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/offgrid_strategy.py +6 -5
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/offpeak_strategy.py +9 -29
- openhems-0.2.4/src/openhems/modules/energy_strategy/simulated_annealing_algo.py +372 -0
- openhems-0.2.4/src/openhems/modules/energy_strategy/simulated_annealing_strategy.py +119 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/solarbased_strategy.py +24 -16
- openhems-0.2.4/src/openhems/modules/energy_strategy/solarnosell_strategy.py +110 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/sourceinverter_strategy.py +12 -7
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/switchoff_strategy.py +11 -16
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/network/driver/home_assistant_api.py +22 -1
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/network/feeder.py +17 -14
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/network/homestate_updater.py +17 -7
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/network/network.py +21 -15
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/network/node.py +136 -33
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/util/cast_utility.py +5 -1
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/js/params.js +50 -35
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/web.py +9 -2
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/server.py +20 -4
- {openhems-0.2.2 → openhems-0.2.4/src/openhems.egg-info}/PKG-INFO +1 -1
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems.egg-info/SOURCES.txt +3 -2
- openhems-0.2.4/tests/test_annealing_strategy.py +48 -0
- {openhems-0.2.2 → openhems-0.2.4}/tests/test_contract_module.py +2 -1
- openhems-0.2.4/tests/test_offpeak_strategy.py +92 -0
- {openhems-0.2.2 → openhems-0.2.4}/tests/test_util_module.py +2 -2
- 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/tests/test_server.py +0 -69
- {openhems-0.2.2 → openhems-0.2.4}/LICENSE +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/MANIFEST.in +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/emhass/config_emhass.yaml +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/emhass/secrets_emhass.yaml +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/readme.md +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/setup.cfg +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/setup.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/__init__.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/check_homeassistant_api.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/data/keys_en.yaml +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/data/keys_fr.yaml +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/data/openhems_default_docker.yaml +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/data_load_cost_forecast.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/data_load_forecast.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/data_prod_price_forecast.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/data_train_load_clustering.pkl +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/data_train_load_forecast.pkl +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/data_weather_forecast.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/heating_prediction.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/opt_res_latest.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/opt_res_perfect_optim_cost.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/opt_res_perfect_optim_profit.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/opt_res_perfect_optim_self-consumption.csv +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/test_df_final.pkl +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/test_response_get_data_get_method.pbz2 +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/test_response_scrapper_get_method.pbz2 +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/test_response_solarforecast_get_method.pbz2 +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/test_response_solcast_get_method.pbz2 +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/docs/conf.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/options.json +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/load_clustering.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/load_forecast_sklearn.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/optim_results_analysis.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/read_csv_plot_data.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/save_pvlib_module_inverter_database.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/script_debug_forecasts.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/script_debug_optim.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/script_simple_thermal_model.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/script_thermal_model_optim.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/special_config_analysis.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/special_options.json +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/use_cases_analysis.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/__init__.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/command_line.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/data/config_defaults.json +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/forecast.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/machine_learning_forecaster.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/machine_learning_regressor.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/optimization.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/retrieve_hass.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/static/data/param_definitions.json +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/utils.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/web_server.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/tests/__init__.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/tests/test_command_line_utils.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/tests/test_forecast.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/tests/test_machine_learning_forecaster.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/tests/test_machine_learning_regressor.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/tests/test_optimization.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/tests/test_retrieve_hass.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/tests/test_utils.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/__init__.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/contract/__init__.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/contract/contract.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/network/__init__.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/network/driver/fake_network.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/network/network_helper.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/util/__init__.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/util/configuration_manager.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/util/notification_manager.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/util/time.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/__init__.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/css/openhems.css +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/driver_vpn.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/img/alarm.svg +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/img/correct_32.ico +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/img/delete-20px.png +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/img/delete.png +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/img/favicon.ico +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/img/hourglass.svg +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/img/save_32.ico +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/img/wait.gif +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/schedule.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/templates/panel.jinja2 +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/templates/params.framework.jinja2 +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems/server_vpn.py +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems.egg-info/dependency_links.txt +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems.egg-info/requires.txt +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/src/openhems.egg-info/top_level.txt +0 -0
- {openhems-0.2.2 → openhems-0.2.4}/tests/test_emhass_adapter.py +0 -0
|
@@ -13,7 +13,7 @@ server:
|
|
|
13
13
|
inDocker: False
|
|
14
14
|
port: 8000
|
|
15
15
|
logformat: "%(levelname)s : %(asctime)s : %(message)s" # for custom configuration see https://docs.python.org/3/library/logging.html#logrecord-attributes
|
|
16
|
-
logfile:
|
|
16
|
+
logfile: "" # set a log file. When "", there is no logfile (only STDOUT)
|
|
17
17
|
loglevel: info # Optional, default is info, availables are debug / info / warn / error / critical / no
|
|
18
18
|
loopDelay: 30 # interval beetween 2 loop
|
|
19
19
|
network: homeassistant # Define the type of network API used to control the home energy.
|
|
@@ -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
|
|
@@ -77,9 +93,10 @@ default:
|
|
|
77
93
|
rtetarifbleu:
|
|
78
94
|
price: 0.2516
|
|
79
95
|
generic:
|
|
80
|
-
|
|
96
|
+
offpeakhoursranges: []
|
|
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
|
|
@@ -22,6 +22,46 @@ network:
|
|
|
22
22
|
nodes: "Configure your home nodes"
|
|
23
23
|
# All default values.
|
|
24
24
|
default:
|
|
25
|
+
strategy:
|
|
26
|
+
emhass:
|
|
27
|
+
freq: 30
|
|
28
|
+
days_to_retrieve: 2
|
|
29
|
+
method_ts_round: nearest
|
|
30
|
+
delta_forecast: 1
|
|
31
|
+
weather_forecast_method: scrapper
|
|
32
|
+
load_cost_hp: 0.30
|
|
33
|
+
load_cost_hc: 0.15
|
|
34
|
+
prod_sell_price: 0.0
|
|
35
|
+
set_total_pv_sell: False
|
|
36
|
+
lp_solver: default
|
|
37
|
+
lp_solver_path: empty
|
|
38
|
+
set_nocharge_from_grid: False
|
|
39
|
+
set_nodischarge_to_grid: True
|
|
40
|
+
set_battery_dynamic: False
|
|
41
|
+
battery_dynamic_max: 0.9
|
|
42
|
+
battery_dynamic_min: -0.9
|
|
43
|
+
offpeak:
|
|
44
|
+
offpeakhours: None
|
|
45
|
+
switchoff:
|
|
46
|
+
reverse: False
|
|
47
|
+
offhours: []
|
|
48
|
+
offconditions: False
|
|
49
|
+
annealing:
|
|
50
|
+
freq: 15
|
|
51
|
+
max_iteration_number: 1000
|
|
52
|
+
initial_temp: 1000
|
|
53
|
+
cooling_factor: 0.95
|
|
54
|
+
min_temp: 0.1
|
|
55
|
+
max_no_improve: 100
|
|
56
|
+
nosell:
|
|
57
|
+
ratio: -1
|
|
58
|
+
margin: 300
|
|
59
|
+
nobuy:
|
|
60
|
+
ratio: 1
|
|
61
|
+
margin: 300
|
|
62
|
+
ratiosellbuy:
|
|
63
|
+
ratio: 0
|
|
64
|
+
margin: 200
|
|
25
65
|
node:
|
|
26
66
|
publicpowergrid:
|
|
27
67
|
currentPower: ""
|
|
@@ -30,26 +70,28 @@ default:
|
|
|
30
70
|
marginPower: ""
|
|
31
71
|
contract:
|
|
32
72
|
rtetempo:
|
|
33
|
-
color:
|
|
34
|
-
|
|
73
|
+
color: null
|
|
74
|
+
nextcolor: null
|
|
75
|
+
offpeakhoursranges: ["22h - 6h"]
|
|
35
76
|
peakprice:
|
|
36
|
-
bleu:
|
|
37
|
-
blanc:
|
|
38
|
-
rouge:
|
|
77
|
+
bleu: 0.1609
|
|
78
|
+
blanc: 0.1894
|
|
79
|
+
rouge: 0.7562
|
|
39
80
|
offpeakprice:
|
|
40
|
-
bleu:
|
|
41
|
-
blanc:
|
|
42
|
-
rouge:
|
|
81
|
+
bleu: 0.1296
|
|
82
|
+
blanc: 0.1486
|
|
83
|
+
rouge: 0.1568
|
|
43
84
|
rteheurescreuses:
|
|
44
|
-
offpeakhoursranges:
|
|
45
|
-
peakprice:
|
|
46
|
-
offpeakprice:
|
|
85
|
+
offpeakhoursranges: ["22h - 6h"]
|
|
86
|
+
peakprice: 0.2700
|
|
87
|
+
offpeakprice: 0.2068
|
|
47
88
|
rtetarifbleu:
|
|
48
|
-
price:
|
|
89
|
+
price: 0.2516
|
|
49
90
|
generic:
|
|
50
|
-
offpeakhoursranges:
|
|
51
|
-
|
|
52
|
-
|
|
91
|
+
offpeakhoursranges: []
|
|
92
|
+
defaultPrice: 0.1
|
|
93
|
+
outRangePrice: 1.0
|
|
94
|
+
sellprice: 0.0
|
|
53
95
|
solarpanel:
|
|
54
96
|
currentPower: ""
|
|
55
97
|
maxPower: ""
|
|
@@ -13,6 +13,7 @@ from datetime import datetime
|
|
|
13
13
|
from threading import Thread
|
|
14
14
|
import argparse
|
|
15
15
|
from pathlib import Path
|
|
16
|
+
from requests.exceptions import ConnectTimeout
|
|
16
17
|
openhemsPath = Path(__file__).parents[1]
|
|
17
18
|
sys.path.append(str(openhemsPath))
|
|
18
19
|
# pylint: disable=wrong-import-position
|
|
@@ -53,7 +54,11 @@ class OpenHEMSApplication:
|
|
|
53
54
|
myHandlers = []
|
|
54
55
|
fileHandler = None
|
|
55
56
|
formatter = logging.Formatter(logformat)
|
|
56
|
-
|
|
57
|
+
# Case wrong logfile path : set to empty : no logging file
|
|
58
|
+
logfileparents = Path(logfile).parents
|
|
59
|
+
if len(logfileparents)==0 or not logfileparents[0].is_dir():
|
|
60
|
+
logfile = "" # No log file
|
|
61
|
+
if not inDocker and logfile!="":
|
|
57
62
|
fileHandler = handlers.TimedRotatingFileHandler(filename=logfile,
|
|
58
63
|
when='D',
|
|
59
64
|
interval=1,
|
|
@@ -82,7 +87,7 @@ class OpenHEMSApplication:
|
|
|
82
87
|
Load YAML configuration, over load it with a secret file if exists.
|
|
83
88
|
Return a "Configurator"
|
|
84
89
|
"""
|
|
85
|
-
print("Load YAML configuration from '",yamlConfFilepath,"'")
|
|
90
|
+
# print("Load YAML configuration from '",yamlConfFilepath,"'")
|
|
86
91
|
path = Path(yamlConfFilepath)
|
|
87
92
|
configurator.addYamlConfig(path)
|
|
88
93
|
if path.suffix!="":
|
|
@@ -90,24 +95,24 @@ class OpenHEMSApplication:
|
|
|
90
95
|
secretPath = str(yamlConfFilepath).replace(path.suffix, ".secret"+path.suffix)
|
|
91
96
|
path = Path(secretPath)
|
|
92
97
|
if path.is_file():
|
|
93
|
-
print("Over load YAML configuration with '",str(path),"'")
|
|
98
|
+
# print("Over load YAML configuration with '",str(path),"'")
|
|
94
99
|
configurator.addYamlConfig(path)
|
|
95
|
-
else:
|
|
96
|
-
print("No '",str(path),"'")
|
|
100
|
+
# else: print("No '",str(path),"'")
|
|
97
101
|
configurator.completeWithDefaults()
|
|
98
102
|
return configurator
|
|
99
103
|
|
|
100
104
|
def __init__(self, yamlConfFilepath:str, *, port=0, logfilepath='', inDocker=False):
|
|
101
105
|
# Temporary logger
|
|
102
106
|
self.logger = logging.getLogger(__name__)
|
|
103
|
-
warnings
|
|
107
|
+
# Keep warnings for tests (self.server can be None if it raise Exception).
|
|
108
|
+
self.warnings = []
|
|
104
109
|
network = None
|
|
105
110
|
schedule = []
|
|
106
111
|
configurator = ConfigurationManager(self.logger)
|
|
107
112
|
try:
|
|
108
113
|
configurator = self.loadYamlConfiguration(configurator, yamlConfFilepath)
|
|
109
114
|
except ConfigurationException as e:
|
|
110
|
-
warnings.append(str(e))
|
|
115
|
+
self.warnings.append(str(e))
|
|
111
116
|
loglevel = configurator.get("server.loglevel")
|
|
112
117
|
logformat = configurator.get("server.logformat")
|
|
113
118
|
logfile = logfilepath if logfilepath!='' else configurator.get("server.logfile")
|
|
@@ -116,19 +121,19 @@ class OpenHEMSApplication:
|
|
|
116
121
|
self.server = None
|
|
117
122
|
try:
|
|
118
123
|
network = network_helper.getNetworkFromConfiguration(self.logger, configurator)
|
|
119
|
-
warnings = warnings + network.getWarningMessages()
|
|
124
|
+
self.warnings = self.warnings + network.getWarningMessages()
|
|
120
125
|
self.server = OpenHEMSServer(self.logger, network, configurator)
|
|
121
126
|
schedule = self.server.getSchedule()
|
|
122
|
-
except ConfigurationException as e:
|
|
127
|
+
except (ConfigurationException, ConnectTimeout) as e:
|
|
123
128
|
self.logger.error(str(e))
|
|
124
|
-
warnings.append(str(e))
|
|
125
|
-
for warning in warnings:
|
|
129
|
+
self.warnings.append(str(e))
|
|
130
|
+
for warning in self.warnings:
|
|
126
131
|
self.logger.error(warning)
|
|
127
132
|
port = port if port>0 else configurator.get("server.port")
|
|
128
133
|
port = CastUtililty.toTypeInt(port)
|
|
129
134
|
root = configurator.get("server.htmlRoot")
|
|
130
135
|
inDocker = inDocker or configurator.get("server.inDocker", "bool")
|
|
131
|
-
self.webserver = OpenhemsHTTPServer(self.logger, schedule, warnings,
|
|
136
|
+
self.webserver = OpenhemsHTTPServer(self.logger, schedule, self.warnings,
|
|
132
137
|
port=port, htmlRoot=root, inDocker=inDocker, configurator=configurator)
|
|
133
138
|
if network is not None:
|
|
134
139
|
self.logger.info("OpenHEMS loaded.")
|
|
@@ -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'})
|
|
@@ -160,7 +160,11 @@ class RTETempoContract(RTEContract):
|
|
|
160
160
|
curCall = now.strftime("%Y%m%d%H")
|
|
161
161
|
if self.lastCall!=curCall:
|
|
162
162
|
if self.color is not None:
|
|
163
|
-
|
|
163
|
+
color = self.color.getValue()
|
|
164
|
+
if color is None or not isinstance(color, str):
|
|
165
|
+
self.logger.warning("RTETempoContract.getCurColor() : color is None, use the API")
|
|
166
|
+
color = self.callApiRteTempo("today")
|
|
167
|
+
self.lastColor = color.lower()
|
|
164
168
|
else:
|
|
165
169
|
self.lastColor = self.callApiRteTempo("today")
|
|
166
170
|
self.lastCall = curCall
|
|
@@ -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.4}/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
|