diff --git a/.coveragerc b/.coveragerc index 3795f7e49b8..2cc3bf2d019 100644 --- a/.coveragerc +++ b/.coveragerc @@ -672,6 +672,7 @@ omit = homeassistant/components/mystrom/switch.py homeassistant/components/myq/__init__.py homeassistant/components/myq/cover.py + homeassistant/components/myq/light.py homeassistant/components/nad/media_player.py homeassistant/components/nanoleaf/light.py homeassistant/components/neato/__init__.py diff --git a/CODEOWNERS b/CODEOWNERS index 4b7cb8520b0..642de7a04d8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -321,7 +321,7 @@ homeassistant/components/msteams/* @peroyvind homeassistant/components/mullvad/* @meichthys homeassistant/components/mutesync/* @currentoor homeassistant/components/my/* @home-assistant/core -homeassistant/components/myq/* @bdraco +homeassistant/components/myq/* @bdraco @ehendrix23 homeassistant/components/mysensors/* @MartinHjelmare @functionpointer homeassistant/components/mystrom/* @fabaff homeassistant/components/nam/* @bieniu diff --git a/homeassistant/components/myq/const.py b/homeassistant/components/myq/const.py index 6189b1601ea..9f3a434ae37 100644 --- a/homeassistant/components/myq/const.py +++ b/homeassistant/components/myq/const.py @@ -5,18 +5,28 @@ from pymyq.garagedoor import ( STATE_OPEN as MYQ_COVER_STATE_OPEN, STATE_OPENING as MYQ_COVER_STATE_OPENING, ) +from pymyq.lamp import STATE_OFF as MYQ_LIGHT_STATE_OFF, STATE_ON as MYQ_LIGHT_STATE_ON -from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING +from homeassistant.const import ( + STATE_CLOSED, + STATE_CLOSING, + STATE_OFF, + STATE_ON, + STATE_OPEN, + STATE_OPENING, +) DOMAIN = "myq" -PLATFORMS = ["cover", "binary_sensor"] +PLATFORMS = ["cover", "binary_sensor", "light"] MYQ_TO_HASS = { MYQ_COVER_STATE_CLOSED: STATE_CLOSED, MYQ_COVER_STATE_CLOSING: STATE_CLOSING, MYQ_COVER_STATE_OPEN: STATE_OPEN, MYQ_COVER_STATE_OPENING: STATE_OPENING, + MYQ_LIGHT_STATE_ON: STATE_ON, + MYQ_LIGHT_STATE_OFF: STATE_OFF, } MYQ_GATEWAY = "myq_gateway" diff --git a/homeassistant/components/myq/light.py b/homeassistant/components/myq/light.py new file mode 100644 index 00000000000..f26d28fe3a3 --- /dev/null +++ b/homeassistant/components/myq/light.py @@ -0,0 +1,115 @@ +"""Support for MyQ-Enabled lights.""" +import logging + +from pymyq.const import ( + DEVICE_STATE as MYQ_DEVICE_STATE, + DEVICE_STATE_ONLINE as MYQ_DEVICE_STATE_ONLINE, + KNOWN_MODELS, + MANUFACTURER, +) +from pymyq.errors import MyQError + +from homeassistant.components.light import LightEntity +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN, MYQ_COORDINATOR, MYQ_GATEWAY, MYQ_TO_HASS + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up myq lights.""" + data = hass.data[DOMAIN][config_entry.entry_id] + myq = data[MYQ_GATEWAY] + coordinator = data[MYQ_COORDINATOR] + + async_add_entities( + [MyQLight(coordinator, device) for device in myq.lamps.values()], True + ) + + +class MyQLight(CoordinatorEntity, LightEntity): + """Representation of a MyQ light.""" + + _attr_supported_features = 0 + + def __init__(self, coordinator, device): + """Initialize with API object, device id.""" + super().__init__(coordinator) + self._device = device + self._attr_unique_id = device.device_id + self._attr_name = device.name + + @property + def available(self): + """Return if the device is online.""" + if not super().available: + return False + + # Not all devices report online so assume True if its missing + return self._device.device_json[MYQ_DEVICE_STATE].get( + MYQ_DEVICE_STATE_ONLINE, True + ) + + @property + def is_on(self): + """Return true if the light is on, else False.""" + return MYQ_TO_HASS.get(self._device.state) == STATE_ON + + @property + def is_off(self): + """Return true if the light is off, else False.""" + return MYQ_TO_HASS.get(self._device.state) == STATE_OFF + + async def async_turn_on(self, **kwargs): + """Issue on command to light.""" + if self.is_on: + return + + try: + await self._device.turnon(wait_for_state=True) + except MyQError as err: + raise HomeAssistantError( + f"Turning light {self._device.name} on failed with error: {err}" + ) from err + + # Write new state to HASS + self.async_write_ha_state() + + async def async_turn_off(self, **kwargs): + """Issue off command to light.""" + if self.is_off: + return + + try: + await self._device.turnoff(wait_for_state=True) + except MyQError as err: + raise HomeAssistantError( + f"Turning light {self._device.name} off failed with error: {err}" + ) from err + + # Write opening state to HASS + self.async_write_ha_state() + + @property + def device_info(self): + """Return the device_info of the device.""" + device_info = { + "identifiers": {(DOMAIN, self._device.device_id)}, + "name": self._device.name, + "manufacturer": MANUFACTURER, + "sw_version": self._device.firmware_version, + } + if model := KNOWN_MODELS.get(self._device.device_id[2:4]): + device_info["model"] = model + if self._device.parent_device_id: + device_info["via_device"] = (DOMAIN, self._device.parent_device_id) + return device_info + + async def async_added_to_hass(self): + """Subscribe to updates.""" + self.async_on_remove( + self.coordinator.async_add_listener(self.async_write_ha_state) + ) diff --git a/homeassistant/components/myq/manifest.json b/homeassistant/components/myq/manifest.json index a4de12290f1..33cbea71bcd 100644 --- a/homeassistant/components/myq/manifest.json +++ b/homeassistant/components/myq/manifest.json @@ -3,7 +3,7 @@ "name": "MyQ", "documentation": "https://www.home-assistant.io/integrations/myq", "requirements": ["pymyq==3.1.2"], - "codeowners": ["@bdraco"], + "codeowners": ["@bdraco","@ehendrix23"], "config_flow": true, "homekit": { "models": ["819LMB", "MYQ"] diff --git a/tests/components/myq/test_light.py b/tests/components/myq/test_light.py new file mode 100644 index 00000000000..c7b3dbc8427 --- /dev/null +++ b/tests/components/myq/test_light.py @@ -0,0 +1,36 @@ +"""The scene tests for the myq platform.""" + +from homeassistant.const import STATE_OFF, STATE_ON + +from .util import async_init_integration + + +async def test_create_lights(hass): + """Test creation of lights.""" + + await async_init_integration(hass) + + state = hass.states.get("light.garage_door_light_off") + assert state.state == STATE_OFF + expected_attributes = { + "friendly_name": "Garage Door Light Off", + "supported_features": 0, + } + # Only test for a subset of attributes in case + # HA changes the implementation and a new one appears + assert all( + state.attributes[key] == expected_attributes[key] for key in expected_attributes + ) + + state = hass.states.get("light.garage_door_light_on") + assert state.state == STATE_ON + expected_attributes = { + "friendly_name": "Garage Door Light On", + "supported_features": 0, + } + # Only test for a subset of attributes in case + # HA changes the implementation and a new one appears + + assert all( + state.attributes[key] == expected_attributes[key] for key in expected_attributes + ) diff --git a/tests/fixtures/myq/devices.json b/tests/fixtures/myq/devices.json index f7c65c6bb20..1e731ffe204 100644 --- a/tests/fixtures/myq/devices.json +++ b/tests/fixtures/myq/devices.json @@ -1,5 +1,5 @@ { - "count" : 4, + "count" : 6, "href" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices", "items" : [ { @@ -128,6 +128,36 @@ "href" : "http://api.myqdevice.com/api/v5/accounts/account_id/devices/small_garage_serial", "device_type" : "wifigaragedooropener", "created_date" : "2020-02-10T23:11:47.487" - } + }, + { + "serial_number" : "garage_light_off", + "state" : { + "last_status" : "2020-03-30T02:48:45.7501595Z", + "online" : true, + "lamp_state" : "off", + "last_update" : "2020-03-26T15:45:31.4713796Z" + }, + "parent_device_id" : "gateway_serial", + "device_platform" : "myq", + "name" : "Garage Door Light Off", + "device_family" : "lamp", + "device_type" : "lamp", + "created_date" : "2020-02-10T23:11:47.487" + }, + { + "serial_number" : "garage_light_on", + "state" : { + "last_status" : "2020-03-30T02:48:45.7501595Z", + "online" : true, + "lamp_state" : "on", + "last_update" : "2020-03-26T15:45:31.4713796Z" + }, + "parent_device_id" : "gateway_serial", + "device_platform" : "myq", + "name" : "Garage Door Light On", + "device_family" : "lamp", + "device_type" : "lamp", + "created_date" : "2020-02-10T23:11:47.487" + } ] }