Repository: davidusb-geek/emhass (language: python)

Issue:
Dev/forecast csv
Pull request for dev/forecast_csv


Discussion / hints:
Merging as tests passed correctly.

Fix patch:
diff --git a/README.md b/README.md
index db606255..834bb4e2 100644
--- a/README.md
+++ b/README.md
@@ -366,6 +366,10 @@ Here is the list of the other additional dictionary keys that can be passed at r
 
 - `def_total_hours` for the total number of hours that each deferrable load should operate.
 
+- `def_start_timestep` for the timestep as from which each deferrable load is allowed to operate (if you don't want the deferrable load to use the whole optimization timewindow).
+
+- `def_end_timestep` for the timestep before which each deferrable load should operate (if you don't want the deferrable load to use the whole optimization timewindow).
+
 - `treat_def_as_semi_cont` to define if we should treat each deferrable load as a semi-continuous variable.
 
 - `set_def_constant` to define if we should set each deferrable load as a constant fixed value variable with just one startup for each optimization task.
@@ -403,10 +407,14 @@ When applying this controller, the following `runtimeparams` should be defined:
 
 - `def_total_hours` for the list of deferrable loads functioning hours. These values can decrease as the day advances to take into account receding horizon daily energy objectives for each deferrable load.
 
+- `def_start_timestep` for the timestep as from which each deferrable load is allowed to operate (if you don't want the deferrable load to use the whole optimization timewindow). If you specify a value of 0 (or negative), the deferrable load will be optimized as from the beginning of the complete prediction horizon window.
+
+- `def_end_timestep` for the timestep before which each deferrable load should operate (if you don't want the deferrable load to use the whole optimization timewindow). If you specify a value of 0 (or negative), the deferrable load optimization window will extend up to the end of the prediction horizon window. 
+
 A correct call for a MPC optimization should look like:
 
 ```
-curl -i -H 'Content-Type:application/json' -X POST -d '{"pv_power_forecast":[0, 70, 141.22, 246.18, 513.5, 753.27, 1049.89, 1797.93, 1697.3, 3078.93], "prediction_horizon":10, "soc_init":0.5,"soc_final":0.6,"def_total_hours":[1,3]}' http://localhost:5000/action/naive-mpc-optim
+curl -i -H 'Content-Type:application/json' -X POST -d '{"pv_power_forecast":[0, 70, 141.22, 246.18, 513.5, 753.27, 1049.89, 1797.93, 1697.3, 3078.93], "prediction_horizon":10, "soc_init":0.5,"soc_final":0.6,"def_total_hours":[1,3],"def_start_timestep":[0,3],"def_end_timestep":[0,6],}' http://localhost:5000/action/naive-mpc-optim
 ```
 
 ## A machine learning forecaster
diff --git a/config_emhass.yaml b/config_emhass.yaml
index cf8b8425..e1a5e491 100644
--- a/config_emhass.yaml
+++ b/config_emhass.yaml
@@ -1,79 +1,85 @@
 # Configuration file for EMHASS
 
 retrieve_hass_conf:
-  - freq: 30 # The time step to resample retrieved data from hass in minutes
-  - days_to_retrieve: 8 # We will retrieve data from now and up to days_to_retrieve days
-  - var_PV: 'sensor.power_photovoltaics' # Photovoltaic produced power sensor in Watts
-  - var_load: 'sensor.power_load_no_var_loads' # Household power consumption sensor in Watts (deferrable loads should be substracted)
-  - load_negative: False # Set to True if the retrived load variable is negative by convention
-  - set_zero_min: True # A special treatment for a minimum value saturation to zero. Values below zero are replaced by nans
-  - var_replace_zero: # A list of retrived variables that we would want  to replace nans with zeros
-    - 'sensor.power_photovoltaics'
-  - var_interp: # A list of retrived variables that we would want to interpolate nan values using linear interpolation
-    - 'sensor.power_photovoltaics'
-    - 'sensor.power_load_no_var_loads'
-  - method_ts_round: 'nearest' # Set the method for timestamp rounding, options are: first, last and nearest
+  freq: 30 # The time step to resample retrieved data from hass in minutes
+  days_to_retrieve: 8 # We will retrieve data from now and up to days_to_retrieve days
+  var_PV: 'sensor.power_photovoltaics' # Photovoltaic produced power sensor in Watts
+  var_load: 'sensor.power_load_no_var_loads' # Household power consumption sensor in Watts (deferrable loads should be substracted)
+  load_negative: False # Set to True if the retrived load variable is negative by convention
+  set_zero_min: True # A special treatment for a minimum value saturation to zero. Values below zero are replaced by nans
+  var_replace_zero: # A list of retrived variables that we would want  to replace nans with zeros
+  - 'sensor.power_photovoltaics'
+  var_interp: # A list of retrived variables that we would want to interpolate nan values using linear interpolation
+  - 'sensor.power_photovoltaics'
+  - 'sensor.power_load_no_var_loads'
+  method_ts_round: 'nearest' # Set the method for timestamp rounding, options are: first, last and nearest
 
 optim_conf:
-  - set_use_battery: False # consider a battery storage
-  - delta_forecast: 1 # days
-  - num_def_loads: 2
-  - P_deferrable_nom: # Watts
-    - 3000.0
-    - 750.0
-  - def_total_hours: # hours
-    - 5
-    - 8
-  - treat_def_as_semi_cont: # treat this variable as semi continuous 
-    - True
-    - True
-  - set_def_constant: # set as a constant fixed value variable with just one startup for each 24h
-    - False
-    - False
-  - weather_forecast_method: 'scrapper' # options are 'scrapper' and 'csv'
-  - load_forecast_method: 'naive' # options are 'csv' to load a custom load forecast from a CSV file or 'naive' for a persistance model
-  - load_cost_forecast_method: 'hp_hc_periods' # options are 'hp_hc_periods' for peak and non-peak hours contracts and 'csv' to load custom cost from CSV file 
-  - list_hp_periods: # list of different tariff periods (only needed if load_cost_forecast_method='hp_hc_periods')
-    - period_hp_1:
-      - start: '02:54'
-      - end: '15:24'
-    - period_hp_2:
-      - start: '17:24'
-      - end: '20:24'
-  - load_cost_hp: 0.1907 # peak hours load cost in €/kWh (only needed if load_cost_forecast_method='hp_hc_periods')
-  - load_cost_hc: 0.1419 # non-peak hours load cost in €/kWh (only needed if load_cost_forecast_method='hp_hc_periods')
-  - prod_price_forecast_method: 'constant' # options are 'constant' for constant fixed value or 'csv' to load custom price forecast from a CSV file
-  - prod_sell_price: 0.065 # power production selling price in €/kWh (only needed if prod_price_forecast_method='constant')
-  - set_total_pv_sell: False # consider that all PV power is injected to the grid (self-consumption with total sell)
-  - lp_solver: 'default' # set the name of the linear programming solver that will be used
-  - lp_solver_path: 'empty' # set the path to the LP solver
-  - set_nocharge_from_grid: False # avoid battery charging from the grid
-  - set_nodischarge_to_grid: True # avoid battery discharging to the grid
-  - set_battery_dynamic: False # add a constraint to limit the dynamic of the battery power in power per time unit
-  - battery_dynamic_max: 0.9 # maximum dynamic positive power variation in percentage of battery maximum power
-  - battery_dynamic_min: -0.9 # minimum dynamic negative power variation in percentage of battery maximum power
-  - weight_battery_discharge: 1.0 # weight applied in cost function to battery usage for discharge
-  - weight_battery_charge: 1.0 # weight applied in cost function to battery usage for charge
+  set_use_battery: False # consider a battery storage
+  delta_forecast: 1 # days
+  num_def_loads: 2
+  P_deferrable_nom: # Watts
+  - 3000.0
+  - 750.0
+  def_total_hours: # hours
+  - 5
+  - 8
+  def_start_timestep: # timesteps
+  - 0
+  - 0
+  def_end_timestep: # timesteps
+  - 0
+  - 0
+  treat_def_as_semi_cont: # treat this variable as semi continuous 
+  - True
+  - True
+  set_def_constant: # set as a constant fixed value variable with just one startup for each 24h
+  - False
+  - False
+  weather_forecast_method: 'scrapper' # options are 'scrapper' and 'csv'
+  load_forecast_method: 'naive' # options are 'csv' to load a custom load forecast from a CSV file or 'naive' for a persistance model
+  load_cost_forecast_method: 'hp_hc_periods' # options are 'hp_hc_periods' for peak and non-peak hours contracts and 'csv' to load custom cost from CSV file 
+  list_hp_periods: # list of different tariff periods (only needed if load_cost_forecast_method='hp_hc_periods')
+  - period_hp_1:
+    - start: '02:54'
+    - end: '15:24'
+  - period_hp_2:
+    - start: '17:24'
+    - end: '20:24'
+  load_cost_hp: 0.1907 # peak hours load cost in €/kWh (only needed if load_cost_forecast_method='hp_hc_periods')
+  load_cost_hc: 0.1419 # non-peak hours load cost in €/kWh (only needed if load_cost_forecast_method='hp_hc_periods')
+  prod_price_forecast_method: 'constant' # options are 'constant' for constant fixed value or 'csv' to load custom price forecast from a CSV file
+  prod_sell_price: 0.065 # power production selling price in €/kWh (only needed if prod_price_forecast_method='constant')
+  set_total_pv_sell: False # consider that all PV power is injected to the grid (self-consumption with total sell)
+  lp_solver: 'default' # set the name of the linear programming solver that will be used
+  lp_solver_path: 'empty' # set the path to the LP solver
+  set_nocharge_from_grid: False # avoid battery charging from the grid
+  set_nodischarge_to_grid: True # avoid battery discharging to the grid
+  set_battery_dynamic: False # add a constraint to limit the dynamic of the battery power in power per time unit
+  battery_dynamic_max: 0.9 # maximum dynamic positive power variation in percentage of battery maximum power
+  battery_dynamic_min: -0.9 # minimum dynamic negative power variation in percentage of battery maximum power
+  weight_battery_discharge: 1.0 # weight applied in cost function to battery usage for discharge
+  weight_battery_charge: 1.0 # weight applied in cost function to battery usage for charge
 
 plant_conf:
-  - P_grid_max: 9000 # The maximum power that can be supplied by the utility grid in Watts
-  - module_model: # The PV module model
-    - 'CSUN_Eurasia_Energy_Systems_Industry_and_Trade_CSUN295_60M'
-  - inverter_model: # The PV inverter model
-    - 'Fronius_International_GmbH__Fronius_Primo_5_0_1_208_240__240V_'
-  - surface_tilt: # The tilt angle of your solar panels
-    - 30
-  - surface_azimuth: # The azimuth angle of your PV installation
-    - 205
-  - modules_per_string: # The number of modules per string
-    - 16 
-  - strings_per_inverter: # The number of used strings per inverter
-    - 1
-  - Pd_max: 1000 # If your system has a battery (set_use_battery=True), the maximum discharge power in Watts
-  - Pc_max: 1000 # If your system has a battery (set_use_battery=True), the maximum charge power in Watts
-  - eta_disch: 0.95 # If your system has a battery (set_use_battery=True), the discharge efficiency
-  - eta_ch: 0.95 # If your system has a battery (set_use_battery=True), the charge efficiency
-  - Enom: 5000 # If your system has a battery (set_use_battery=True), the total capacity of the battery stack in Wh
-  - SOCmin: 0.3 # If your system has a battery (set_use_battery=True), the minimun allowable battery state of charge
-  - SOCmax: 0.9 # If your system has a battery (set_use_battery=True), the minimun allowable battery state of charge
-  - SOCtarget: 0.6 # If your system has a battery (set_use_battery=True), the desired battery state of charge at the end of each optimization cycle
+  P_grid_max: 9000 # The maximum power that can be supplied by the utility grid in Watts
+  module_model: # The PV module model
+  - 'CSUN_Eurasia_Energy_Systems_Industry_and_Trade_CSUN295_60M'
+  inverter_model: # The PV inverter model
+  - 'Fronius_International_GmbH__Fronius_Primo_5_0_1_208_240__240V_'
+  surface_tilt: # The tilt angle of your solar panels
+  - 30
+  surface_azimuth: # The azimuth angle of your PV installation
+  - 205
+  modules_per_string: # The number of modules per string
+  - 16 
+  strings_per_inverter: # The number of used strings per inverter
+  - 1
+  Pd_max: 1000 # If your system has a battery (set_use_battery=True), the maximum discharge power in Watts
+  Pc_max: 1000 # If your system has a battery (set_use_battery=True), the maximum charge power in Watts
+  eta_disch: 0.95 # If your system has a battery (set_use_battery=True), the discharge efficiency
+  eta_ch: 0.95 # If your system has a battery (set_use_battery=True), the charge efficiency
+  Enom: 5000 # If your system has a battery (set_use_battery=True), the total capacity of the battery stack in Wh
+  SOCmin: 0.3 # If your system has a battery (set_use_battery=True), the minimun allowable battery state of charge
+  SOCmax: 0.9 # If your system has a battery (set_use_battery=True), the minimun allowable battery state of charge
+  SOCtarget: 0.6 # If your system has a battery (set_use_battery=True), the desired battery state of charge at the end of each optimization cycle
diff --git a/docs/config.md b/docs/config.md
index 02017b60..4c8741d3 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -12,81 +12,87 @@ We will find three main parts on the configuration file:
 
 These are the parameters that we will need to define to retrieve data from Home Assistant. There are no optional parameters. In the case of a list, an empty list is a valid entry.
 
-- freq: The time step to resample retrieved data from hass. This parameter is given in minutes. It should not be defined too low or you will run into memory problems when defining the Linear Programming optimization. Defaults to 30. 
-- days_to_retrieve: We will retrieve data from now and up to days_to_retrieve days. Defaults to 2.
-- var_PV: This is the name of the photovoltaic produced power sensor in Watts from Home Assistant. For example: 'sensor.power_photovoltaics'.
-- var_load: The name of the household power consumption sensor in Watts from Home Assistant. The deferrable loads that we will want to include in the optimization problem should be substracted from this sensor in HASS. For example: 'sensor.power_load_no_var_loads'
-- load_negative: Set this parameter to True if the retrived load variable is negative by convention. Defaults to False.
-- set_zero_min: Set this parameter to True to give a special treatment for a minimum value saturation to zero for power consumption data. Values below zero are replaced by nans. Defaults to True.
-- var_replace_zero: The list of retrieved variables that we would want to replace nans (if they exist) with zeros. For example:
+- `freq`: The time step to resample retrieved data from hass. This parameter is given in minutes. It should not be defined too low or you will run into memory problems when defining the Linear Programming optimization. Defaults to 30. 
+- `days_to_retrieve`: We will retrieve data from now and up to days_to_retrieve days. Defaults to 2.
+- `var_PV`: This is the name of the photovoltaic produced power sensor in Watts from Home Assistant. For example: 'sensor.power_photovoltaics'.
+- `var_load`: The name of the household power consumption sensor in Watts from Home Assistant. The deferrable loads that we will want to include in the optimization problem should be substracted from this sensor in HASS. For example: 'sensor.power_load_no_var_loads'
+- `load_negative`: Set this parameter to True if the retrived load variable is negative by convention. Defaults to False.
+- `set_zero_min`: Set this parameter to True to give a special treatment for a minimum value saturation to zero for power consumption data. Values below zero are replaced by nans. Defaults to True.
+- `var_replace_zero`: The list of retrieved variables that we would want to replace nans (if they exist) with zeros. For example:
 	- 'sensor.power_photovoltaics'
-- var_interp: The list of retrieved variables that we would want to interpolate nans values using linear interpolation. For example:
+- `var_interp`: The list of retrieved variables that we would want to interpolate nans values using linear interpolation. For example:
 	- 'sensor.power_photovoltaics'
 	- 'sensor.power_load_no_var_loads'
-- method_ts_round: Set the method for timestamp rounding, options are: first, last and nearest.
+- `method_ts_round`: Set the method for timestamp rounding, options are: first, last and nearest.
 
 A second part of this section is given by some privacy-sensitive parameters that should be included in a `secrets_emhass.yaml` file alongside the `config_emhass.yaml` file.
 
 The parameters in the `secrets_emhass.yaml` file are:
 
-- hass_url: The URL to your Home Assistant instance. For example: https://myhass.duckdns.org/
-- long_lived_token: A Long-Lived Access Token from the Lovelace profile page.
-- time_zone: The time zone of your system. For example: Europe/Paris.
-- lat: The latitude. For example: 45.0.
-- lon: The longitude. For example: 6.0
-- alt: The altitude in meters. For example: 100.0
+- `hass_url`: The URL to your Home Assistant instance. For example: https://myhass.duckdns.org/
+- `long_lived_token`: A Long-Lived Access Token from the Lovelace profile page.
+- `time_zone`: The time zone of your system. For example: Europe/Paris.
+- `lat`: The latitude. For example: 45.0.
+- `lon`: The longitude. For example: 6.0
+- `alt`: The altitude in meters. For example: 100.0
 
 ## Optimization configuration parameters
 
 These are the parameters needed to properly define the optimization problem.
 
-- set_use_battery: Set to True if we should consider an energy storage device such as a Li-Ion battery. Defaults to False.
-- delta_forecast: The number of days for forecasted data. Defaults to 1.
-- num_def_loads: Define the number of deferrable loads to consider. Defaults to 2.
-- P_deferrable_nom: The nominal power for each deferrable load in Watts. This is a list with a number of elements consistent with the number of deferrable loads defined before. For example:
+- `set_use_battery`: Set to True if we should consider an energy storage device such as a Li-Ion battery. Defaults to False.
+- `delta_forecast`: The number of days for forecasted data. Defaults to 1.
+- `num_def_loads`: Define the number of deferrable loads to consider. Defaults to 2.
+- `P_deferrable_nom`: The nominal power for each deferrable load in Watts. This is a list with a number of elements consistent with the number of deferrable loads defined before. For example:
 	- 3000
 	- 750
-- def_total_hours: The total number of hours that each deferrable load should operate. For example:
+- `def_total_hours`: The total number of hours that each deferrable load should operate. For example:
 	- 5
 	- 8
-- treat_def_as_semi_cont: Define if we should treat each deferrable load as a semi-continuous variable. Semi-continuous variables (`True`) are variables that must take a value that can be either their maximum or minimum/zero (for example On = Maximum load, Off = 0 W). Non semi-continuous (which means continuous) variables (`False`) can take any values between their maximum and minimum. For example:
+- `def_start_timestep`: The timestep as from which each deferrable load is allowed to operate (if you don't want the deferrable load to use the whole optimization timewindow). If you specify a value of 0 (or negative), the deferrable load will be optimized as from the beginning of the complete prediction horizon window. For example:
+    - 0
+    - 1 
+- `def_end_timestep`: The timestep before which each deferrable load should operate. The deferrable load is not allowed to operate after the specified timestep. If a value of 0 (or negative) is provided, the deferrable load is allowed to operate in the complete optimization window). For example:
+	- 0
+	- 3
+- `treat_def_as_semi_cont`: Define if we should treat each deferrable load as a semi-continuous variable. Semi-continuous variables (`True`) are variables that must take a value that can be either their maximum or minimum/zero (for example On = Maximum load, Off = 0 W). Non semi-continuous (which means continuous) variables (`False`) can take any values between their maximum and minimum. For example:
 	- True
 	- True
-- set_def_constant: Define if we should set each deferrable load as a constant fixed value variable with just one startup for each optimization task. For example:
+- `set_def_constant`: Define if we should set each deferrable load as a constant fixed value variable with just one startup for each optimization task. For example:
 	- False
 	- False
-- weather_forecast_method: This will define the weather forecast method that will be used. The options are 'scrapper' for a scrapping method for weather forecast from clearoutside.com and 'csv' to load a CSV file. When loading a CSV file this will be directly considered as the PV power forecast in Watts. The default CSV file path that will be used is '/data/data_weather_forecast.csv'. Defaults to 'scrapper' method.
-- load_forecast_method: The load forecast method that will be used. The options are 'csv' to load a CSV file or 'naive' for a simple 1-day persistance model. The default CSV file path that will be used is '/data/data_load_forecast.csv'. Defaults to 'naive'.
-- load_cost_forecast_method: Define the method that will be used for load cost forecast. The options are 'hp_hc_periods' for peak and non-peak hours contracts and 'csv' to load custom cost from CSV file. The default CSV file path that will be used is '/data/data_load_cost_forecast.csv'.
+- `weather_forecast_method`: This will define the weather forecast method that will be used. The options are 'scrapper' for a scrapping method for weather forecast from clearoutside.com and 'csv' to load a CSV file. When loading a CSV file this will be directly considered as the PV power forecast in Watts. The default CSV file path that will be used is '/data/data_weather_forecast.csv'. Defaults to 'scrapper' method.
+- `load_forecast_method`: The load forecast method that will be used. The options are 'csv' to load a CSV file or 'naive' for a simple 1-day persistance model. The default CSV file path that will be used is '/data/data_load_forecast.csv'. Defaults to 'naive'.
+- `load_cost_forecast_method`: Define the method that will be used for load cost forecast. The options are 'hp_hc_periods' for peak and non-peak hours contracts and 'csv' to load custom cost from CSV file. The default CSV file path that will be used is '/data/data_load_cost_forecast.csv'.
 The following parameters and definitions are only needed if load_cost_forecast_method='hp_hc_periods':
-	- list_hp_periods: Define a list of peak hour periods for load consumption from the grid. This is useful if you have a contract with peak and non-peak hours. For example for two peak hour periods: 
+	- `list_hp_periods`: Define a list of peak hour periods for load consumption from the grid. This is useful if you have a contract with peak and non-peak hours. For example for two peak hour periods: 
 		- period_hp_1:
 			- start: '02:54'
 			- end: '15:24'
 		- period_hp_2:
 			- start: '17:24'
 			- end: '20:24'
-	- load_cost_hp: The cost of the electrical energy from the grid during peak hours in €/kWh. Defaults to 0.1907.
-	- load_cost_hc: The cost of the electrical energy from the grid during non-peak hours in €/kWh. Defaults to 0.1419.
-
-- prod_price_forecast_method: Define the method that will be used for PV power production price forecast. This is the price that is payed by the utility for energy injected to the grid. The options are 'constant' for a constant fixed value or 'csv' to load custom price forecast from a CSV file. The default CSV file path that will be used is '/data/data_prod_price_forecast.csv'.
-- prod_sell_price: The paid price for energy injected to the grid from excedent PV production in €/kWh. Defaults to 0.065. This parameter is only needed if prod_price_forecast_method='constant'.
-- set_total_pv_sell: Set this parameter to true to consider that all the PV power produced is injected to the grid. No direct self-consumption. The default is false, for as system with direct self-consumption.
-- lp_solver: Set the name of the linear programming solver that will be used. Defaults to 'COIN_CMD'. The options are 'PULP_CBC_CMD', 'GLPK_CMD' and 'COIN_CMD'. 
-- lp_solver_path: Set the path to the LP solver. Defaults to '/usr/bin/cbc'. 
-- set_nocharge_from_grid: Set this to true if you want to forbidden to charge the battery from the grid. The battery will only be charged from excess PV.
-- set_nodischarge_to_grid: Set this to true if you want to forbidden to discharge the battery power to the grid.
-- set_battery_dynamic: Set a power dynamic limiting condition to the battery power. This is an additional constraint on the battery dynamic in power per unit of time, which allows you to set a percentage of the battery nominal full power as the maximum power allowed for (dis)charge.
-- battery_dynamic_max: The maximum positive (for discharge) battery power dynamic. This is the allowed power variation (in percentage) of battery maximum power per unit of time.
-- battery_dynamic_min: The maximum negative (for charge) battery power dynamic. This is the allowed power variation (in percentage) of battery maximum power per unit of time.
-- weight_battery_discharge: An additional weight applied in cost function to battery usage for discharge.
-- weight_battery_charge: An additional weight applied in cost function to battery usage for charge.
+	- `load_cost_hp`: The cost of the electrical energy from the grid during peak hours in €/kWh. Defaults to 0.1907.
+	- `load_cost_hc`: The cost of the electrical energy from the grid during non-peak hours in €/kWh. Defaults to 0.1419.
+
+- `prod_price_forecast_method`: Define the method that will be used for PV power production price forecast. This is the price that is payed by the utility for energy injected to the grid. The options are 'constant' for a constant fixed value or 'csv' to load custom price forecast from a CSV file. The default CSV file path that will be used is '/data/data_prod_price_forecast.csv'.
+- `prod_sell_price`: The paid price for energy injected to the grid from excedent PV production in €/kWh. Defaults to 0.065. This parameter is only needed if prod_price_forecast_method='constant'.
+- `set_total_pv_sell`: Set this parameter to true to consider that all the PV power produced is injected to the grid. No direct self-consumption. The default is false, for as system with direct self-consumption.
+- `lp_solver`: Set the name of the linear programming solver that will be used. Defaults to 'COIN_CMD'. The options are 'PULP_CBC_CMD', 'GLPK_CMD' and 'COIN_CMD'. 
+- `lp_solver_path`: Set the path to the LP solver. Defaults to '/usr/bin/cbc'. 
+- `set_nocharge_from_grid`: Set this to true if you want to forbidden to charge the battery from the grid. The battery will only be charged from excess PV.
+- `set_nodischarge_to_grid`: Set this to true if you want to forbidden to discharge the battery power to the grid.
+- `set_battery_dynamic`: Set a power dynamic limiting condition to the battery power. This is an additional constraint on the battery dynamic in power per unit of time, which allows you to set a percentage of the battery nominal full power as the maximum power allowed for (dis)charge.
+- `battery_dynamic_max`: The maximum positive (for discharge) battery power dynamic. This is the allowed power variation (in percentage) of battery maximum power per unit of time.
+- `battery_dynamic_min`: The maximum negative (for charge) battery power dynamic. This is the allowed power variation (in percentage) of battery maximum power per unit of time.
+- `weight_battery_discharge`: An additional weight applied in cost function to battery usage for discharge.
+- `weight_battery_charge`: An additional weight applied in cost function to battery usage for charge.
 
 ## System configuration parameters
 
 These are the technical parameters of the energy system of the household.
 
-- P_grid_max: The maximum power that can be supplied by the utility grid in Watts. Defaults to 9000.
+- `P_grid_max`: The maximum power that can be supplied by the utility grid in Watts. Defaults to 9000.
 
 We will define the technical parameters of the PV installation. For the modeling task we rely on the PVLib Python package. For more information see: [https://pvlib-python.readthedocs.io/en/stable/](https://pvlib-python.readthedocs.io/en/stable/)
 The complete list of supported modules can be found here: [https://github.com/davidusb-geek/emhass-add-on/files/9234460/sam-library-cec-modules-2019-03-05.csv](https://github.com/davidusb-geek/emhass-add-on/files/9234460/sam-library-cec-modules-2019-03-05.csv) 
@@ -94,20 +100,20 @@ And the list of inverter models can be found here: [https://github.com/davidusb-
 If your specific model is not found in these lists then solution (1) is to pick another model as close as possible as yours in terms of the nominal power.
 Solution (2) would be to use SolCast and pass that data directly to emhass as a list of values from a template. Take a look at this example here: [https://emhass.readthedocs.io/en/latest/forecasts.html#example-using-solcast-forecast-amber-prices](https://emhass.readthedocs.io/en/latest/forecasts.html#example-using-solcast-forecast-amber-prices)
 
-- module_model: The PV module model. For example: 'CSUN_Eurasia_Energy_Systems_Industry_and_Trade_CSUN295_60M'. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example one east-facing array (azimuth=90) and one west-facing array (azimuth=270). When finding the correct model for your installation remember to replace all the special characters in the model name by '_'.
-- inverter_model: The PV inverter model. For example: 'Fronius_International_GmbH__Fronius_Primo_5_0_1_208_240__240V_'. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example one east-facing array (azimuth=90) and one west-facing array (azimuth=270). When finding the correct model for your installation remember to replace all the special characters in the model name by '_'.
-- surface_tilt: The tilt angle of your solar panels. Defaults to 30. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example one east-facing array (azimuth=90) and one west-facing array (azimuth=270). 
-- surface_azimuth: The azimuth of your PV installation. Defaults to 205. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example one east-facing array (azimuth=90) and one west-facing array (azimuth=270). 
-- modules_per_string: The number of modules per string. Defaults to 16. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example one east-facing array (azimuth=90) and one west-facing array (azimuth=270). 
-- strings_per_inverter: The number of used strings per inverter. Defaults to 1. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example one east-facing array (azimuth=90) and one west-facing array (azimuth=270). 
+- `module_model`: The PV module model. For example: 'CSUN_Eurasia_Energy_Systems_Industry_and_Trade_CSUN295_60M'. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example one east-facing array (azimuth=90) and one west-facing array (azimuth=270). When finding the correct model for your installation remember to replace all the special characters in the model name by '_'.
+- `inverter_model`: The PV inverter model. For example: 'Fronius_International_GmbH__Fronius_Primo_5_0_1_208_240__240V_'. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example one east-facing array (azimuth=90) and one west-facing array (azimuth=270). When finding the correct model for your installation remember to replace all the special characters in the model name by '_'.
+- `surface_tilt`: The tilt angle of your solar panels. Defaults to 30. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example one east-facing array (azimuth=90) and one west-facing array (azimuth=270). 
+- `surface_azimuth`: The azimuth of your PV installation. Defaults to 205. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example one east-facing array (azimuth=90) and one west-facing array (azimuth=270). 
+- `modules_per_string`: The number of modules per string. Defaults to 16. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example one east-facing array (azimuth=90) and one west-facing array (azimuth=270). 
+- `strings_per_inverter`: The number of used strings per inverter. Defaults to 1. This parameter can be a list of items to enable the simulation of mixed orientation systems, for example one east-facing array (azimuth=90) and one west-facing array (azimuth=270). 
 
 If your system has a battery (set_use_battery=True), then you should define the following parameters:
 
-- Pd_max: The maximum discharge power in Watts. Defaults to 1000.
-- Pc_max: The maximum charge power in Watts. Defaults to 1000.
-- eta_disch: The discharge efficiency. Defaults to 0.95.
-- eta_ch: The charge efficiency. Defaults to 0.95.
-- Enom: The total capacity of the battery stack in Wh. Defaults to 5000.
-- SOCmin: The minimun allowable battery state of charge. Defaults to 0.3.
-- SOCmax: The maximum allowable battery state of charge. Defaults to 0.9.
-- SOCtarget: The desired battery state of charge at the end of each optimization cycle. Defaults to 0.6.
+- `Pd_max`: The maximum discharge power in Watts. Defaults to 1000.
+- `Pc_max`: The maximum charge power in Watts. Defaults to 1000.
+- `eta_disch`: The discharge efficiency. Defaults to 0.95.
+- `eta_ch`: The charge efficiency. Defaults to 0.95.
+- `Enom`: The total capacity of the battery stack in Wh. Defaults to 5000.
+- `SOCmin`: The minimun allowable battery state of charge. Defaults to 0.3.
+- `SOCmax`: The maximum allowable battery state of charge. Defaults to 0.9.
+- `SOCtarget`: The desired battery state of charge at the end of each optimization cycle. Defaults to 0.6.
diff --git a/docs/lpems.md b/docs/lpems.md
index fb470f31..b6609d8c 100644
--- a/docs/lpems.md
+++ b/docs/lpems.md
@@ -219,6 +219,10 @@ When applying this controller, the following `runtimeparams` should be defined:
 
 - `def_total_hours` for the list of deferrable loads functioning hours. These values can decrease as the day advances to take into account receding horizon daily energy objectives for each deferrable load.
 
+- `def_start_timestep` for the timestep as from which each deferrable load is allowed to operate (if you don't want the deferrable load to use the whole optimization timewindow). If you specify a value of 0 (or negative), the deferrable load will be optimized as from the beginning of the complete prediction horizon window.
+
+- `def_end_timestep` for the timestep before which each deferrable load should operate (if you don't want the deferrable load to use the whole optimization timewindow). If you specify a value of 0 (or negative), the deferrable load will be optimized over the complete prediction horizon window.
+
 In a practical use case, the values for `soc_init` and `soc_final` for each MPC optimization can be taken from the initial day-ahead optimization performed at the beginning of each day.
 
 We are now ready to configure our system using the proposed configuration file and link our package to Home Assistant!
diff --git a/options.json b/options.json
index 7814aef8..14cc9de8 100644
--- a/options.json
+++ b/options.json
@@ -39,7 +39,29 @@
     {                                                                                                                                                                                                            
       "operating_hours_of_each_deferrable_load": 5                                                                                                                                                               
     }                                                                                                                                                                                                            
-  ],                                                                                                                                                                                                             
+  ],
+  "list_start_timesteps_of_each_deferrable_load": [
+    {
+      "start_timesteps_of_each_deferrable_load": 0
+    },
+    {
+      "start_timesteps_of_each_deferrable_load": 0
+    },
+    {
+      "start_timesteps_of_each_deferrable_load": 0
+    }
+  ],
+  "list_end_timesteps_of_each_deferrable_load": [
+    {
+      "end_timesteps_of_each_deferrable_load": 0
+    },
+    {
+      "end_timesteps_of_each_deferrable_load": 0
+    },
+    {
+      "end_timesteps_of_each_deferrable_load": 0
+    }
+  ],
   "list_peak_hours_periods_start_hours": [                                                                                                                                                                       
     {                                                                                                                                                                                                            
       "peak_hours_periods_start_hours": "02:54"                                                                                                                                                                  
@@ -110,4 +132,4 @@
   "battery_minimum_state_of_charge": 0.3,
   "battery_maximum_state_of_charge": 0.9,
   "battery_target_state_of_charge": 0.6
-}
\ No newline at end of file
+}
diff --git a/src/emhass/command_line.py b/src/emhass/command_line.py
index 9d549fe4..26e638c2 100644
--- a/src/emhass/command_line.py
+++ b/src/emhass/command_line.py
@@ -272,9 +272,11 @@ def naive_mpc_optim(input_data_dict: dict, logger: logging.Logger,
     soc_init = input_data_dict['params']['passed_data']['soc_init']
     soc_final = input_data_dict['params']['passed_data']['soc_final']
     def_total_hours = input_data_dict['params']['passed_data']['def_total_hours']
+    def_start_timestep = input_data_dict['params']['passed_data']['def_start_timestep']
+    def_end_timestep = input_data_dict['params']['passed_data']['def_end_timestep']
     opt_res_naive_mpc = input_data_dict['opt'].perform_naive_mpc_optim(
         df_input_data_dayahead, input_data_dict['P_PV_forecast'], input_data_dict['P_load_forecast'],
-        prediction_horizon, soc_init, soc_final, def_total_hours)
+        prediction_horizon, soc_init, soc_final, def_total_hours, def_start_timestep, def_end_timestep)
     # Save CSV file for publish_data
     if save_data_to_file:
         today = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
diff --git a/src/emhass/optimization.py b/src/emhass/optimization.py
index ad5dadb4..8e9da3ca 100644
--- a/src/emhass/optimization.py
+++ b/src/emhass/optimization.py
@@ -3,11 +3,12 @@
 
 import logging
 import copy
-from typing import Optional
+from typing import Optional, Tuple
 import pandas as pd
 import numpy as np
 import pulp as plp
 from pulp import PULP_CBC_CMD, COIN_CMD, GLPK_CMD
+from math import ceil
 
 
 class optimization:
@@ -89,6 +90,8 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n
                              unit_load_cost: np.array, unit_prod_price: np.array,
                              soc_init: Optional[float] = None, soc_final: Optional[float] = None,
                              def_total_hours: Optional[list] = None, 
+                             def_start_timestep: Optional[list] = None,
+                             def_end_timestep: Optional[list] = None,
                              debug: Optional[bool] = False) -> pd.DataFrame:
         r"""
         Perform the actual optimization using linear programming (LP).
@@ -118,6 +121,10 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n
         :param def_total_hours: The functioning hours for this iteration for each deferrable load. \
             (For continuous deferrable loads: functioning hours at nominal power)
         :type def_total_hours: list
+        :param def_start_timestep: The timestep as from which each deferrable load is allowed to operate.
+        :type def_start_timestep: list
+        :param def_end_timestep: The timestep before which each deferrable load should operate.
+        :type def_end_timestep: list
         :return: The input DataFrame with all the different results from the \
             optimization appended
         :rtype: pd.DataFrame
@@ -137,6 +144,10 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n
                     soc_final = self.plant_conf['SOCtarget']
         if def_total_hours is None:
             def_total_hours = self.optim_conf['def_total_hours']
+        if def_start_timestep is None:
+            def_start_timestep = self.optim_conf['def_start_timestep']
+        if def_end_timestep is None:
+            def_end_timestep = self.optim_conf['def_end_timestep']
         type_self_conso = 'bigm' # maxmin
         
         #### The LP problem using Pulp ####
@@ -215,7 +226,7 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n
             elif type_self_conso == 'maxmin':
                 objective = plp.lpSum(0.001*self.timeStep*unit_load_cost[i]*SC[i] for i in set_I)
             else:
-                self.logger.error("Not a valida option for type_self_conso parameter")
+                self.logger.error("Not a valid option for type_self_conso parameter")
         else:
             self.logger.error("The cost function specified type is not valid")
         # Add more terms to the objective function in the case of battery use
@@ -273,6 +284,27 @@ def perform_optimization(self, data_opt: pd.DataFrame, P_PV: np.array, P_load: n
                     sense = plp.LpConstraintEQ,
                     rhs = def_total_hours[k]*self.optim_conf['P_deferrable_nom'][k])
                 })
+            # Ensure deferrable loads consume energy between def_start_timestep & def_end_timestep
+            self.logger.debug("Deferrable load {}: Proposed optimization window: {} --> {}".format(k, def_start_timestep[k], def_end_timestep[k]))
+            def_start, def_end, warning = optimization.validate_def_timewindow(def_start_timestep[k], def_end_timestep[k], ceil(def_total_hours[k]/self.timeStep), n)
+            if warning is not None: 
+                self.logger.warning("Deferrable load {} : {}".format(k, warning))
+            self.logger.debug("Deferrable load {}: Validated optimization window: {} --> {}".format(k, def_start, def_end))
+            if def_start > 0:                    
+                constraints.update({"constraint_defload{}_start_timestep".format(k) :
+                    plp.LpConstraint(
+                        e = plp.lpSum(P_deferrable[k][i]*self.timeStep for i in range(0, def_start)),
+                        sense = plp.LpConstraintEQ,
+                        rhs = 0)
+                    })
+            if def_end > 0:                    
+                constraints.update({"constraint_defload{}_end_timestep".format(k) :
+                    plp.LpConstraint(
+                        e = plp.lpSum(P_deferrable[k][i]*self.timeStep for i in range(def_end, n)),
+                        sense = plp.LpConstraintEQ,
+                        rhs = 0)
+                    })
+            
             # Treat deferrable load as a semi-continuous variable
             if self.optim_conf['treat_def_as_semi_cont'][k]:
                 constraints.update({"constraint_pdef{}_semicont1_{}".format(k, i) : 
@@ -546,7 +578,9 @@ def perform_dayahead_forecast_optim(self, df_input_data: pd.DataFrame,
         
     def perform_naive_mpc_optim(self, df_input_data: pd.DataFrame, P_PV: pd.Series, P_load: pd.Series,
                                 prediction_horizon: int, soc_init: Optional[float] = None, soc_final: Optional[float] = None,
-                                def_total_hours: Optional[list] = None) -> pd.DataFrame:
+                                def_total_hours: Optional[list] = None,
+                                def_start_timestep: Optional[list] = None,
+                                def_end_timestep: Optional[list] = None) -> pd.DataFrame:
         r"""
         Perform a naive approach to a Model Predictive Control (MPC). \
         This implementaion is naive because we are not using the formal formulation \
@@ -573,6 +607,10 @@ def perform_naive_mpc_optim(self, df_input_data: pd.DataFrame, P_PV: pd.Series,
         :param def_total_hours: The functioning hours for this iteration for each deferrable load. \
             (For continuous deferrable loads: functioning hours at nominal power)
         :type def_total_hours: list
+        :param def_start_timestep: The timestep as from which each deferrable load is allowed to operate.
+        :type def_start_timestep: list
+        :param def_end_timestep: The timestep before which each deferrable load should operate.
+        :type def_end_timestep: list
         :return: opt_res: A DataFrame containing the optimization results
         :rtype: pandas.DataFrame
 
@@ -591,6 +629,43 @@ def perform_naive_mpc_optim(self, df_input_data: pd.DataFrame, P_PV: pd.Series,
         # Call optimization function
         self.opt_res = self.perform_optimization(df_input_data, P_PV.values.ravel(), P_load.values.ravel(), 
                                                  unit_load_cost, unit_prod_price, soc_init=soc_init, 
-                                                 soc_final=soc_final, def_total_hours=def_total_hours)
+                                                 soc_final=soc_final, def_total_hours=def_total_hours,
+                                                 def_start_timestep=def_start_timestep, def_end_timestep=def_end_timestep)
         return self.opt_res
-    
+
+    @staticmethod
+    def validate_def_timewindow(start: int, end: int, min_steps: int, window: int) -> Tuple[int,int,str]:
+        r"""
+        Helper function to validate (and if necessary: correct) the defined optimization window of a deferrable load.
+        
+        :param start: Start timestep of the optimization window of the deferrable load
+        :type start: int
+        :param end: End timestep of the optimization window of the deferrable load
+        :type end: int
+        :param min_steps: Minimal timesteps during which the load should operate (at nominal power)
+        :type min_steps: int
+        :param window: Total number of timesteps in the optimization window
+        :type window: int
+        :return: start_validated: Validated start timestep of the optimization window of the deferrable load
+        :rtype: int
+        :return: end_validated: Validated end timestep of the optimization window of the deferrable load
+        :rtype: int
+        :return: warning: Any warning information to be returned from the validation steps
+        :rtype: string
+
+        """
+        start_validated = 0
+        end_validated = 0
+        warning = None
+        # Verify that start <= end
+        if start <= end or start <= 0 or end <= 0:
+            # start and end should be within the optimization timewindow [0, window]
+            start_validated = max(0, min(window, start))
+            end_validated = max(0, min(window, end))
+            if end_validated > 0:
+                # If the available timeframe is shorter than the number of timesteps needed to meet the hours to operate (def_total_hours), issue a warning.
+                if (end_validated-start_validated) < min_steps:
+                    warning = "Available timeframe is shorter than the specified number of hours to operate. Optimization will fail."
+        else:
+            warning = "Invalid timeframe for deferrable load (start timestep is not <= end timestep). Continuing optimization without timewindow constraint."
+        return start_validated, end_validated, warning
diff --git a/src/emhass/utils.py b/src/emhass/utils.py
index 8cd5f6f6..250d9414 100644
--- a/src/emhass/utils.py
+++ b/src/emhass/utils.py
@@ -173,6 +173,16 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
             else:
                 def_total_hours = runtimeparams['def_total_hours']
             params['passed_data']['def_total_hours'] = def_total_hours
+            if 'def_start_timestep' not in runtimeparams.keys():
+                def_start_timestep = optim_conf['def_start_timestep']
+            else:
+                def_start_timestep = runtimeparams['def_start_timestep']
+            params['passed_data']['def_start_timestep'] = def_start_timestep
+            if 'def_end_timestep' not in runtimeparams.keys():
+                def_end_timestep = optim_conf['def_end_timestep']
+            else:
+                def_end_timestep = runtimeparams['def_end_timestep']
+            params['passed_data']['def_end_timestep'] = def_end_timestep
             if 'alpha' not in runtimeparams.keys():
                 alpha = 0.5
             else:
@@ -189,6 +199,8 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
             params['passed_data']['soc_init'] = None
             params['passed_data']['soc_final'] = None
             params['passed_data']['def_total_hours'] = None
+            params['passed_data']['def_start_timestep'] = None
+            params['passed_data']['def_end_timestep'] = None
             params['passed_data']['alpha'] = None
             params['passed_data']['beta'] = None
         # Treat passed forecast data lists
@@ -311,6 +323,10 @@ def treat_runtimeparams(runtimeparams: str, params: str, retrieve_hass_conf: dic
             optim_conf['P_deferrable_nom'] = runtimeparams['P_deferrable_nom']
         if 'def_total_hours' in runtimeparams.keys():
             optim_conf['def_total_hours'] = runtimeparams['def_total_hours']
+        if 'def_start_timestep' in runtimeparams.keys():
+            optim_conf['def_start_timestep'] = runtimeparams['def_start_timestep']
+        if 'def_end_timestep' in runtimeparams.keys():
+            optim_conf['def_end_timestep'] = runtimeparams['def_end_timestep']
         if 'treat_def_as_semi_cont' in runtimeparams.keys():
             optim_conf['treat_def_as_semi_cont'] = runtimeparams['treat_def_as_semi_cont']
         if 'set_def_constant' in runtimeparams.keys():
@@ -391,7 +407,7 @@ def get_yaml_parse(config_path: str, use_secrets: Optional[bool] = True,
         else:
             input_secrets = input_conf.pop('params_secrets', None)
         
-    retrieve_hass_conf = dict((key,d[key]) for d in input_conf['retrieve_hass_conf'] for key in d)
+    retrieve_hass_conf = input_conf.get('retrieve_hass_conf', {})
     if use_secrets:
         retrieve_hass_conf = {**retrieve_hass_conf, **input_secrets}
     else:
@@ -404,11 +420,11 @@ def get_yaml_parse(config_path: str, use_secrets: Optional[bool] = True,
     retrieve_hass_conf['freq'] = pd.to_timedelta(retrieve_hass_conf['freq'], "minutes")
     retrieve_hass_conf['time_zone'] = pytz.timezone(retrieve_hass_conf['time_zone'])
     
-    optim_conf = dict((key,d[key]) for d in input_conf['optim_conf'] for key in d)
+    optim_conf = input_conf.get('optim_conf', {})
     optim_conf['list_hp_periods'] = dict((key,d[key]) for d in optim_conf['list_hp_periods'] for key in d)
     optim_conf['delta_forecast'] = pd.Timedelta(days=optim_conf['delta_forecast'])
     
-    plant_conf = dict((key,d[key]) for d in input_conf['plant_conf'] for key in d)
+    plant_conf = input_conf.get('plant_conf', {})
     
     return retrieve_hass_conf, optim_conf, plant_conf
 
diff --git a/src/emhass/web_server.py b/src/emhass/web_server.py
index cc26d5c0..2e614ad6 100644
--- a/src/emhass/web_server.py
+++ b/src/emhass/web_server.py
@@ -99,59 +99,61 @@ def get_injection_dict_forecast_model_tune(df_pred_optim, mlf):
 def build_params(params, options, addon):
     if addon == 1:
         # Updating variables in retrieve_hass_conf
-        params['retrieve_hass_conf'][0]['freq'] = options['optimization_time_step']
-        params['retrieve_hass_conf'][1]['days_to_retrieve'] = options['historic_days_to_retrieve']
-        params['retrieve_hass_conf'][2]['var_PV'] = options['sensor_power_photovoltaics']
-        params['retrieve_hass_conf'][3]['var_load'] = options['sensor_power_load_no_var_loads']
-        params['retrieve_hass_conf'][6]['var_replace_zero'] = [options['sensor_power_photovoltaics']]
-        params['retrieve_hass_conf'][7]['var_interp'] = [options['sensor_power_photovoltaics'], options['sensor_power_load_no_var_loads']]
-        params['retrieve_hass_conf'][8]['method_ts_round'] = options['method_ts_round']
+        params['retrieve_hass_conf']['freq'] = options['optimization_time_step']
+        params['retrieve_hass_conf']['days_to_retrieve'] = options['historic_days_to_retrieve']
+        params['retrieve_hass_conf']['var_PV'] = options['sensor_power_photovoltaics']
+        params['retrieve_hass_conf']['var_load'] = options['sensor_power_load_no_var_loads']
+        params['retrieve_hass_conf']['var_replace_zero'] = [options['sensor_power_photovoltaics']]
+        params['retrieve_hass_conf']['var_interp'] = [options['sensor_power_photovoltaics'], options['sensor_power_load_no_var_loads']]
+        params['retrieve_hass_conf']['method_ts_round'] = options['method_ts_round']
         # Updating variables in optim_conf
-        params['optim_conf'][0]['set_use_battery'] = options['set_use_battery']
-        params['optim_conf'][2]['num_def_loads'] = options['number_of_deferrable_loads']
-        params['optim_conf'][3]['P_deferrable_nom'] = [i['nominal_power_of_deferrable_loads'] for i in options['list_nominal_power_of_deferrable_loads']]
-        params['optim_conf'][4]['def_total_hours'] = [i['operating_hours_of_each_deferrable_load'] for i in options['list_operating_hours_of_each_deferrable_load']]
-        params['optim_conf'][5]['treat_def_as_semi_cont'] = [i['treat_deferrable_load_as_semi_cont'] for i in options['list_treat_deferrable_load_as_semi_cont']]
-        params['optim_conf'][6]['set_def_constant'] = [False for i in range(len(params['optim_conf'][3]['P_deferrable_nom']))]
-        params['optim_conf'][8]['load_forecast_method'] = options['load_forecast_method']
+        params['optim_conf']['set_use_battery'] = options['set_use_battery']
+        params['optim_conf']['num_def_loads'] = options['number_of_deferrable_loads']
+        params['optim_conf']['P_deferrable_nom'] = [i['nominal_power_of_deferrable_loads'] for i in options['list_nominal_power_of_deferrable_loads']]
+        params['optim_conf']['def_total_hours'] = [i['operating_hours_of_each_deferrable_load'] for i in options['list_operating_hours_of_each_deferrable_load']]
+        params['optim_conf']['treat_def_as_semi_cont'] = [i['treat_deferrable_load_as_semi_cont'] for i in options['list_treat_deferrable_load_as_semi_cont']]
+        params['optim_conf']['set_def_constant'] = [False for i in range(len(params['optim_conf']['P_deferrable_nom']))]
+        params['optim_conf']['load_forecast_method'] = options['load_forecast_method']
         start_hours_list = [i['peak_hours_periods_start_hours'] for i in options['list_peak_hours_periods_start_hours']]
         end_hours_list = [i['peak_hours_periods_end_hours'] for i in options['list_peak_hours_periods_end_hours']]
         num_peak_hours = len(start_hours_list)
         list_hp_periods_list = [{'period_hp_'+str(i+1):[{'start':start_hours_list[i]},{'end':end_hours_list[i]}]} for i in range(num_peak_hours)]
-        params['optim_conf'][10]['list_hp_periods'] = list_hp_periods_list
-        params['optim_conf'][11]['load_cost_hp'] = options['load_peak_hours_cost']
-        params['optim_conf'][12]['load_cost_hc'] = options['load_offpeak_hours_cost']
-        params['optim_conf'][14]['prod_sell_price'] = options['photovoltaic_production_sell_price']
-        params['optim_conf'][15]['set_total_pv_sell'] = options['set_total_pv_sell']
-        params['optim_conf'][16]['lp_solver'] = options['lp_solver']
-        params['optim_conf'][17]['lp_solver_path'] = options['lp_solver_path']
-        params['optim_conf'][18]['set_nocharge_from_grid'] = options['set_nocharge_from_grid']
-        params['optim_conf'][19]['set_nodischarge_to_grid'] = options['set_nodischarge_to_grid']
-        params['optim_conf'][20]['set_battery_dynamic'] = options['set_battery_dynamic']
-        params['optim_conf'][21]['battery_dynamic_max'] = options['battery_dynamic_max']
-        params['optim_conf'][22]['battery_dynamic_min'] = options['battery_dynamic_min']
-        params['optim_conf'][23]['weight_battery_discharge'] = options['weight_battery_discharge']
-        params['optim_conf'][24]['weight_battery_charge'] = options['weight_battery_charge']
+        params['optim_conf']['list_hp_periods'] = list_hp_periods_list
+        params['optim_conf']['load_cost_hp'] = options['load_peak_hours_cost']
+        params['optim_conf']['load_cost_hc'] = options['load_offpeak_hours_cost']
+        params['optim_conf']['prod_sell_price'] = options['photovoltaic_production_sell_price']
+        params['optim_conf']['set_total_pv_sell'] = options['set_total_pv_sell']
+        params['optim_conf']['lp_solver'] = options['lp_solver']
+        params['optim_conf']['lp_solver_path'] = options['lp_solver_path']
+        params['optim_conf']['set_nocharge_from_grid'] = options['set_nocharge_from_grid']
+        params['optim_conf']['set_nodischarge_to_grid'] = options['set_nodischarge_to_grid']
+        params['optim_conf']['set_battery_dynamic'] = options['set_battery_dynamic']
+        params['optim_conf']['battery_dynamic_max'] = options['battery_dynamic_max']
+        params['optim_conf']['battery_dynamic_min'] = options['battery_dynamic_min']
+        params['optim_conf']['weight_battery_discharge'] = options['weight_battery_discharge']
+        params['optim_conf']['weight_battery_charge'] = options['weight_battery_charge']
+        params['optim_conf']['def_start_timestep'] = [i['start_timesteps_of_each_deferrable_load'] for i in options['list_start_timesteps_of_each_deferrable_load']]
+        params['optim_conf']['def_end_timestep'] = [i['end_timesteps_of_each_deferrable_load'] for i in options['list_end_timesteps_of_each_deferrable_load']]
         # Updating variables in plant_conf
-        params['plant_conf'][0]['P_grid_max'] = options['maximum_power_from_grid']
-        params['plant_conf'][1]['module_model'] = [i['pv_module_model'] for i in options['list_pv_module_model']]
-        params['plant_conf'][2]['inverter_model'] = [i['pv_inverter_model'] for i in options['list_pv_inverter_model']]
-        params['plant_conf'][3]['surface_tilt'] = [i['surface_tilt'] for i in options['list_surface_tilt']]
-        params['plant_conf'][4]['surface_azimuth'] = [i['surface_azimuth'] for i in options['list_surface_azimuth']]
-        params['plant_conf'][5]['modules_per_string'] = [i['modules_per_string'] for i in options['list_modules_per_string']]
-        params['plant_conf'][6]['strings_per_inverter'] = [i['strings_per_inverter'] for i in options['list_strings_per_inverter']]
-        params['plant_conf'][7]['Pd_max'] = options['battery_discharge_power_max']
-        params['plant_conf'][8]['Pc_max'] = options['battery_charge_power_max']
-        params['plant_conf'][9]['eta_disch'] = options['battery_discharge_efficiency']
-        params['plant_conf'][10]['eta_ch'] = options['battery_charge_efficiency']
-        params['plant_conf'][11]['Enom'] = options['battery_nominal_energy_capacity']
-        params['plant_conf'][12]['SOCmin'] = options['battery_minimum_state_of_charge']
-        params['plant_conf'][13]['SOCmax'] = options['battery_maximum_state_of_charge']
-        params['plant_conf'][14]['SOCtarget'] = options['battery_target_state_of_charge']
+        params['plant_conf']['P_grid_max'] = options['maximum_power_from_grid']
+        params['plant_conf']['module_model'] = [i['pv_module_model'] for i in options['list_pv_module_model']]
+        params['plant_conf']['inverter_model'] = [i['pv_inverter_model'] for i in options['list_pv_inverter_model']]
+        params['plant_conf']['surface_tilt'] = [i['surface_tilt'] for i in options['list_surface_tilt']]
+        params['plant_conf']['surface_azimuth'] = [i['surface_azimuth'] for i in options['list_surface_azimuth']]
+        params['plant_conf']['modules_per_string'] = [i['modules_per_string'] for i in options['list_modules_per_string']]
+        params['plant_conf']['strings_per_inverter'] = [i['strings_per_inverter'] for i in options['list_strings_per_inverter']]
+        params['plant_conf']['Pd_max'] = options['battery_discharge_power_max']
+        params['plant_conf']['Pc_max'] = options['battery_charge_power_max']
+        params['plant_conf']['eta_disch'] = options['battery_discharge_efficiency']
+        params['plant_conf']['eta_ch'] = options['battery_charge_efficiency']
+        params['plant_conf']['Enom'] = options['battery_nominal_energy_capacity']
+        params['plant_conf']['SOCmin'] = options['battery_minimum_state_of_charge']
+        params['plant_conf']['SOCmax'] = options['battery_maximum_state_of_charge']
+        params['plant_conf']['SOCtarget'] = options['battery_target_state_of_charge']
     # The params dict
     params['params_secrets'] = params_secrets
     params['passed_data'] = {'pv_power_forecast':None,'load_power_forecast':None,'load_cost_forecast':None,'prod_price_forecast':None,
-                             'prediction_horizon':None,'soc_init':None,'soc_final':None,'def_total_hours':None,'alpha':None,'beta':None}
+                             'prediction_horizon':None,'soc_init':None,'soc_final':None,'def_total_hours':None,'def_start_timestep':None,'def_end_timestep':None,'alpha':None,'beta':None}
     return params
 
 @app.route('/')
@@ -282,7 +284,7 @@ def action_call(action_name):
         plant_conf = config['plant_conf']
     else:
         app.logger.error("config_emhass.json does not exists")
-        app.logger.info("Falied config_path: "+str(config_path))
+        app.logger.info("Failed config_path: "+str(config_path))
 
     params = {}
     params['retrieve_hass_conf'] = retrieve_hass_conf


Test patch:
diff --git a/tests/test_command_line_utils.py b/tests/test_command_line_utils.py
index 881d4b0d..8e4d15bc 100644
--- a/tests/test_command_line_utils.py
+++ b/tests/test_command_line_utils.py
@@ -185,13 +185,13 @@ def test_naive_mpc_optim(self):
         # A test similar to the docs
         runtimeparams = {"pv_power_forecast":
             [1,2,3,4,5,6,7,8,9,10], 
-            "prediction_horizon":10, "soc_init":0.5,"soc_final":0.6,"def_total_hours":[1,3]}
+            "prediction_horizon":10, "soc_init":0.5,"soc_final":0.6,"def_total_hours":[1,3],"def_start_timestep":[-3,0],"def_end_timestep":[8,0]}
         runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
-        params['optim_conf'][7]['weather_forecast_method'] = 'list'
-        params['optim_conf'][8]['load_forecast_method'] = 'naive'
-        params['optim_conf'][9]['load_cost_forecast_method'] = 'hp_hc_periods'
-        params['optim_conf'][13]['prod_price_forecast_method'] = 'constant'
+        params['optim_conf']['weather_forecast_method'] = 'list'
+        params['optim_conf']['load_forecast_method'] = 'naive'
+        params['optim_conf']['load_cost_forecast_method'] = 'hp_hc_periods'
+        params['optim_conf']['prod_price_forecast_method'] = 'constant'
         params_json = json.dumps(params)
         input_data_dict = set_input_data_dict(config_path, base_path, costfun, params_json, runtimeparams_json, 
                                               action, logger, get_data_from_file=True)
@@ -205,7 +205,7 @@ def test_naive_mpc_optim(self):
         costfun = 'profit'
         action = 'naive-mpc-optim'
         params = copy.deepcopy(json.loads(self.params_json))
-        params['retrieve_hass_conf'][8]['method_ts_round'] = 'first'
+        params['retrieve_hass_conf']['method_ts_round'] = 'first'
         params_json = json.dumps(params)
         input_data_dict = set_input_data_dict(config_path, base_path, costfun, params_json, self.runtimeparams_json, 
                                               action, logger, get_data_from_file=True)
@@ -217,8 +217,8 @@ def test_naive_mpc_optim(self):
         self.assertTrue(len(opt_res_first)==1)
         action = 'naive-mpc-optim'
         params = copy.deepcopy(json.loads(self.params_json))
-        params['retrieve_hass_conf'][8]['method_ts_round'] = 'last'
-        params['optim_conf'][0]['set_use_battery'] = True
+        params['retrieve_hass_conf']['method_ts_round'] = 'last'
+        params['optim_conf']['set_use_battery'] = True
         params_json = json.dumps(params)
         input_data_dict = set_input_data_dict(config_path, base_path, costfun, params_json, self.runtimeparams_json, 
                                               action, logger, get_data_from_file=True)
@@ -270,7 +270,7 @@ def test_forecast_model_fit_predict_tune(self):
         }
         runtimeparams_json = json.dumps(runtimeparams)
         # params['passed_data'] = runtimeparams
-        params['optim_conf'][8]['load_forecast_method'] = 'skforecast'
+        params['optim_conf']['load_forecast_method'] = 'skforecast'
         params_json = json.dumps(params)
         input_data_dict = set_input_data_dict(config_path, base_path, costfun, params_json, runtimeparams_json, 
                                               action, logger, get_data_from_file=True)
@@ -346,7 +346,7 @@ def test_main_forecast_model_fit(self):
         }
         runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
-        params['optim_conf'][8]['load_forecast_method'] = 'skforecast'
+        params['optim_conf']['load_forecast_method'] = 'skforecast'
         params_json = json.dumps(params)
         with patch('sys.argv', ['main', '--action', 'forecast-model-fit', '--config', str(pathlib.Path(root+'/config_emhass.yaml')), 
                                 '--params', params_json, '--runtimeparams', runtimeparams_json,
@@ -368,7 +368,7 @@ def test_main_forecast_model_predict(self):
         }
         runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
-        params['optim_conf'][8]['load_forecast_method'] = 'skforecast'
+        params['optim_conf']['load_forecast_method'] = 'skforecast'
         params_json = json.dumps(params)
         with patch('sys.argv', ['main', '--action', 'forecast-model-predict', '--config', str(pathlib.Path(root+'/config_emhass.yaml')), 
                                 '--params', params_json, '--runtimeparams', runtimeparams_json,
@@ -390,7 +390,7 @@ def test_main_forecast_model_tune(self):
         }
         runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
-        params['optim_conf'][8]['load_forecast_method'] = 'skforecast'
+        params['optim_conf']['load_forecast_method'] = 'skforecast'
         params_json = json.dumps(params)
         with patch('sys.argv', ['main', '--action', 'forecast-model-tune', '--config', str(pathlib.Path(root+'/config_emhass.yaml')), 
                                 '--params', params_json, '--runtimeparams', runtimeparams_json,
diff --git a/tests/test_forecast.py b/tests/test_forecast.py
index 643afa5a..3830656e 100644
--- a/tests/test_forecast.py
+++ b/tests/test_forecast.py
@@ -391,7 +391,7 @@ def test_get_load_forecast_mlforecaster(self):
         }
         runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
-        params['optim_conf'][8]['load_forecast_method'] = 'mlforecaster'
+        params['optim_conf']['load_forecast_method'] = 'mlforecaster'
         params_json = json.dumps(params)
         input_data_dict = set_input_data_dict(config_path, base_path, costfun, params_json, runtimeparams_json, 
                                               action, logger, get_data_from_file=True)
diff --git a/tests/test_machine_learning_forecaster.py b/tests/test_machine_learning_forecaster.py
index 05c211a4..d9ace8e4 100644
--- a/tests/test_machine_learning_forecaster.py
+++ b/tests/test_machine_learning_forecaster.py
@@ -58,7 +58,7 @@ def setUp(self):
         }
         runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
-        params['optim_conf'][8]['load_forecast_method'] = 'skforecast'
+        params['optim_conf']['load_forecast_method'] = 'skforecast'
         params_json = json.dumps(params)
         self.input_data_dict = set_input_data_dict(config_path, base_path, costfun, params_json, runtimeparams_json, 
                                                    action, logger, get_data_from_file=True)
diff --git a/tests/test_optimization.py b/tests/test_optimization.py
index a41d2c00..982d6494 100644
--- a/tests/test_optimization.py
+++ b/tests/test_optimization.py
@@ -234,9 +234,11 @@ def test_perform_naive_mpc_optim(self):
         soc_init = 0.4
         soc_final = 0.6
         def_total_hours = [2, 3]
+        def_start_timestep = [-5, 0]
+        def_end_timestep = [4, 0]
         self.opt_res_dayahead = self.opt.perform_naive_mpc_optim(
             self.df_input_data_dayahead, self.P_PV_forecast, self.P_load_forecast, prediction_horizon,
-            soc_init=soc_init, soc_final=soc_final, def_total_hours=def_total_hours)
+            soc_init=soc_init, soc_final=soc_final, def_total_hours=def_total_hours, def_start_timestep=def_start_timestep, def_end_timestep=def_end_timestep)
         self.assertIsInstance(self.opt_res_dayahead, type(pd.DataFrame()))
         self.assertTrue('P_batt' in self.opt_res_dayahead.columns)
         self.assertTrue('SOC_opt' in self.opt_res_dayahead.columns)
@@ -248,7 +250,7 @@ def test_perform_naive_mpc_optim(self):
         soc_final = 0.5
         self.opt_res_dayahead = self.opt.perform_naive_mpc_optim(
             self.df_input_data_dayahead, self.P_PV_forecast, self.P_load_forecast, prediction_horizon,
-            soc_init=soc_init, soc_final=soc_final, def_total_hours=def_total_hours)
+            soc_init=soc_init, soc_final=soc_final, def_total_hours=def_total_hours, def_start_timestep=def_start_timestep, def_end_timestep=def_end_timestep)
         self.assertAlmostEqual(self.opt_res_dayahead.loc[self.opt_res_dayahead.index[-1],'SOC_opt'], soc_final)
         
         
diff --git a/tests/test_retrieve_hass.py b/tests/test_retrieve_hass.py
index 4b0c7d75..ca7dd7ab 100644
--- a/tests/test_retrieve_hass.py
+++ b/tests/test_retrieve_hass.py
@@ -75,10 +75,10 @@ def test_yaml_parse_wab_server(self):
         params['optim_conf'] = optim_conf
         params['plant_conf'] = plant_conf
         # Just check forecast methods
-        self.assertTrue(list(params['optim_conf'][7].keys())[0] == 'weather_forecast_method')
-        self.assertTrue(list(params['optim_conf'][8].keys())[0] == 'load_forecast_method')
-        self.assertTrue(list(params['optim_conf'][9].keys())[0] == 'load_cost_forecast_method')
-        self.assertTrue(list(params['optim_conf'][13].keys())[0] == 'prod_price_forecast_method')
+        self.assertFalse(params['optim_conf'].get('weather_forecast_method') == None)
+        self.assertFalse(params['optim_conf'].get('load_forecast_method') == None)
+        self.assertFalse(params['optim_conf'].get('load_cost_forecast_method') == None)
+        self.assertFalse(params['optim_conf'].get('prod_price_forecast_method') == None)
 
     def test_get_data_failed(self):
         days_list = get_days_list(1)
@@ -170,4 +170,4 @@ def test_publish_data(self):
 if __name__ == '__main__':
     unittest.main()
     ch.close()
-    logger.removeHandler(ch)
\ No newline at end of file
+    logger.removeHandler(ch)
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 430a6897..d8367c51 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -40,10 +40,10 @@ def setUp(self):
         }
         self.runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
-        params['optim_conf'][7]['weather_forecast_method'] = 'list'
-        params['optim_conf'][8]['load_forecast_method'] = 'list'
-        params['optim_conf'][9]['load_cost_forecast_method'] = 'list'
-        params['optim_conf'][13]['prod_price_forecast_method'] = 'list'
+        params['optim_conf']['weather_forecast_method'] = 'list'
+        params['optim_conf']['load_forecast_method'] = 'list'
+        params['optim_conf']['load_cost_forecast_method'] = 'list'
+        params['optim_conf']['prod_price_forecast_method'] = 'list'
         self.params_json = json.dumps(params)
         
     def test_get_yaml_parse(self):
@@ -178,10 +178,10 @@ def test_treat_runtimeparams_failed(self):
         }
         runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
-        params['optim_conf'][7]['weather_forecast_method'] = 'list'
-        params['optim_conf'][8]['load_forecast_method'] = 'list'
-        params['optim_conf'][9]['load_cost_forecast_method'] = 'list'
-        params['optim_conf'][13]['prod_price_forecast_method'] = 'list'
+        params['optim_conf']['weather_forecast_method'] = 'list'
+        params['optim_conf']['load_forecast_method'] = 'list'
+        params['optim_conf']['load_cost_forecast_method'] = 'list'
+        params['optim_conf']['prod_price_forecast_method'] = 'list'
         params_json = json.dumps(params)
         retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(
             pathlib.Path(root+'/config_emhass.yaml'), use_secrets=True, params=params_json)
@@ -204,10 +204,10 @@ def test_treat_runtimeparams_failed(self):
         }
         runtimeparams_json = json.dumps(runtimeparams)
         params['passed_data'] = runtimeparams
-        params['optim_conf'][7]['weather_forecast_method'] = 'list'
-        params['optim_conf'][8]['load_forecast_method'] = 'list'
-        params['optim_conf'][9]['load_cost_forecast_method'] = 'list'
-        params['optim_conf'][13]['prod_price_forecast_method'] = 'list'
+        params['optim_conf']['weather_forecast_method'] = 'list'
+        params['optim_conf']['load_forecast_method'] = 'list'
+        params['optim_conf']['load_cost_forecast_method'] = 'list'
+        params['optim_conf']['prod_price_forecast_method'] = 'list'
         params_json = json.dumps(params)
         retrieve_hass_conf, optim_conf, plant_conf = utils.get_yaml_parse(
             pathlib.Path(root+'/config_emhass.yaml'), use_secrets=True, params=params_json)