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.
Files changed (130) hide show
  1. {openhems-0.2.2/src/openhems.egg-info → openhems-0.2.3}/PKG-INFO +1 -1
  2. {openhems-0.2.2 → openhems-0.2.3}/pyproject.toml +1 -1
  3. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/data/openhems_default.yaml +17 -0
  4. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/contract/generic_contract.py +32 -12
  5. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/contract/rte_contract.py +1 -1
  6. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/__init__.py +2 -0
  7. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/driver/emhass_adapter.py +26 -5
  8. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/emhass_strategy.py +19 -81
  9. openhems-0.2.3/src/openhems/modules/energy_strategy/energy_strategy.py +273 -0
  10. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/hybridinverter_strategy.py +6 -4
  11. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/offgrid_strategy.py +6 -5
  12. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/offpeak_strategy.py +9 -29
  13. openhems-0.2.3/src/openhems/modules/energy_strategy/simulated_annealing_algo.py +372 -0
  14. openhems-0.2.3/src/openhems/modules/energy_strategy/simulated_annealing_strategy.py +119 -0
  15. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/solarbased_strategy.py +24 -16
  16. openhems-0.2.3/src/openhems/modules/energy_strategy/solarnosell_strategy.py +111 -0
  17. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/sourceinverter_strategy.py +11 -6
  18. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/energy_strategy/switchoff_strategy.py +11 -16
  19. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/network/feeder.py +4 -4
  20. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/network/network.py +20 -8
  21. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/network/node.py +110 -15
  22. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/server.py +20 -4
  23. {openhems-0.2.2 → openhems-0.2.3/src/openhems.egg-info}/PKG-INFO +1 -1
  24. {openhems-0.2.2 → openhems-0.2.3}/src/openhems.egg-info/SOURCES.txt +3 -2
  25. openhems-0.2.3/tests/test_annealing_strategy.py +48 -0
  26. {openhems-0.2.2 → openhems-0.2.3}/tests/test_contract_module.py +2 -1
  27. openhems-0.2.2/tests/test_server.py → openhems-0.2.3/tests/test_offpeak_strategy.py +27 -31
  28. openhems-0.2.2/src/openhems/modules/energy_strategy/energy_strategy.py +0 -153
  29. openhems-0.2.2/src/openhems/modules/energy_strategy/solarnosell_strategy.py +0 -34
  30. openhems-0.2.2/tests/test_offpeak_strategy.py +0 -24
  31. openhems-0.2.2/tests/test_openhems.py +0 -36
  32. {openhems-0.2.2 → openhems-0.2.3}/LICENSE +0 -0
  33. {openhems-0.2.2 → openhems-0.2.3}/MANIFEST.in +0 -0
  34. {openhems-0.2.2 → openhems-0.2.3}/emhass/config_emhass.yaml +0 -0
  35. {openhems-0.2.2 → openhems-0.2.3}/emhass/secrets_emhass.yaml +0 -0
  36. {openhems-0.2.2 → openhems-0.2.3}/readme.md +0 -0
  37. {openhems-0.2.2 → openhems-0.2.3}/setup.cfg +0 -0
  38. {openhems-0.2.2 → openhems-0.2.3}/setup.py +0 -0
  39. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/__init__.py +0 -0
  40. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/check_homeassistant_api.py +0 -0
  41. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/data/keys_en.yaml +0 -0
  42. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/data/keys_fr.yaml +0 -0
  43. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/data/openhems_default_docker.yaml +0 -0
  44. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/data/openhems_tooltips_en.yaml +0 -0
  45. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/data/openhems_tooltips_fr.yaml +0 -0
  46. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/data_load_cost_forecast.csv +0 -0
  47. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/data_load_forecast.csv +0 -0
  48. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/data_prod_price_forecast.csv +0 -0
  49. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/data_train_load_clustering.pkl +0 -0
  50. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/data_train_load_forecast.pkl +0 -0
  51. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/data_weather_forecast.csv +0 -0
  52. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/heating_prediction.csv +0 -0
  53. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/opt_res_latest.csv +0 -0
  54. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/opt_res_perfect_optim_cost.csv +0 -0
  55. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/opt_res_perfect_optim_profit.csv +0 -0
  56. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/opt_res_perfect_optim_self-consumption.csv +0 -0
  57. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/test_df_final.pkl +0 -0
  58. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/test_response_get_data_get_method.pbz2 +0 -0
  59. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/test_response_scrapper_get_method.pbz2 +0 -0
  60. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/test_response_solarforecast_get_method.pbz2 +0 -0
  61. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/data/test_response_solcast_get_method.pbz2 +0 -0
  62. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/docs/conf.py +0 -0
  63. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/options.json +0 -0
  64. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/load_clustering.py +0 -0
  65. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/load_forecast_sklearn.py +0 -0
  66. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/optim_results_analysis.py +0 -0
  67. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/read_csv_plot_data.py +0 -0
  68. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/save_pvlib_module_inverter_database.py +0 -0
  69. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/script_debug_forecasts.py +0 -0
  70. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/script_debug_optim.py +0 -0
  71. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/script_simple_thermal_model.py +0 -0
  72. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/script_thermal_model_optim.py +0 -0
  73. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/special_config_analysis.py +0 -0
  74. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/special_options.json +0 -0
  75. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/scripts/use_cases_analysis.py +0 -0
  76. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/__init__.py +0 -0
  77. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/command_line.py +0 -0
  78. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/data/config_defaults.json +0 -0
  79. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/forecast.py +0 -0
  80. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/machine_learning_forecaster.py +0 -0
  81. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/machine_learning_regressor.py +0 -0
  82. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/optimization.py +0 -0
  83. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/retrieve_hass.py +0 -0
  84. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/static/data/param_definitions.json +0 -0
  85. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/utils.py +0 -0
  86. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/src/emhass/web_server.py +0 -0
  87. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/tests/__init__.py +0 -0
  88. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/tests/test_command_line_utils.py +0 -0
  89. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/tests/test_forecast.py +0 -0
  90. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/tests/test_machine_learning_forecaster.py +0 -0
  91. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/tests/test_machine_learning_regressor.py +0 -0
  92. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/tests/test_optimization.py +0 -0
  93. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/tests/test_retrieve_hass.py +0 -0
  94. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/emhass/tests/test_utils.py +0 -0
  95. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/main.py +0 -0
  96. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/__init__.py +0 -0
  97. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/contract/__init__.py +0 -0
  98. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/contract/contract.py +0 -0
  99. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/network/__init__.py +0 -0
  100. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/network/driver/fake_network.py +0 -0
  101. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/network/driver/home_assistant_api.py +0 -0
  102. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/network/homestate_updater.py +0 -0
  103. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/network/network_helper.py +0 -0
  104. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/util/__init__.py +0 -0
  105. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/util/cast_utility.py +0 -0
  106. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/util/configuration_manager.py +0 -0
  107. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/util/notification_manager.py +0 -0
  108. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/util/time.py +0 -0
  109. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/__init__.py +0 -0
  110. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/css/openhems.css +0 -0
  111. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/driver_vpn.py +0 -0
  112. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/img/alarm.svg +0 -0
  113. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/img/correct_32.ico +0 -0
  114. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/img/delete-20px.png +0 -0
  115. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/img/delete.png +0 -0
  116. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/img/favicon.ico +0 -0
  117. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/img/hourglass.svg +0 -0
  118. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/img/save_32.ico +0 -0
  119. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/img/wait.gif +0 -0
  120. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/js/params.js +0 -0
  121. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/schedule.py +0 -0
  122. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/templates/panel.jinja2 +0 -0
  123. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/templates/params.framework.jinja2 +0 -0
  124. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/modules/web/web.py +0 -0
  125. {openhems-0.2.2 → openhems-0.2.3}/src/openhems/server_vpn.py +0 -0
  126. {openhems-0.2.2 → openhems-0.2.3}/src/openhems.egg-info/dependency_links.txt +0 -0
  127. {openhems-0.2.2 → openhems-0.2.3}/src/openhems.egg-info/requires.txt +0 -0
  128. {openhems-0.2.2 → openhems-0.2.3}/src/openhems.egg-info/top_level.txt +0 -0
  129. {openhems-0.2.2 → openhems-0.2.3}/tests/test_emhass_adapter.py +0 -0
  130. {openhems-0.2.2 → openhems-0.2.3}/tests/test_util_module.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.3
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.3"
4
4
  dynamic = [
5
5
  # "version" # , "description"
6
6
  ]
@@ -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("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'})
@@ -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
@@ -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
@@ -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: OpenHEMSNetwork, geoposition: GeoPosition, offpeakHoursRanges=None):
27
- del offpeakHoursRanges
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, allowSleep:bool, now=None):
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