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.
Files changed (131) hide show
  1. {openhems-0.2.2/src/openhems.egg-info → openhems-0.2.4}/PKG-INFO +1 -1
  2. {openhems-0.2.2 → openhems-0.2.4}/pyproject.toml +1 -1
  3. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/data/openhems_default.yaml +19 -2
  4. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/data/openhems_tooltips_en.yaml +57 -15
  5. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/data/openhems_tooltips_fr.yaml +3 -2
  6. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/main.py +17 -12
  7. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/contract/generic_contract.py +32 -12
  8. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/contract/rte_contract.py +6 -2
  9. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/__init__.py +2 -0
  10. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/driver/emhass_adapter.py +26 -5
  11. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/emhass_strategy.py +19 -81
  12. openhems-0.2.4/src/openhems/modules/energy_strategy/energy_strategy.py +273 -0
  13. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/hybridinverter_strategy.py +6 -4
  14. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/offgrid_strategy.py +6 -5
  15. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/offpeak_strategy.py +9 -29
  16. openhems-0.2.4/src/openhems/modules/energy_strategy/simulated_annealing_algo.py +372 -0
  17. openhems-0.2.4/src/openhems/modules/energy_strategy/simulated_annealing_strategy.py +119 -0
  18. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/solarbased_strategy.py +24 -16
  19. openhems-0.2.4/src/openhems/modules/energy_strategy/solarnosell_strategy.py +110 -0
  20. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/sourceinverter_strategy.py +12 -7
  21. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/energy_strategy/switchoff_strategy.py +11 -16
  22. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/network/driver/home_assistant_api.py +22 -1
  23. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/network/feeder.py +17 -14
  24. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/network/homestate_updater.py +17 -7
  25. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/network/network.py +21 -15
  26. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/network/node.py +136 -33
  27. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/util/cast_utility.py +5 -1
  28. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/js/params.js +50 -35
  29. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/web.py +9 -2
  30. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/server.py +20 -4
  31. {openhems-0.2.2 → openhems-0.2.4/src/openhems.egg-info}/PKG-INFO +1 -1
  32. {openhems-0.2.2 → openhems-0.2.4}/src/openhems.egg-info/SOURCES.txt +3 -2
  33. openhems-0.2.4/tests/test_annealing_strategy.py +48 -0
  34. {openhems-0.2.2 → openhems-0.2.4}/tests/test_contract_module.py +2 -1
  35. openhems-0.2.4/tests/test_offpeak_strategy.py +92 -0
  36. {openhems-0.2.2 → openhems-0.2.4}/tests/test_util_module.py +2 -2
  37. openhems-0.2.2/src/openhems/modules/energy_strategy/energy_strategy.py +0 -153
  38. openhems-0.2.2/src/openhems/modules/energy_strategy/solarnosell_strategy.py +0 -34
  39. openhems-0.2.2/tests/test_offpeak_strategy.py +0 -24
  40. openhems-0.2.2/tests/test_openhems.py +0 -36
  41. openhems-0.2.2/tests/test_server.py +0 -69
  42. {openhems-0.2.2 → openhems-0.2.4}/LICENSE +0 -0
  43. {openhems-0.2.2 → openhems-0.2.4}/MANIFEST.in +0 -0
  44. {openhems-0.2.2 → openhems-0.2.4}/emhass/config_emhass.yaml +0 -0
  45. {openhems-0.2.2 → openhems-0.2.4}/emhass/secrets_emhass.yaml +0 -0
  46. {openhems-0.2.2 → openhems-0.2.4}/readme.md +0 -0
  47. {openhems-0.2.2 → openhems-0.2.4}/setup.cfg +0 -0
  48. {openhems-0.2.2 → openhems-0.2.4}/setup.py +0 -0
  49. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/__init__.py +0 -0
  50. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/check_homeassistant_api.py +0 -0
  51. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/data/keys_en.yaml +0 -0
  52. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/data/keys_fr.yaml +0 -0
  53. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/data/openhems_default_docker.yaml +0 -0
  54. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/data_load_cost_forecast.csv +0 -0
  55. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/data_load_forecast.csv +0 -0
  56. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/data_prod_price_forecast.csv +0 -0
  57. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/data_train_load_clustering.pkl +0 -0
  58. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/data_train_load_forecast.pkl +0 -0
  59. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/data_weather_forecast.csv +0 -0
  60. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/heating_prediction.csv +0 -0
  61. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/opt_res_latest.csv +0 -0
  62. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/opt_res_perfect_optim_cost.csv +0 -0
  63. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/opt_res_perfect_optim_profit.csv +0 -0
  64. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/opt_res_perfect_optim_self-consumption.csv +0 -0
  65. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/test_df_final.pkl +0 -0
  66. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/test_response_get_data_get_method.pbz2 +0 -0
  67. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/test_response_scrapper_get_method.pbz2 +0 -0
  68. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/test_response_solarforecast_get_method.pbz2 +0 -0
  69. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/data/test_response_solcast_get_method.pbz2 +0 -0
  70. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/docs/conf.py +0 -0
  71. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/options.json +0 -0
  72. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/load_clustering.py +0 -0
  73. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/load_forecast_sklearn.py +0 -0
  74. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/optim_results_analysis.py +0 -0
  75. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/read_csv_plot_data.py +0 -0
  76. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/save_pvlib_module_inverter_database.py +0 -0
  77. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/script_debug_forecasts.py +0 -0
  78. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/script_debug_optim.py +0 -0
  79. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/script_simple_thermal_model.py +0 -0
  80. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/script_thermal_model_optim.py +0 -0
  81. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/special_config_analysis.py +0 -0
  82. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/special_options.json +0 -0
  83. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/scripts/use_cases_analysis.py +0 -0
  84. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/__init__.py +0 -0
  85. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/command_line.py +0 -0
  86. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/data/config_defaults.json +0 -0
  87. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/forecast.py +0 -0
  88. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/machine_learning_forecaster.py +0 -0
  89. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/machine_learning_regressor.py +0 -0
  90. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/optimization.py +0 -0
  91. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/retrieve_hass.py +0 -0
  92. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/static/data/param_definitions.json +0 -0
  93. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/utils.py +0 -0
  94. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/src/emhass/web_server.py +0 -0
  95. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/tests/__init__.py +0 -0
  96. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/tests/test_command_line_utils.py +0 -0
  97. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/tests/test_forecast.py +0 -0
  98. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/tests/test_machine_learning_forecaster.py +0 -0
  99. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/tests/test_machine_learning_regressor.py +0 -0
  100. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/tests/test_optimization.py +0 -0
  101. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/tests/test_retrieve_hass.py +0 -0
  102. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/emhass/tests/test_utils.py +0 -0
  103. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/__init__.py +0 -0
  104. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/contract/__init__.py +0 -0
  105. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/contract/contract.py +0 -0
  106. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/network/__init__.py +0 -0
  107. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/network/driver/fake_network.py +0 -0
  108. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/network/network_helper.py +0 -0
  109. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/util/__init__.py +0 -0
  110. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/util/configuration_manager.py +0 -0
  111. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/util/notification_manager.py +0 -0
  112. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/util/time.py +0 -0
  113. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/__init__.py +0 -0
  114. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/css/openhems.css +0 -0
  115. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/driver_vpn.py +0 -0
  116. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/img/alarm.svg +0 -0
  117. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/img/correct_32.ico +0 -0
  118. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/img/delete-20px.png +0 -0
  119. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/img/delete.png +0 -0
  120. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/img/favicon.ico +0 -0
  121. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/img/hourglass.svg +0 -0
  122. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/img/save_32.ico +0 -0
  123. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/img/wait.gif +0 -0
  124. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/schedule.py +0 -0
  125. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/templates/panel.jinja2 +0 -0
  126. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/modules/web/templates/params.framework.jinja2 +0 -0
  127. {openhems-0.2.2 → openhems-0.2.4}/src/openhems/server_vpn.py +0 -0
  128. {openhems-0.2.2 → openhems-0.2.4}/src/openhems.egg-info/dependency_links.txt +0 -0
  129. {openhems-0.2.2 → openhems-0.2.4}/src/openhems.egg-info/requires.txt +0 -0
  130. {openhems-0.2.2 → openhems-0.2.4}/src/openhems.egg-info/top_level.txt +0 -0
  131. {openhems-0.2.2 → openhems-0.2.4}/tests/test_emhass_adapter.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openhems
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A sample Home Energy Managment System based on Home-Assistant
5
5
  Home-page: https://github.com/abriotde/openhems-sample.git
6
6
  Author: OpenHomeSystem
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "openhems"
3
- version = "0.2.2"
3
+ version = "0.2.4"
4
4
  dynamic = [
5
5
  # "version" # , "description"
6
6
  ]
@@ -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: openhems.log # Optional, default is /var/log/openhems.log
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
- hoursRanges: []
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
- offpeakhoursranges: ": ["22h - 6h"]"
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: "["22h - 6h"]"
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: "["22h - 6h"]"
51
- peakprice: ""
52
- offpeakprice: ""
91
+ offpeakhoursranges: []
92
+ defaultPrice: 0.1
93
+ outRangePrice: 1.0
94
+ sellprice: 0.0
53
95
  solarpanel:
54
96
  currentPower: ""
55
97
  maxPower: ""
@@ -48,8 +48,9 @@ default:
48
48
  price: ""
49
49
  generic:
50
50
  offpeakhoursranges: "['22h - 6h']"
51
- peakprice: ""
52
- offpeakprice: ""
51
+ defaultPrice: 0.1
52
+ outRangePrice: 1.0
53
+ sellprice: 0.0
53
54
  solarpanel:
54
55
  currentPower: ""
55
56
  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
- if not inDocker:
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("GenericContract(hoursRanges=%s, defaultCost=%s, outRangeCost=%s)",
21
- str(hoursRanges), defaultPrice, outRangePrice)
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
- baseKey+"."+classname+"."+key,
151
+ completeKey,
137
152
  defaultType
138
153
  )
139
154
  if value is None:
140
- GenericContract.logger.warning(
141
- "No default value for '%s.%s.%s'. Availables are %s",
142
- baseKey, classname, key,
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 : %s.", url)
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
- self.lastColor = self.color.getValue().lower()
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
@@ -7,7 +7,7 @@ import os
7
7
  import logging
8
8
  import json
9
9
  import sys
10
- import dataclasses
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
- duration: float # Duration in hours? (In emhass granularity)
48
- node:str = None
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.duration for d in self.deferables]
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 math
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
- super().__init__(strategyId, network, mylogger, True)
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.data = None
41
- self.deferables = {}
42
- self.deferablesKeys = []
43
- self.nextEvalDate = datetime.now(self.timezone) - self.emhassEvalFrequence
40
+ self._data = None
44
41
 
45
- def emhassEval(self):
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.data = data
67
- self.nextEvalDate = datetime.now(self.timezone) + self.emhassEvalFrequence
63
+ self._data = data
68
64
  return data
69
65
 
70
- def getDurationInHour(self, durationInSecs):
66
+ def getDeferrables(self, node, durationInSecs):
71
67
  """
72
- Return duration in Emhass prevision granularity
68
+ Return a Deferrable representing the node (adding usefull informations for algo).
73
69
  """
74
- # TODO granularity can be less or more than hour...
75
- return math.ceil(durationInSecs / 3600)
76
-
77
- def updateDeferables(self):
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.data is None or isinstance(self.data, bool): # Case no deferables or Error
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.data.iterrows():
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 emhassEval()?
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 emhassApply(self, cycleDuration, now=None):
118
+ def apply(self, cycleDuration, now=None):
166
119
  """
167
- This should apply emhass result (emhassEval call) : self.data
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