Skip to content

Commit

Permalink
With Tesla amps date and new entites
Browse files Browse the repository at this point in the history
  • Loading branch information
Jean-Marc Collin committed Jun 12, 2023
1 parent 0e638e4 commit 602ab70
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 69 deletions.
11 changes: 8 additions & 3 deletions custom_components/solar_optimizer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

from .const import DOMAIN, PLATFORMS
from .coordinator import SolarOptimizerCoordinator
from .sensor import SolarOptimizerSensorEntity, async_setup_entry
from .sensor import async_setup_entry as async_setup_entry_sensor
from .binary_sensor import async_setup_entry as async_setup_entry_binary_sensor

_LOGGER = logging.getLogger(__name__)

Expand All @@ -28,15 +29,19 @@ async def async_setup(
# L'argument config contient votre fichier configuration.yaml
solar_optimizer_config = config.get(DOMAIN)

hass.data[DOMAIN]["coordinator"] = coordinator = SolarOptimizerCoordinator(hass, solar_optimizer_config)
hass.data[DOMAIN]["coordinator"] = coordinator = SolarOptimizerCoordinator(
hass, solar_optimizer_config
)

await coordinator.async_config_entry_first_refresh()

await async_setup_entry(hass)
await async_setup_entry_sensor(hass)
await async_setup_entry_binary_sensor(hass)

# Return boolean to indicate that initialization was successful.
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Handle removal of an entry."""
if unloaded := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
Expand Down
84 changes: 84 additions & 0 deletions custom_components/solar_optimizer/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
""" A bonary sensor entity that holds the state of each managed_device """
import logging
from homeassistant.core import callback, HomeAssistant
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.components.binary_sensor import (
BinarySensorEntity,
DOMAIN as BINARY_SENSOR_DOMAIN,
)
from .const import DOMAIN, name_to_unique_id, get_tz
from .coordinator import SolarOptimizerCoordinator
from .managed_device import ManagedDevice

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(hass: HomeAssistant) -> None:
"""Setup the entries of type Binary sensor, one for each ManagedDevice"""
coordinator: SolarOptimizerCoordinator = hass.data[DOMAIN]["coordinator"]

entities = []
for _, device in enumerate(coordinator.devices):
entity = ManagedDeviceBinarySensor(
coordinator, hass, device.name, name_to_unique_id(device.name)
)
if entity is not None:
entities.append(entity)

component: EntityComponent[BinarySensorEntity] = hass.data.get(BINARY_SENSOR_DOMAIN)
if component is None:
component = hass.data[BINARY_SENSOR_DOMAIN] = EntityComponent[
BinarySensorEntity
](_LOGGER, BINARY_SENSOR_DOMAIN, hass)
await component.async_add_entities(entities)


class ManagedDeviceBinarySensor(CoordinatorEntity, BinarySensorEntity):
"""The entity holding the algorithm calculation"""

def __init__(self, coordinator, hass, name, idx):
super().__init__(coordinator, context=idx)
self._hass = hass
self.idx = idx
self._attr_name = name
self._attr_unique_id = "solar_optimizer_" + idx

self._attr_is_on = None

def update_custom_attributes(self, device):
"""Add some custom attributes to the entity"""
current_tz = get_tz(self._hass)
self._attr_extra_state_attributes: dict(str, str) = {
"is_active": device.is_active,
"is_waiting": device.is_waiting,
"is_usable": device.is_usable,
"can_change_power": device.can_change_power,
"current_power": device.current_power,
"requested_power": device.requested_power,
"next_date_available": device.next_date_available.astimezone(
current_tz
).isoformat(),
"next_date_available_power": device.next_date_available_power.astimezone(
current_tz
).isoformat(),
}

@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
device: ManagedDevice = self.coordinator.data[self.idx]
if not device:
return
self._attr_is_on = device.is_active
self.update_custom_attributes(device)
self.async_write_ha_state()

@property
def device_info(self):
# Retournez des informations sur le périphérique associé à votre entité
return {
"identifiers": {(DOMAIN, "solar_optimizer_device")},
"name": "Solar Optimizer",
# Autres attributs du périphérique ici
}
8 changes: 7 additions & 1 deletion custom_components/solar_optimizer/const.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
""" Les constantes pour l'intégration Tuto HACS """
from slugify import slugify

from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.util import dt as dt_util

DOMAIN = "solar_optimizer"
PLATFORMS: list[Platform] = [Platform.SENSOR]
PLATFORMS: list[Platform] = [Platform.SENSOR, Platform.BINARY_SENSOR]

DEFAULT_REFRESH_PERIOD_SEC = 300

Expand All @@ -24,6 +25,11 @@ def get_tz(hass: HomeAssistant):
return dt_util.get_time_zone(hass.config.time_zone)


def name_to_unique_id(name: str) -> str:
"""Convert a name to a unique id. Replace ' ' by _"""
return slugify(name).replace("-", "_")


class ConfigurationError(Exception):
"""An error in configuration"""

Expand Down
108 changes: 64 additions & 44 deletions custom_components/solar_optimizer/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# UpdateFailed,
)

from .const import DEFAULT_REFRESH_PERIOD_SEC
from .const import DEFAULT_REFRESH_PERIOD_SEC, name_to_unique_id
from .managed_device import ManagedDevice
from .simulated_annealing_algo import SimulatedAnnealingAlgorithm

Expand Down Expand Up @@ -87,26 +87,33 @@ async def _async_update_data(self):

calculated_data = {}

device_states = {}
# device_states = {}
# Add a device state attributes
for _, device in enumerate(self._devices):
# Initialize current power if not set and is active

device_states[device.name] = {
"name": device.name,
"is_active": device.is_active,
"is_usable": device.is_usable,
"is_waiting": device.is_waiting,
"current_power": device.current_power,
"requested_power": device.requested_power,
}
_LOGGER.debug(
"Evaluation of %s, device_active: %s, device_usable: %s",
device.name,
device.is_active,
device.is_usable,
)
calculated_data["device_states"] = device_states
if device.is_active and device.current_power == 0:
device.init_power(
device.power_max if device.can_change_power else device.power_min
)
if not device.is_active:
device.init_power(0)

# calculate a device_states (not used)
# device_states[device.name] = {
# "name": device.name,
# "is_active": device.is_active,
# "is_usable": device.is_usable,
# "is_waiting": device.is_waiting,
# "current_power": device.current_power,
# "requested_power": device.requested_power,
# }
# _LOGGER.debug(
# "Evaluation of %s, device_active: %s, device_usable: %s",
# device.name,
# device.is_active,
# device.is_usable,
# )
# calculated_data["device_states"] = device_states

# Add a power_consumption and power_production
calculated_data["power_production"] = get_safe_float(
Expand Down Expand Up @@ -145,33 +152,46 @@ async def _async_update_data(self):
# Uses the result to turn on or off or change power
for _, equipement in enumerate(best_solution):
_LOGGER.debug("Dealing with best_solution for %s", equipement)
for _, device in enumerate(self._devices):
name = equipement["name"]
if device.name == name:
requested_power = equipement.get("requested_power")
state = equipement["state"]
is_active = device.is_active
if is_active and not state:
_LOGGER.debug("Extinction de %s", name)
await device.deactivate()
elif not is_active and state:
_LOGGER.debug("Allumage de %s", name)
await device.activate(requested_power)

# Send change power if state is now on and change power is accepted and (power have change or eqt is just activated)
if (
state
and device.can_change_power
and (device.current_power != requested_power or not is_active)
):
_LOGGER.debug(
"Change power of %s to %s",
equipement["name"],
requested_power,
)
await device.change_requested_power(requested_power)
break
name = equipement["name"]
requested_power = equipement.get("requested_power")
state = equipement["state"]
device = self.get_device_name(name)
calculated_data[name_to_unique_id(name)] = device
if not device:
continue
is_active = device.is_active
if is_active and not state:
_LOGGER.debug("Extinction de %s", name)
await device.deactivate()
elif not is_active and state:
_LOGGER.debug("Allumage de %s", name)
await device.activate(requested_power)

# Send change power if state is now on and change power is accepted and (power have change or eqt is just activated)
if (
state
and device.can_change_power
and (device.current_power != requested_power or not is_active)
):
_LOGGER.debug(
"Change power of %s to %s",
equipement["name"],
requested_power,
)
await device.change_requested_power(requested_power)

_LOGGER.debug("Calculated data are: %s", calculated_data)

return calculated_data

@property
def devices(self) -> list[ManagedDevice]:
"""Get all the managed device"""
return self._devices

def get_device_name(self, name: str) -> ManagedDevice | None:
"""Returns the device which name is given in argument"""
for _, device in enumerate(self._devices):
if device.name == name:
return device
return None
17 changes: 16 additions & 1 deletion custom_components/solar_optimizer/managed_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,8 @@ async def _apply_action(self, action_type: str, requested_power=None):
)

self._current_power = self._requested_power
self.reset_next_date_available()
if action_type == ACTION_ACTIVATE or action_type == ACTION_DEACTIVATE:
self.reset_next_date_available()

async def activate(self, requested_power=None):
"""Use this method to activate this ManagedDevice"""
Expand Down Expand Up @@ -255,6 +256,10 @@ def reset_next_date_available_power(self):
self._next_date_available_power,
)

def init_power(self, power: int):
"""Initialise current_power and requested_power to the given value"""
self._requested_power = self._current_power = power

@property
def is_active(self):
"""Check if device is active by getting the underlying state of the device"""
Expand Down Expand Up @@ -342,3 +347,13 @@ def requested_power(self) -> int:
def can_change_power(self) -> bool:
"""true is the device can change its power"""
return self._can_change_power

@property
def next_date_available(self) -> datetime:
"""true is the device can change its power"""
return self._next_date_available

@property
def next_date_available_power(self) -> datetime:
"""true is the device can change its power"""
return self._next_date_available_power
2 changes: 1 addition & 1 deletion custom_components/solar_optimizer/sensor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
""" A sensor entoy that holds the result of the recuit simule algorithm """
""" A sensor entity that holds the result of the recuit simule algorithm """
import logging
from homeassistant.core import callback, HomeAssistant
from homeassistant.helpers.update_coordinator import CoordinatorEntity
Expand Down
Loading

0 comments on commit 602ab70

Please sign in to comment.