diff --git a/homeassistant/components/modern_forms/__init__.py b/homeassistant/components/modern_forms/__init__.py index ca5e5388aaa..24195b96ea4 100644 --- a/homeassistant/components/modern_forms/__init__.py +++ b/homeassistant/components/modern_forms/__init__.py @@ -13,6 +13,7 @@ from aiomodernforms.models import Device as ModernFormsDeviceState from homeassistant.components.fan import DOMAIN as FAN_DOMAIN from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_MODEL, ATTR_NAME, ATTR_SW_VERSION, CONF_HOST @@ -31,6 +32,7 @@ SCAN_INTERVAL = timedelta(seconds=5) PLATFORMS = [ LIGHT_DOMAIN, FAN_DOMAIN, + SENSOR_DOMAIN, SWITCH_DOMAIN, ] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/modern_forms/sensor.py b/homeassistant/components/modern_forms/sensor.py new file mode 100644 index 00000000000..01efe3f1d28 --- /dev/null +++ b/homeassistant/components/modern_forms/sensor.py @@ -0,0 +1,118 @@ +"""Support for Modern Forms switches.""" +from __future__ import annotations + +from datetime import datetime + +from homeassistant.components.sensor import SensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import DEVICE_CLASS_TIMESTAMP +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType +from homeassistant.util import dt as dt_util + +from . import ModernFormsDataUpdateCoordinator, ModernFormsDeviceEntity +from .const import CLEAR_TIMER, DOMAIN + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Modern Forms sensor based on a config entry.""" + coordinator: ModernFormsDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + sensors: list[ModernFormsSensor] = [ + ModernFormsFanTimerRemainingTimeSensor(entry.entry_id, coordinator), + ] + + # Only setup light sleep timer sensor if light unit installed + if coordinator.data.info.light_type: + sensors.append( + ModernFormsLightTimerRemainingTimeSensor(entry.entry_id, coordinator) + ) + + async_add_entities(sensors) + + +class ModernFormsSensor(ModernFormsDeviceEntity, SensorEntity): + """Defines a Modern Forms binary sensor.""" + + def __init__( + self, + *, + entry_id: str, + coordinator: ModernFormsDataUpdateCoordinator, + name: str, + icon: str, + key: str, + ) -> None: + """Initialize Modern Forms switch.""" + self._key = key + super().__init__( + entry_id=entry_id, coordinator=coordinator, name=name, icon=icon + ) + self._attr_unique_id = f"{self.coordinator.data.info.mac_address}_{self._key}" + + +class ModernFormsLightTimerRemainingTimeSensor(ModernFormsSensor): + """Defines the Modern Forms Light Timer remaining time sensor.""" + + def __init__( + self, entry_id: str, coordinator: ModernFormsDataUpdateCoordinator + ) -> None: + """Initialize Modern Forms Away mode switch.""" + super().__init__( + coordinator=coordinator, + entry_id=entry_id, + icon="mdi:timer-outline", + key="light_timer_remaining_time", + name=f"{coordinator.data.info.device_name} Light Sleep Time", + ) + self._attr_device_class = DEVICE_CLASS_TIMESTAMP + + @property + def state(self) -> StateType: + """Return the state of the sensor.""" + sleep_time: datetime = dt_util.utc_from_timestamp( + self.coordinator.data.state.light_sleep_timer + ) + if ( + self.coordinator.data.state.light_sleep_timer == CLEAR_TIMER + or (sleep_time - dt_util.utcnow()).total_seconds() < 0 + ): + return None + return sleep_time.isoformat() + + +class ModernFormsFanTimerRemainingTimeSensor(ModernFormsSensor): + """Defines the Modern Forms Light Timer remaining time sensor.""" + + def __init__( + self, entry_id: str, coordinator: ModernFormsDataUpdateCoordinator + ) -> None: + """Initialize Modern Forms Away mode switch.""" + super().__init__( + coordinator=coordinator, + entry_id=entry_id, + icon="mdi:timer-outline", + key="fan_timer_remaining_time", + name=f"{coordinator.data.info.device_name} Fan Sleep Time", + ) + self._attr_device_class = DEVICE_CLASS_TIMESTAMP + + @property + def state(self) -> StateType: + """Return the state of the sensor.""" + sleep_time: datetime = dt_util.utc_from_timestamp( + self.coordinator.data.state.fan_sleep_timer + ) + + if ( + self.coordinator.data.state.fan_sleep_timer == CLEAR_TIMER + or (sleep_time - dt_util.utcnow()).total_seconds() < 0 + ): + return None + + return sleep_time.isoformat() diff --git a/tests/components/modern_forms/__init__.py b/tests/components/modern_forms/__init__.py index 54fcf53ce89..c6d2a8b3637 100644 --- a/tests/components/modern_forms/__init__.py +++ b/tests/components/modern_forms/__init__.py @@ -37,6 +37,18 @@ async def modern_forms_no_light_call_mock(method, url, data): return response +async def modern_forms_timers_set_mock(method, url, data): + """Set up the basic returns based on info or status request.""" + if COMMAND_QUERY_STATIC_DATA in data: + fixture = "modern_forms/device_info.json" + else: + fixture = "modern_forms/device_status_timers_active.json" + response = AiohttpClientMockResponse( + method=method, url=url, json=json.loads(load_fixture(fixture)) + ) + return response + + async def init_integration( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker, diff --git a/tests/components/modern_forms/test_sensor.py b/tests/components/modern_forms/test_sensor.py new file mode 100644 index 00000000000..d18793f51c2 --- /dev/null +++ b/tests/components/modern_forms/test_sensor.py @@ -0,0 +1,57 @@ +"""Tests for the Modern Forms sensor platform.""" +from datetime import datetime + +from homeassistant.const import ATTR_DEVICE_CLASS, ATTR_ICON, DEVICE_CLASS_TIMESTAMP +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er + +from tests.components.modern_forms import init_integration, modern_forms_timers_set_mock +from tests.test_util.aiohttp import AiohttpClientMocker + + +async def test_sensors( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the creation and values of the Modern Forms sensors.""" + + # await init_integration(hass, aioclient_mock) + await init_integration(hass, aioclient_mock) + er.async_get(hass) + + # Light timer remaining time + state = hass.states.get("sensor.modernformsfan_light_sleep_time") + assert state + assert state.attributes.get(ATTR_ICON) == "mdi:timer-outline" + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TIMESTAMP + assert state.state == "unknown" + + # Fan timer remaining time + state = hass.states.get("sensor.modernformsfan_fan_sleep_time") + assert state + assert state.attributes.get(ATTR_ICON) == "mdi:timer-outline" + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TIMESTAMP + assert state.state == "unknown" + + +async def test_active_sensors( + hass: HomeAssistant, aioclient_mock: AiohttpClientMocker +) -> None: + """Test the creation and values of the Modern Forms sensors.""" + + # await init_integration(hass, aioclient_mock) + await init_integration(hass, aioclient_mock, mock_type=modern_forms_timers_set_mock) + er.async_get(hass) + + # Light timer remaining time + state = hass.states.get("sensor.modernformsfan_light_sleep_time") + assert state + assert state.attributes.get(ATTR_ICON) == "mdi:timer-outline" + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TIMESTAMP + datetime.fromisoformat(state.state) + + # Fan timer remaining time + state = hass.states.get("sensor.modernformsfan_fan_sleep_time") + assert state + assert state.attributes.get(ATTR_ICON) == "mdi:timer-outline" + assert state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TIMESTAMP + datetime.fromisoformat(state.state) diff --git a/tests/fixtures/modern_forms/device_status_timers_active.json b/tests/fixtures/modern_forms/device_status_timers_active.json new file mode 100644 index 00000000000..e788b3e5882 --- /dev/null +++ b/tests/fixtures/modern_forms/device_status_timers_active.json @@ -0,0 +1,17 @@ +{ + "adaptiveLearning": false, + "awayModeEnabled": false, + "clientId": "MF_000000000000", + "decommission": false, + "factoryReset": false, + "fanDirection": "forward", + "fanOn": true, + "fanSleepTimer": 9999999999, + "fanSpeed": 3, + "lightBrightness": 50, + "lightOn": true, + "lightSleepTimer": 9999999999, + "resetRfPairList": false, + "rfPairModeActive": false, + "schedule": "" +}