Skip to content

Commit

Permalink
Merge pull request #74 from jmcollin78/issue_70-Add-min_on_time_per_d…
Browse files Browse the repository at this point in the history
…ay_min

Issue #70 add min on time per day min
  • Loading branch information
jmcollin78 authored Nov 13, 2024
2 parents f442eab + 254cf26 commit d746284
Show file tree
Hide file tree
Showing 19 changed files with 801 additions and 51 deletions.
4 changes: 4 additions & 0 deletions .devcontainer/configuration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ solar_optimizer:
activation_service: "input_boolean/turn_on"
deactivation_service: "input_boolean/turn_off"
max_on_time_per_day_min: 10
min_on_time_per_day_min: 5
offpeak_time: "14:00"
- name: "Equipement B"
entity_id: "input_boolean.fake_device_b"
power_max: 500
Expand All @@ -170,6 +172,8 @@ solar_optimizer:
action_mode: "service_call"
activation_service: "input_boolean/turn_on"
deactivation_service: "input_boolean/turn_off"
min_on_time_per_day_min: 15
offpeak_time: "13:50"
- name: "Equipement C"
entity_id: "input_boolean.fake_device_c"
power_max: 800
Expand Down
24 changes: 21 additions & 3 deletions README-fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@


> ![Nouveau](https://github.com/jmcollin78/solar_optimizer/blob/main/images/new-icon.png?raw=true) _*Nouveautés*_
> * **release 2.1.0** :
> - ajout d'une durée minimale d'allumage en heure creuses. Permet de gérer les équipements qui doivent avoir un minimum d'allumage par jour comme les chauffes-eau ou les chargeurs (voitures, batteries, ……). Si l'ensoleillement n'a pas durée d'atteindre la durée requise, alors l'équipement s'allumera pendant les heures creuses. Vous pouvez en plus définir à quelle heure les compteurs d'allumage sont remis à zéro ce qui permet de profiter des toutes les heures creuses
> * **release 2.0.0** :
> - ajout d'un appareil (device) par équipement piloté pour regrouper les entités,
> - ajout d'un compteur de temps d'allumage pour chaque appareil. Lorsque le switch commandé passe à 'Off', le compteur de temps est incrémenté du temps passé à 'On', en secondes. Ce compteur est remis à zéro tous les jours à minuit.
Expand Down Expand Up @@ -70,7 +72,9 @@ Si une batterie est spécifiée lors du paramétrage de l'intégration et si le

Un temps d'utilisation maximal journalier est paramétrable en facultatif. Si il est valorisé et si la durée d'utilisation de l'équipement est dépasée, alors l'équipement ne sera pas utilisable par l'algorithme et laisse donc de la puissance pour les autres équipements.

Ces 4 règles permettent à l'algorithme de ne commander que ce qui est réellement utile à un instant t. Ces règles sont ré-évaluées à chaque cycle.
Un temps d'utilisation minimal journalier est aussi paramétrable en facultatif. Ce paramètre permet d'assurer que l'équipement sera allumé pendant une certaine durée minimale. Vous spécifiez à quelle heure commence les heures creuses, (`offpeak_time`) et la durée minimale en minutes (`min_on_time_per_day_min`). Si à l'heure indiquée par `offpeak_time`, la durée minimale d'activation n'a pas été atteinte, alors l'équipement est activé jusqu'au changement de jour (paramètrable dans l'intégration et 05:00 par défaut) ou jusqu'à ce que le maximum d'utilisation soit atteint (`max_on_time_per_day_min`) ou pendant toute la durée des heures creuses si `max_on_time_per_day_min` n'est pas précisé. Vous assurez ainsi que le chauffe-eau ou la voiture sera chargée le lendemain matin même si la production solaire n'a pas permise de recharger l'appareil. A vous d'inventer les usages de cette fonction.

Ces 5 règles permettent à l'algorithme de ne commander que ce qui est réellement utile à un instant t. Ces règles sont ré-évaluées à chaque cycle.

# Comment on l'installe ?
## HACS installation (recommendé)
Expand All @@ -92,8 +96,10 @@ Vous devez spécifier :
1. le sensor qui donne la consommation nette instantanée du logement (elle doit être négative si la production dépasse la consommation). Ce chiffre est indiqué en Watt,
2. le sensor qui donne la production photovoltaïque instantanée en Watt aussi,
3. un sensor ou input_number qui donne le cout du kwh importée,
3. un sensor ou input_number qui donne le prix du kwh exortée (dépend de votre contrat),
3. un sensor ou input_number qui donne la taxe applicable sur les kwh exortée (dépend de votre contrat)
4. un sensor ou input_number qui donne le prix du kwh exortée (dépend de votre contrat),
5. un sensor ou input_number qui donne la taxe applicable sur les kwh exortée (dépend de votre contrat)
6. l'heure de début de journée. A cette heure les compteurs d'uitlisation des équipements sont remis à zéro. La valeur par défaut est 05:00. Elle doit être avant la première production et le plus tard possible pour les activations en heures creuses. Cf. ci-dessus.


Ces 5 informations sont nécessaires à l'algorithme pour fonctionner, elles sont donc toutes obligatoires. Le fait que ce soit des sensor ou input_number permet d'avoir des valeurs qui sont réévaluées à chaque cycle. En conséquence le passage en heure creuse peut modifier le calcul et donc les états des équipements puisque l'import devient moins cher. Donc tout est dynamique et recalculé à chaque cycle.

Expand Down Expand Up @@ -121,6 +127,8 @@ devices:
deactivation_service: "switch/turn_off"
battery_soc_threshold: <l'état de charge minimal pour utiliser cet équipement>
max_on_time_per_day_min: <la durée maximamle d'allumage par jour en minutes>
offpeak_time: <l'heure de début des heures creuses>
min_on_day_per_day_min: <la durée minimale d'allumage par jour en minutes>
```

Note: les paramètres sous `algorithm` ne doivent pas être touchés sauf si vous savez exactement ce que vous faites.
Expand All @@ -140,6 +148,8 @@ Sous `devices` il faut déclarer tous les équipements qui seront commandés par
| `deactivation_service` | uniquement si action_mode="service_call" | le service a appeler pour désactiver l'équipement sous la forme "domain/service" | "switch/turn_off" | la désactivation déclenchera le service "switch/turn_off" sur l'entité "entity_id" |
| `battery_soc_threshold` | tous | le pourcentage minimal de charge de la batterie pour que l'équipement soit utilisable | 30 | |
| `max_on_time_per_day_min` | tous | le nombre de minutes maximal en position allumé pour cet équipement. Au delà, l'équipement n'est plus utilisable par l'algorithme | 10 | L'équipement est sera allumé au maximum 10 minutes par jour |
| `offpeak_time` | tous | L'heure de début des heures creuses au format hh:mm | 22:00 | L'équipement pourra être allumé à 22h00 si la production de la journeé n'a pas été suffisante |
| `min_on_time_per_day_min` | tous | le nombre de minutes minimale en position allumé pour cet équipement. Si lors du démarrage des heures creuses, ce minimum n'est pas atteint alors l'équipement sera allumé à concurrence du début de journée ou du `max_on_time_per_day_min` | 5 | L'équipement est sera allumé au minimum 5 minutes par jour |

Pour les équipements à puissance variable, les attributs suivants doivent être valorisés :

Expand Down Expand Up @@ -175,6 +185,10 @@ devices:
battery_soc_threshold: 10
# Une heure par jour maximum
max_on_time_per_day_min: 60
# 1/2h par jour minimum ...
min_on_time_per_day_min: 30
# ... à partir de 22:30
offpeak_time: "22:30"
- name: "Recharge Tesla"
entity_id: "switch.testla_charger"
Expand Down Expand Up @@ -204,6 +218,10 @@ devices:
convert_power_divide_factor: 660
# On ne démarre pas une charge si la batterie de l'installation solaire n'est pas chargée à au moins 50%
battery_soc_threshold: 50
# 4h par jour minimum ...
min_on_time_per_day_min: 240
# ... à partir de 23:00
offpeak_time: "22:00"
...
```
Tout changement dans la configuration nécessite un arrêt / relance de l'intégration (ou de Home Assistant) pour être pris en compte.
Expand Down
26 changes: 21 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@


>![New](https://github.com/jmcollin78/solar_optimizer/blob/main/images/new-icon.png?raw=true) _*News*_
> * **release 2.0.0** :
> * **release 2.1.0** :
> - added a minimum duration of ignition during off-peak hours. Allows you to manage equipment that must have a minimum of ignition per day such as water heaters or chargers (cars, battery, ...). If the sunshine has not reached the required duration, then the equipment will turn on during off-peak hours. You can also define at what time the ignition counters are reset to zero, which allows you to take advantage of all off-peak hours> * **release 2.0.0** :
> - added a device per controlled equipment to group the entities,
> - added an ignition time counter for each device. When the controlled switch goes to 'Off', the time counter is incremented by the time spent 'On', in seconds. This counter is reset to zero every day at midnight.
> - added a maximum time to 'On' in the configuration (in minutes). When this duration is exceeded, the equipment is no longer usable by the algorithm (is_usable = off) until the next reset. This offers the possibility of not exceeding a maximum ignition time per day, even when solar power is available.
Expand Down Expand Up @@ -70,7 +71,9 @@ If a battery is specified when configuring the integration and if the threshold

A maximum daily usage time is optionally configurable. If it is valued and if the duration of use of the equipment is exceeded, then the equipment will not be usable by the algorithm and therefore leaves power for other equipment.

These 4 rules allow the algorithm to only order what is really useful at a time t. These rules are re-evaluated at each cycle.
A minimum daily usage time is also optionally configurable. This parameter ensures that the equipment will be on for a certain minimum duration. You specify at what time the off-peak hours start (`offpeak_time`) and the minimum duration in minutes (`min_on_time_per_day_min`). If at the time indicated by `offpeak_time`, the minimum activation duration has not been reached, then the equipment is activated until the change of day (configurable in the integration and 05:00 by default) or until the maximum usage is reached (`max_on_time_per_day_min`) or during all the off-peak hours if `max_on_time_per_day_min` is not set. This ensures that the water heater or the car will be charged the next morning even if the solar production has not allowed the device to be recharged. It is up to you to invent the uses of this function.

These 5 rules allow the algorithm to only order what is really useful at a time t. These rules are re-evaluated at each cycle.

# How do we install it?
## HACS installation (recommended)
Expand All @@ -92,8 +95,9 @@ You must specify:
1. the sensor which gives the instantaneous net consumption of the dwelling (it must be negative if production exceeds consumption). This figure is indicated in Watt,
2. the sensor which gives the instantaneous photovoltaic production in Watt too,
3. a sensor or input_number which gives the cost of the imported kwh,
3. a sensor or input_number which gives the price of the exported kwh (depends on your contract),
3. a sensor or input_number which gives the applicable tax on the exported kwh (depends on your contract)
4. a sensor or input_number which gives the price of the exported kwh (depends on your contract),
5. a sensor or input_number which gives the applicable tax on the exported kwh (depends on your contract)
6. the start time of the day. At this time the equipment usage counters are reset to zero. The default value is 05:00. It must be before the first production and as late as possible for activations during off-peak hours. See above.

These 5 pieces of information are necessary for the algorithm to work, so they are all mandatory. The fact that they are sensors or input_number allows to have values that are re-evaluated at each cycle. Consequently, switching to off-peak hours can modify the calculation and therefore the states of the equipment since the import becomes less expensive. So everything is dynamic and recalculated at each cycle.

Expand Down Expand Up @@ -121,6 +125,8 @@ devices:
deactivation_service: "<service name>"
battery_soc_threshold: <the state of charge minimal to use this device>
max_on_time_per_day_min: <the maximum time on 'on' per day in minutes>
offpeak_time: <start time of off-peak hours>
min_on_day_per_day_min: <the minimal time on 'on' per day in minutes>
```

Note: parameters under `algorithm` should not be touched unless you know exactly what you are doing.
Expand All @@ -140,6 +146,8 @@ Under `devices` you must declare all the equipment that will be controlled by So
| `deactivation_service` | only if action_mode="service_call" | the service to call to deactivate the equipment in the form "domain/service" | "switch/turn_off" | deactivation will trigger the "switch/turn_off" service on the entity "entity_id" |
| `battery_soc_threshold` | tous | minimal percentage of charge of the solar battery to enable this device | 30 | |
| `max_on_time_per_day_min` | all | the maximum number of minutes in the on position for this equipment. Beyond that, the equipment is no longer usable by the algorithm | 10 | The equipment will be on for a maximum of 10 minutes per day |
| `offpeak_time` | all | The start time of off-peak hours in hh:mm format | 22:00 | The equipment can be switched on at 22:00 if the production of the day has not been sufficient |
| `min_on_time_per_day_min` | all | the minimum number of minutes in the on position for this equipment. If at the start of off-peak hours, this minimum is not reached then the equipment will be switched on up to the start of the day or the `max_on_time_per_day_min` | 5 | The equipment will be switched on for a minimum of 5 minutes per day |

For variable power equipment, the following attributes must be valued:

Expand Down Expand Up @@ -174,7 +182,11 @@ devices:
# We authorize the pump to start if there is 10% battery in the solar installation
battery_soc_threshold: 10
# One hour per day maximum
max_on_time_per_day_min: 60
max_on_time_per_day_min: 60
# 1/2h per day minimum ...
min_on_time_per_day_min: 30
# ... starting at 22:30
offpeak_time: "22:30"
- name: "Tesla Recharge"
entity_id: "switch.cloucloute_charger"
Expand Down Expand Up @@ -204,6 +216,10 @@ devices:
convert_power_divide_factor: 660
# We do not start a charge if the battery of the solar installation is not at least 50% charged
battery_soc_threshold: 50
# 4h par day minimum ...
min_on_time_per_day_min: 240
# ... starting at 23:00
offpeak_time: "22:00"
...
```
Any change in the configuration requires a stop / restart of the integration (or of Home Assistant) to be taken into account.
Expand Down
5 changes: 4 additions & 1 deletion custom_components/solar_optimizer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component


from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.typing import ConfigType
import homeassistant.helpers.config_validation as cv
Expand All @@ -29,7 +30,7 @@
# from homeassistant.helpers.entity_component import EntityComponent


from .const import DOMAIN, PLATFORMS
from .const import DOMAIN, PLATFORMS, SERVICE_RESET_ON_TIME, validate_time_format
from .coordinator import SolarOptimizerCoordinator

# from .input_boolean import async_setup_entry as async_setup_entry_input_boolean
Expand Down Expand Up @@ -90,6 +91,8 @@
"battery_soc_threshold", default=0
): vol.Coerce(float),
vol.Optional("max_on_time_per_day_min"): vol.Coerce(int),
vol.Optional("min_on_time_per_day_min"): vol.Coerce(int),
vol.Optional("offpeak_time"): validate_time_format,
}
]
),
Expand Down
46 changes: 44 additions & 2 deletions custom_components/solar_optimizer/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import logging
import voluptuous as vol


from homeassistant.core import callback
from homeassistant.config_entries import (
ConfigFlow,
Expand All @@ -15,11 +14,13 @@
from homeassistant.components.input_number import DOMAIN as INPUT_NUMBER_DOMAIN
from homeassistant.helpers import selector
import homeassistant.helpers.config_validation as cv
from homeassistant.exceptions import HomeAssistantError

from .const import DOMAIN
from .const import DOMAIN, validate_time_format, DEFAULT_RAZ_TIME

_LOGGER = logging.getLogger(__name__)


solar_optimizer_schema = {
vol.Required("refresh_period_sec", default=300): int,
vol.Required("power_consumption_entity_id"): selector.EntitySelector(
Expand All @@ -41,6 +42,7 @@
vol.Optional("battery_soc_entity_id"): selector.EntitySelector(
selector.EntitySelectorConfig(domain=[SENSOR_DOMAIN, INPUT_NUMBER_DOMAIN])
),
vol.Optional("raz_time", default=DEFAULT_RAZ_TIME): str,
}


Expand All @@ -67,6 +69,19 @@ async def async_step_user(self, user_input: dict | None = None) -> FlowResult:
)
return self.async_show_form(step_id="user", data_schema=user_form)

try:
validate_time_format(user_input.get("raz_time"))
except vol.Invalid:
errors = {"raz_time": "format_time_invalid"}
return self.async_show_form(
step_id="user",
data_schema=self.add_suggested_values_to_schema(
data_schema=user_form,
suggested_values=user_input,
),
errors=errors,
)

# 2ème appel : il y a des user_input -> on stocke le résultat
self._user_inputs.update(user_input)
_LOGGER.debug(
Expand All @@ -82,6 +97,10 @@ def async_get_options_flow(config_entry: ConfigEntry):
"""Get options flow for this handler"""
return SolarOptimizerOptionsFlow(config_entry)

async def validate_input(self, data: dict) -> None:
"""Validate the user input allows us to connect."""
validate_time_format(data.get("raz_time"))


class SolarOptimizerOptionsFlow(OptionsFlow):
"""The class which enable to modified the configuration"""
Expand All @@ -94,6 +113,8 @@ def __init__(self, config_entry: ConfigEntry) -> None:
self.config_entry = config_entry
# On initialise les user_inputs avec les données du configEntry
self._user_inputs = config_entry.data.copy()
if self._user_inputs.get("raz_time") is None:
self._user_inputs["raz_time"] = DEFAULT_RAZ_TIME

async def async_step_init(self, user_input: dict | None = None) -> FlowResult:
"""Gestion de l'étape 'user'. Point d'entrée de notre
Expand All @@ -115,6 +136,20 @@ async def async_step_init(self, user_input: dict | None = None) -> FlowResult:
),
)

try:
validate_time_format(user_input.get("raz_time"))
except vol.Invalid:
errors = {"raz_time": "format_time_invalid"}

return self.async_show_form(
step_id="init",
data_schema=self.add_suggested_values_to_schema(
data_schema=user_form,
suggested_values=user_input,
),
errors=errors,
)

# 2ème appel : il y a des user_input -> on stocke le résultat
self._user_inputs.update(user_input)
_LOGGER.debug(
Expand All @@ -140,3 +175,10 @@ async def async_end(self):
)
# Suppression de l'objet options dans la configEntry
return self.async_create_entry(title=None, data=None)

def validate_input(self, data: dict) -> None:
"""Validate the user input allows us to connect."""
try:
validate_time_format(data.get("raz_time"))
except vol.Invalid as err:
raise HomeAssistantError("raz_time") from err
Loading

0 comments on commit d746284

Please sign in to comment.