Skip to content

Commit

Permalink
Add testu
Browse files Browse the repository at this point in the history
  • Loading branch information
Jean-Marc Collin committed Oct 1, 2024
1 parent 6becf43 commit 6556271
Show file tree
Hide file tree
Showing 5 changed files with 254 additions and 57 deletions.
31 changes: 19 additions & 12 deletions custom_components/solar_optimizer/managed_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ class ManagedDevice:

def __init__(self, hass: HomeAssistant, device_config):
"""Initialize a manageable device"""
self._now = None # For testing purpose only
self._current_tz = get_tz(hass)

self._hass = hass
self._name = device_config.get("name")
self._unique_id = name_to_unique_id(self._name)
Expand Down Expand Up @@ -169,9 +172,7 @@ def __init__(self, hass: HomeAssistant, device_config):
"{{ is_state('" + self._entity_id + "', '" + STATE_ON + "') }}"
)
self._check_active_template = Template(template_string, hass)
self._next_date_available_power = self._next_date_available = datetime.now(
get_tz(hass)
)
self._next_date_available_power = self._next_date_available = self.now
self._action_mode = device_config.get("action_mode")
self._activation_service = device_config.get("activation_service")
self._deactivation_service = device_config.get("deactivation_service")
Expand Down Expand Up @@ -264,11 +265,9 @@ async def change_requested_power(self, requested_power):
def reset_next_date_available(self, action_type):
"""Incremente the next availability date to now + _duration_sec"""
if action_type == ACTION_ACTIVATE:
self._next_date_available = datetime.now(get_tz(self._hass)) + timedelta(
seconds=self._duration_sec
)
self._next_date_available = self.now + timedelta(seconds=self._duration_sec)
else:
self._next_date_available = datetime.now(get_tz(self._hass)) + timedelta(
self._next_date_available = self.now + timedelta(
seconds=self._duration_stop_sec
)

Expand All @@ -278,7 +277,7 @@ def reset_next_date_available(self, action_type):

def reset_next_date_available_power(self):
"""Incremente the next availability date for power change to now + _duration_power_sec"""
self._next_date_available_power = datetime.now(get_tz(self._hass)) + timedelta(
self._next_date_available_power = self.now + timedelta(
seconds=self._duration_power_sec
)
_LOGGER.debug(
Expand Down Expand Up @@ -367,7 +366,7 @@ def is_usable(self) -> bool:
result = False
else:
context = {}
now = datetime.now(get_tz(self._hass))
now = self.now
result = self._check_usable_template.async_render(context) and (
now >= self._next_date_available
or (self._can_change_power and now >= self._next_date_available_power)
Expand All @@ -394,8 +393,7 @@ def is_usable(self) -> bool:
@property
def is_waiting(self):
"""A device is waiting if the device is waiting for the end of its cycle"""
now = datetime.now(get_tz(self._hass))
result = now < self._next_date_available
result = self.now < self._next_date_available

if result:
_LOGGER.debug("%s is waiting", self._name)
Expand Down Expand Up @@ -497,7 +495,6 @@ def battery_soc_threshold(self) -> int:
"""The battery soc"""
return self._battery_soc_threshold


def set_battery_soc(self, battery_soc):
"""Define the battery soc. This is used with is_usable
to determine if the device is usable"""
Expand All @@ -516,3 +513,13 @@ def publish_enable_state_change(self) -> None:
"is_waiting": self.is_waiting,
},
)

# For testing purpose only
def _set_now(self, now: datetime):
"""Set the now timestamp. This is only for tests purpose"""
self._now = now

@property
def now(self) -> datetime:
"""Get now. The local datetime or the overloaded _set_now date"""
return self._now if self._now is not None else datetime.now(self._current_tz)
19 changes: 14 additions & 5 deletions custom_components/solar_optimizer/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
DOMAIN,
DEVICE_MANUFACTURER,
INTEGRATION_MODEL,
get_tz,
name_to_unique_id,
DEVICE_MODEL,
seconds_to_hms,
Expand Down Expand Up @@ -192,7 +191,7 @@ async def async_added_to_hass(self) -> None:
# Add listener to midnight to reset the counter
self.async_on_remove(
async_track_time_change(
hass=self.hass, action=self._on_midnight, hour=8, minute=30, second=0
hass=self.hass, action=self._on_midnight, hour=0, minute=0, second=0
)
)

Expand Down Expand Up @@ -222,7 +221,7 @@ async def async_added_to_hass(self) -> None:
@callback
async def _on_state_change(self, event: Event) -> None:
"""The entity have change its state"""
now = datetime.now(tz=get_tz(self.hass))
now = self._device.now
_LOGGER.info("Call of on_state_change at %s with event %s", now, event)

if not event.data:
Expand Down Expand Up @@ -268,7 +267,7 @@ async def _on_midnight(self, _=None) -> None:
# reset _last_datetime_on to now if it was active. Here we lose the time on of yesterday but it is too late I can't do better.
# Else you will have two point with the same date and not the same value (one with value + duration and one with 0)
if self._last_datetime_on is not None:
self._last_datetime_on = datetime.now(tz=get_tz(self.hass))
self._last_datetime_on = self._device.now

self.update_custom_attributes()
self.async_write_ha_state()
Expand All @@ -277,7 +276,7 @@ async def _on_midnight(self, _=None) -> None:
@callback
async def _on_update_on_time(self, _=None) -> None:
"""Called priodically to update the on_time sensor"""
now = datetime.now(tz=get_tz(self.hass))
now = self._device.now
_LOGGER.info("Call of _on_update_on_time at %s", now)

if self._last_datetime_on is not None:
Expand Down Expand Up @@ -330,3 +329,13 @@ def native_unit_of_measurement(self) -> str | None:
def suggested_display_precision(self) -> int | None:
"""Return the suggested number of decimal digits for display."""
return 0

@property
def last_datetime_on(self) -> datetime | None:
"""Returns the last_datetime_on"""
return self._last_datetime_on

@property
def get_attr_extra_state_attributes(self):
"""Get the extra state attributes for the entity"""
return self._attr_extra_state_attributes
76 changes: 39 additions & 37 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,45 +55,46 @@ def define_config_2_devices():
""" Define a configuration with 2 devices. One with power and the other without power """

return {
"solar_optimizer": {
"algorithm": {
"initial_temp": 1000,
"min_temp": 0.1,
"cooling_factor": 0.95,
"max_iteration_number": 1000,
"solar_optimizer": {
"algorithm": {
"initial_temp": 1000,
"min_temp": 0.1,
"cooling_factor": 0.95,
"max_iteration_number": 1000,
},
"devices": [
{
"name": "Equipement A",
"entity_id": "input_boolean.fake_device_a",
"power_max": 1000,
"check_usable_template": "{{ True }}",
"duration_min": 0.3,
"duration_stop_min": 0.1,
"action_mode": "service_call",
"activation_service": "input_boolean/turn_on",
"deactivation_service": "input_boolean/turn_off",
"max_on_time_per_day_min": 10,
},
"devices": [
{
"name": "Equipement A",
"entity_id": "input_boolean.fake_device_a",
"power_max": 1000,
"check_usable_template": "{{ True }}",
"duration_min": 0.3,
"duration_stop_min": 0.1,
"action_mode": "service_call",
"activation_service": "input_boolean/turn_on",
"deactivation_service": "input_boolean/turn_off",
},
{
"name": "Equipement B",
"entity_id": "input_boolean.fake_device_b",
"power_max": 2000,
"power_min": 100,
"power_step": 150,
"check_usable_template": "{{ False }}",
"duration_min": 1,
"duration_stop_min": 2,
"duration_power_min": 3,
"action_mode": "event",
"convert_power_divide_factor": 6,
"change_power_service": "input_number/set_value",
"power_entity_id": "input_number.tesla_amps",
"activation_service": "input_boolean/turn_on",
"deactivation_service": "input_boolean/turn_off"
}
],
}
{
"name": "Equipement B",
"entity_id": "input_boolean.fake_device_b",
"power_max": 2000,
"power_min": 100,
"power_step": 150,
"check_usable_template": "{{ False }}",
"duration_min": 1,
"duration_stop_min": 2,
"duration_power_min": 3,
"action_mode": "event",
"convert_power_divide_factor": 6,
"change_power_service": "input_number/set_value",
"power_entity_id": "input_number.tesla_amps",
"activation_service": "input_boolean/turn_on",
"deactivation_service": "input_boolean/turn_off",
},
],
}
}

@pytest.fixture(name="init_solar_optimizer_with_2_devices_power_not_power")
async def init_solar_optimizer_with_2_devices_power_not_power(hass, config_2_devices_power_not_power) -> SolarOptimizerCoordinator:
Expand Down Expand Up @@ -129,6 +130,7 @@ def define_config_2_devices_battery():
"activation_service": "input_boolean/turn_on",
"deactivation_service": "input_boolean/turn_off",
"battery_soc_threshold": 30,
"max_on_time_per_day_min": 10,
},
{
"name": "Equipement B",
Expand Down
Loading

0 comments on commit 6556271

Please sign in to comment.