-
-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8810dcc
commit ca42249
Showing
5 changed files
with
368 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
"""Number platform for EV Smart Charging.""" | ||
import logging | ||
|
||
from homeassistant.components.number import NumberEntity | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.entity import EntityCategory | ||
|
||
from .const import ( | ||
CONF_MAX_PRICE, | ||
CONF_MIN_SOC, | ||
CONF_PCT_PER_HOUR, | ||
DOMAIN, | ||
ENTITY_NAME_CONF_PCT_PER_HOUR_NUMBER, | ||
ENTITY_NAME_CONF_MAX_PRICE_NUMBER, | ||
ENTITY_NAME_CONF_MIN_SOC_NUMBER, | ||
ICON_BATTERY_50, | ||
ICON_CASH, | ||
NUMBER, | ||
) | ||
from .coordinator import EVSmartChargingCoordinator | ||
from .entity import EVSmartChargingEntity | ||
from .helpers.general import get_parameter | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
async def async_setup_entry( | ||
hass: HomeAssistant, entry, async_add_devices | ||
): # pylint: disable=unused-argument | ||
"""Setup number platform.""" | ||
_LOGGER.debug("EVSmartCharging.number.py") | ||
coordinator = hass.data[DOMAIN][entry.entry_id] | ||
numbers = [] | ||
numbers.append(EVSmartChargingNumberChargingSpeed(entry, coordinator)) | ||
numbers.append(EVSmartChargingNumberPriceLimit(entry, coordinator)) | ||
numbers.append(EVSmartChargingNumberMinSOC(entry, coordinator)) | ||
async_add_devices(numbers) | ||
|
||
|
||
class EVSmartChargingNumber(EVSmartChargingEntity, NumberEntity): | ||
"""EV Smart Charging switch class.""" | ||
|
||
_attr_native_value: float | None = None # To support HA 2022.7 | ||
|
||
def __init__(self, entry, coordinator: EVSmartChargingCoordinator): | ||
_LOGGER.debug("EVSmartChargingNumber.__init__()") | ||
super().__init__(entry) | ||
self.coordinator = coordinator | ||
id_name = self._attr_name.replace(" ", "").lower() | ||
self._attr_unique_id = ".".join([entry.entry_id, NUMBER, id_name]) | ||
|
||
async def async_set_native_value(self, value: float) -> None: | ||
"""Set new value.""" | ||
self._attr_native_value = value | ||
|
||
|
||
class EVSmartChargingNumberChargingSpeed(EVSmartChargingNumber): | ||
"""EV Smart Charging active switch class.""" | ||
|
||
_attr_name = ENTITY_NAME_CONF_PCT_PER_HOUR_NUMBER | ||
_attr_entity_category = EntityCategory.CONFIG | ||
_attr_native_min_value = 0.1 | ||
_attr_native_max_value = 100.0 | ||
_attr_native_step = 0.1 | ||
|
||
def __init__(self, entry, coordinator: EVSmartChargingCoordinator): | ||
_LOGGER.debug("EVSmartChargingNumberChargingSpeed.__init__()") | ||
super().__init__(entry, coordinator) | ||
if self.value is None: | ||
self._attr_native_value = get_parameter(entry, CONF_PCT_PER_HOUR) | ||
self.update_ha_state() | ||
|
||
async def async_set_native_value(self, value: float) -> None: | ||
"""Set new value.""" | ||
await super().async_set_native_value(value) | ||
self.coordinator.charging_pct_per_hour = value | ||
await self.coordinator.update_sensors() | ||
|
||
|
||
class EVSmartChargingNumberPriceLimit(EVSmartChargingNumber): | ||
"""EV Smart Charging apply limit switch class.""" | ||
|
||
_attr_name = ENTITY_NAME_CONF_MAX_PRICE_NUMBER | ||
_attr_icon = ICON_CASH | ||
_attr_entity_category = EntityCategory.CONFIG | ||
_attr_native_min_value = 0.0 | ||
_attr_native_max_value = 10000.0 | ||
_attr_native_step = 0.01 | ||
|
||
def __init__(self, entry, coordinator: EVSmartChargingCoordinator): | ||
_LOGGER.debug("EVSmartChargingNumberPriceLimit.__init__()") | ||
super().__init__(entry, coordinator) | ||
if self.value is None: | ||
self._attr_native_value = get_parameter(entry, CONF_MAX_PRICE) | ||
self.update_ha_state() | ||
|
||
async def async_set_native_value(self, value: float) -> None: | ||
"""Set new value.""" | ||
await super().async_set_native_value(value) | ||
self.coordinator.max_price = value | ||
await self.coordinator.update_sensors() | ||
|
||
|
||
class EVSmartChargingNumberMinSOC(EVSmartChargingNumber): | ||
"""EV Smart Charging continuous switch class.""" | ||
|
||
_attr_name = ENTITY_NAME_CONF_MIN_SOC_NUMBER | ||
_attr_icon = ICON_BATTERY_50 | ||
_attr_entity_category = EntityCategory.CONFIG | ||
_attr_native_min_value = 0.0 | ||
_attr_native_max_value = 100.0 | ||
_attr_native_step = 1.0 | ||
|
||
def __init__(self, entry, coordinator: EVSmartChargingCoordinator): | ||
_LOGGER.debug("EVSmartChargingNumberMinSOC.__init__()") | ||
super().__init__(entry, coordinator) | ||
if self.value is None: | ||
self._attr_native_value = get_parameter(entry, CONF_MIN_SOC) | ||
self.update_ha_state() | ||
|
||
async def async_set_native_value(self, value: float) -> None: | ||
"""Set new value.""" | ||
await super().async_set_native_value(value) | ||
self.coordinator.number_min_soc = value | ||
await self.coordinator.update_sensors() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
"""Select platform for EV Smart Charging.""" | ||
import logging | ||
|
||
from homeassistant.components.select import SelectEntity | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.helpers.entity import EntityCategory | ||
|
||
from .const import ( | ||
CONF_READY_HOUR, | ||
DOMAIN, | ||
ENTITY_NAME_CONF_READY_HOUR, | ||
HOURS, | ||
ICON_TIME, | ||
SELECT, | ||
) | ||
from .coordinator import EVSmartChargingCoordinator | ||
from .entity import EVSmartChargingEntity | ||
from .helpers.general import get_parameter | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
async def async_setup_entry( | ||
hass: HomeAssistant, entry, async_add_devices | ||
): # pylint: disable=unused-argument | ||
"""Setup select platform.""" | ||
_LOGGER.debug("EVSmartCharging.select.py") | ||
coordinator = hass.data[DOMAIN][entry.entry_id] | ||
selects = [] | ||
selects.append(EVSmartChargingSelectReadyHour(entry, coordinator)) | ||
async_add_devices(selects) | ||
|
||
|
||
class EVSmartChargingSelect(EVSmartChargingEntity, SelectEntity): | ||
"""EV Smart Charging switch class.""" | ||
|
||
_attr_current_option: str | None = None | ||
|
||
def __init__(self, entry, coordinator: EVSmartChargingCoordinator): | ||
_LOGGER.debug("EVSmartChargingSelect.__init__()") | ||
super().__init__(entry) | ||
self.coordinator = coordinator | ||
id_name = self._attr_name.replace(" ", "").lower() | ||
self._attr_unique_id = ".".join([entry.entry_id, SELECT, id_name]) | ||
|
||
async def async_select_option(self, option: str) -> None: | ||
"""Change the selected option.""" | ||
self._attr_current_option = option | ||
|
||
|
||
class EVSmartChargingSelectReadyHour(EVSmartChargingSelect): | ||
"""EV Smart Charging active switch class.""" | ||
|
||
_attr_name = ENTITY_NAME_CONF_READY_HOUR | ||
_attr_icon = ICON_TIME | ||
_attr_entity_category = EntityCategory.CONFIG | ||
_attr_options = HOURS | ||
|
||
def __init__(self, entry, coordinator: EVSmartChargingCoordinator): | ||
_LOGGER.debug("EVSmartChargingSelectReadyHour.__init__()") | ||
super().__init__(entry, coordinator) | ||
if self.state is None: | ||
self._attr_current_option = get_parameter(entry, CONF_READY_HOUR) | ||
self.update_ha_state() | ||
|
||
async def async_select_option(self, option: str) -> None: | ||
"""Change the selected option.""" | ||
await super().async_select_option(option) | ||
if self.state: | ||
try: | ||
self.coordinator.ready_hour_local = int(self.state[0:2]) | ||
except ValueError: | ||
# Don't use ready_hour. Select a time in the far future. | ||
self.coordinator.ready_hour_local = 72 | ||
if self.coordinator.ready_hour_local == 0: | ||
# Treat 00:00 as 24:00 | ||
self.coordinator.ready_hour_local = 24 | ||
await self.coordinator.update_sensors() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
"""Test ev_smart_charging number.""" | ||
from pytest_homeassistant_custom_component.common import MockConfigEntry | ||
|
||
from custom_components.ev_smart_charging import ( | ||
async_setup_entry, | ||
async_unload_entry, | ||
) | ||
from custom_components.ev_smart_charging.const import ( | ||
CONF_MAX_PRICE, | ||
CONF_MIN_SOC, | ||
CONF_PCT_PER_HOUR, | ||
DOMAIN, | ||
NUMBER, | ||
) | ||
from custom_components.ev_smart_charging.coordinator import ( | ||
EVSmartChargingCoordinator, | ||
) | ||
from custom_components.ev_smart_charging.number import ( | ||
EVSmartChargingNumberChargingSpeed, | ||
EVSmartChargingNumberPriceLimit, | ||
EVSmartChargingNumberMinSOC, | ||
) | ||
|
||
from .const import MOCK_CONFIG_MIN_SOC | ||
|
||
# We can pass fixtures as defined in conftest.py to tell pytest to use the fixture | ||
# for a given test. We can also leverage fixtures and mocks that are available in | ||
# Home Assistant using the pytest_homeassistant_custom_component plugin. | ||
# Assertions allow you to verify that the return value of whatever is on the left | ||
# side of the assertion matches with the right side. | ||
|
||
# pylint: disable=unused-argument | ||
async def test_number(hass, bypass_validate_input_sensors): | ||
"""Test sensor properties.""" | ||
# Create a mock entry so we don't have to go through config flow | ||
config_entry = MockConfigEntry( | ||
domain=DOMAIN, data=MOCK_CONFIG_MIN_SOC, entry_id="test" | ||
) | ||
|
||
# Set up the entry and assert that the values set during setup are where we expect | ||
# them to be. Because we have patched the BlueprintDataUpdateCoordinator.async_get_data | ||
# call, no code from custom_components/integration_blueprint/api.py actually runs. | ||
assert await async_setup_entry(hass, config_entry) | ||
await hass.async_block_till_done() | ||
|
||
assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] | ||
assert isinstance( | ||
hass.data[DOMAIN][config_entry.entry_id], EVSmartChargingCoordinator | ||
) | ||
coordinator = hass.data[DOMAIN][config_entry.entry_id] | ||
|
||
# Get the numbers | ||
number_charging_speed: EVSmartChargingNumberChargingSpeed = hass.data[ | ||
"entity_components" | ||
][NUMBER].get_entity("number.none_charging_speed") | ||
number_price_limit: EVSmartChargingNumberPriceLimit = hass.data[ | ||
"entity_components" | ||
][NUMBER].get_entity("number.none_electricity_price_limit") | ||
number_min_soc: EVSmartChargingNumberMinSOC = hass.data["entity_components"][ | ||
NUMBER | ||
].get_entity("number.none_minimum_ev_soc") | ||
assert number_charging_speed | ||
assert number_price_limit | ||
assert number_min_soc | ||
assert isinstance(number_charging_speed, EVSmartChargingNumberChargingSpeed) | ||
assert isinstance(number_price_limit, EVSmartChargingNumberPriceLimit) | ||
assert isinstance(number_min_soc, EVSmartChargingNumberMinSOC) | ||
|
||
# Test the numbers | ||
|
||
assert number_charging_speed.native_value == MOCK_CONFIG_MIN_SOC[CONF_PCT_PER_HOUR] | ||
assert number_price_limit.native_value == MOCK_CONFIG_MIN_SOC[CONF_MAX_PRICE] | ||
assert number_min_soc.native_value == MOCK_CONFIG_MIN_SOC[CONF_MIN_SOC] | ||
|
||
await number_charging_speed.async_set_native_value(3.2) | ||
assert coordinator.charging_pct_per_hour == 3.2 | ||
|
||
await number_price_limit.async_set_native_value(123) | ||
assert coordinator.max_price == 123 | ||
|
||
await number_min_soc.async_set_native_value(33) | ||
assert coordinator.number_min_soc == 33 | ||
|
||
# Unload the entry and verify that the data has been removed | ||
assert await async_unload_entry(hass, config_entry) | ||
assert config_entry.entry_id not in hass.data[DOMAIN] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
"""Test ev_smart_charging select.""" | ||
from pytest_homeassistant_custom_component.common import MockConfigEntry | ||
|
||
from custom_components.ev_smart_charging import ( | ||
async_setup_entry, | ||
async_unload_entry, | ||
) | ||
from custom_components.ev_smart_charging.const import ( | ||
CONF_READY_HOUR, | ||
DOMAIN, | ||
SELECT, | ||
) | ||
from custom_components.ev_smart_charging.coordinator import ( | ||
EVSmartChargingCoordinator, | ||
) | ||
from custom_components.ev_smart_charging.select import ( | ||
EVSmartChargingSelectReadyHour, | ||
) | ||
|
||
from .const import MOCK_CONFIG_MIN_SOC | ||
|
||
# We can pass fixtures as defined in conftest.py to tell pytest to use the fixture | ||
# for a given test. We can also leverage fixtures and mocks that are available in | ||
# Home Assistant using the pytest_homeassistant_custom_component plugin. | ||
# Assertions allow you to verify that the return value of whatever is on the left | ||
# side of the assertion matches with the right side. | ||
|
||
# pylint: disable=unused-argument | ||
async def test_select(hass, bypass_validate_input_sensors): | ||
"""Test sensor properties.""" | ||
# Create a mock entry so we don't have to go through config flow | ||
config_entry = MockConfigEntry( | ||
domain=DOMAIN, data=MOCK_CONFIG_MIN_SOC, entry_id="test" | ||
) | ||
|
||
# Set up the entry and assert that the values set during setup are where we expect | ||
# them to be. Because we have patched the BlueprintDataUpdateCoordinator.async_get_data | ||
# call, no code from custom_components/integration_blueprint/api.py actually runs. | ||
assert await async_setup_entry(hass, config_entry) | ||
await hass.async_block_till_done() | ||
|
||
assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] | ||
assert isinstance( | ||
hass.data[DOMAIN][config_entry.entry_id], EVSmartChargingCoordinator | ||
) | ||
coordinator = hass.data[DOMAIN][config_entry.entry_id] | ||
|
||
# Get the selects | ||
select_ready_hour: EVSmartChargingSelectReadyHour = hass.data["entity_components"][ | ||
SELECT | ||
].get_entity("select.none_charge_completion_time") | ||
assert select_ready_hour | ||
assert isinstance(select_ready_hour, EVSmartChargingSelectReadyHour) | ||
|
||
# Test the selects | ||
|
||
assert select_ready_hour.state == MOCK_CONFIG_MIN_SOC[CONF_READY_HOUR] | ||
|
||
await select_ready_hour.async_select_option("00:00") | ||
assert coordinator.ready_hour_local == 24 | ||
await select_ready_hour.async_select_option("13:00") | ||
assert coordinator.ready_hour_local == 13 | ||
await select_ready_hour.async_select_option("None") | ||
assert coordinator.ready_hour_local == 72 | ||
|
||
# Unload the entry and verify that the data has been removed | ||
assert await async_unload_entry(hass, config_entry) | ||
assert config_entry.entry_id not in hass.data[DOMAIN] |