diff --git a/homeassistant/components/homewizard/const.py b/homeassistant/components/homewizard/const.py index c1c788d371b0..677f7ec46d9b 100644 --- a/homeassistant/components/homewizard/const.py +++ b/homeassistant/components/homewizard/const.py @@ -10,7 +10,7 @@ from homewizard_energy.models import Data, Device, State from homeassistant.const import Platform DOMAIN = "homewizard" -PLATFORMS = [Platform.SENSOR, Platform.SWITCH] +PLATFORMS = [Platform.SENSOR, Platform.SWITCH, Platform.NUMBER] # Platform config. CONF_API_ENABLED = "api_enabled" diff --git a/homeassistant/components/homewizard/number.py b/homeassistant/components/homewizard/number.py new file mode 100644 index 000000000000..43dccc364bd2 --- /dev/null +++ b/homeassistant/components/homewizard/number.py @@ -0,0 +1,65 @@ +"""Creates HomeWizard Number entities.""" +from __future__ import annotations + +from homeassistant.components.number import NumberEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import PERCENTAGE +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN +from .coordinator import HWEnergyDeviceUpdateCoordinator + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up numbers for device.""" + coordinator: HWEnergyDeviceUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + if coordinator.data["state"]: + async_add_entities( + [ + HWEnergyNumberEntity(coordinator, entry), + ] + ) + + +class HWEnergyNumberEntity( + CoordinatorEntity[HWEnergyDeviceUpdateCoordinator], NumberEntity +): + """Representation of status light number.""" + + _attr_entity_category = EntityCategory.CONFIG + _attr_has_entity_name = True + + def __init__( + self, + coordinator: HWEnergyDeviceUpdateCoordinator, + entry: ConfigEntry, + ) -> None: + """Initialize the control number.""" + super().__init__(coordinator) + self._attr_unique_id = f"{entry.unique_id}_status_light_brightness" + self._attr_name = "Status light brightness" + self._attr_native_unit_of_measurement = PERCENTAGE + self._attr_icon = "mdi:lightbulb-on" + self._attr_device_info = { + "identifiers": {(DOMAIN, coordinator.data["device"].serial)}, + } + + async def async_set_native_value(self, value: float) -> None: + """Set a new value.""" + await self.coordinator.api.state_set(brightness=value * (255 / 100)) + await self.coordinator.async_refresh() + + @property + def native_value(self) -> float | None: + """Return the current value.""" + if self.coordinator.data["state"].brightness is None: + return None + return round(self.coordinator.data["state"].brightness * (100 / 255)) diff --git a/tests/components/homewizard/test_number.py b/tests/components/homewizard/test_number.py new file mode 100644 index 000000000000..9538fd3cef9a --- /dev/null +++ b/tests/components/homewizard/test_number.py @@ -0,0 +1,141 @@ +"""Test the update coordinator for HomeWizard.""" + +from unittest.mock import AsyncMock, patch + +from homewizard_energy.models import State + +from homeassistant.components import number +from homeassistant.components.number import ATTR_VALUE, SERVICE_SET_VALUE +from homeassistant.const import ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME +from homeassistant.helpers import entity_registry as er + +from .generator import get_mock_device + + +async def test_number_entity_not_loaded_when_not_available( + hass, mock_config_entry_data, mock_config_entry +): + """Test entity does not load number when brightness is not available.""" + + api = get_mock_device() + + with patch( + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", + return_value=api, + ): + entry = mock_config_entry + entry.data = mock_config_entry_data + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert ( + hass.states.get("number.product_name_aabbccddeeff_status_light_brightness") + is None + ) + + +async def test_number_loads_entities(hass, mock_config_entry_data, mock_config_entry): + """Test entity does load number when brightness is available.""" + + api = get_mock_device() + api.state = AsyncMock(return_value=State.from_dict({"brightness": 255})) + + with patch( + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", + return_value=api, + ): + entry = mock_config_entry + entry.data = mock_config_entry_data + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + entity_registry = er.async_get(hass) + + state = hass.states.get("number.product_name_aabbccddeeff_status_light_brightness") + assert state + assert state.state == "100" + assert ( + state.attributes.get(ATTR_FRIENDLY_NAME) + == "Product Name (aabbccddeeff) Status light brightness" + ) + + entry = entity_registry.async_get( + "number.product_name_aabbccddeeff_status_light_brightness" + ) + assert entry + assert entry.unique_id == "aabbccddeeff_status_light_brightness" + assert not entry.disabled + + +async def test_brightness_level_set(hass, mock_config_entry_data, mock_config_entry): + """Test entity turns sets light level.""" + + api = get_mock_device() + api.state = AsyncMock(return_value=State.from_dict({"brightness": 255})) + + def state_set(brightness): + api.state = AsyncMock(return_value=State.from_dict({"brightness": brightness})) + + api.state_set = AsyncMock(side_effect=state_set) + + with patch( + "homeassistant.components.homewizard.coordinator.HomeWizardEnergy", + return_value=api, + ): + entry = mock_config_entry + entry.data = mock_config_entry_data + entry.add_to_hass(hass) + + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert ( + hass.states.get( + "number.product_name_aabbccddeeff_status_light_brightness" + ).state + == "100" + ) + + # Set level halfway + await hass.services.async_call( + number.DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: "number.product_name_aabbccddeeff_status_light_brightness", + ATTR_VALUE: 50, + }, + blocking=True, + ) + + await hass.async_block_till_done() + assert ( + hass.states.get( + "number.product_name_aabbccddeeff_status_light_brightness" + ).state + == "50" + ) + assert len(api.state_set.mock_calls) == 1 + + # Turn off level + await hass.services.async_call( + number.DOMAIN, + SERVICE_SET_VALUE, + { + ATTR_ENTITY_ID: "number.product_name_aabbccddeeff_status_light_brightness", + ATTR_VALUE: 0, + }, + blocking=True, + ) + + await hass.async_block_till_done() + assert ( + hass.states.get( + "number.product_name_aabbccddeeff_status_light_brightness" + ).state + == "0" + ) + assert len(api.state_set.mock_calls) == 2