-
-
Notifications
You must be signed in to change notification settings - Fork 32k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
deCONZ cover platform for Keen vents
- Loading branch information
Showing
8 changed files
with
263 additions
and
12 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,146 @@ | ||
""" | ||
Support for deCONZ covers. | ||
For more details about this platform, please refer to the documentation at | ||
https://home-assistant.io/components/cover.deconz/ | ||
""" | ||
from homeassistant.components.deconz.const import ( | ||
COVER_TYPES, DOMAIN as DATA_DECONZ, DATA_DECONZ_ID, DATA_DECONZ_UNSUB, | ||
DECONZ_DOMAIN) | ||
from homeassistant.components.cover import ( | ||
ATTR_POSITION, CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN, | ||
SUPPORT_SET_POSITION) | ||
from homeassistant.core import callback | ||
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE | ||
from homeassistant.helpers.dispatcher import async_dispatcher_connect | ||
|
||
DEPENDENCIES = ['deconz'] | ||
|
||
|
||
async def async_setup_platform(hass, config, async_add_entities, | ||
discovery_info=None): | ||
"""Unsupported way of setting up deCONZ covers.""" | ||
pass | ||
|
||
|
||
async def async_setup_entry(hass, config_entry, async_add_entities): | ||
"""Set up covers for deCONZ component. | ||
Covers are based on same device class as lights in deCONZ. | ||
""" | ||
@callback | ||
def async_add_cover(lights): | ||
"""Add cover from deCONZ.""" | ||
entities = [] | ||
for light in lights: | ||
if light.type in COVER_TYPES: | ||
entities.append(DeconzCover(light)) | ||
async_add_entities(entities, True) | ||
|
||
hass.data[DATA_DECONZ_UNSUB].append( | ||
async_dispatcher_connect(hass, 'deconz_new_light', async_add_cover)) | ||
|
||
async_add_cover(hass.data[DATA_DECONZ].lights.values()) | ||
|
||
|
||
class DeconzCover(CoverDevice): | ||
"""Representation of a deCONZ cover.""" | ||
|
||
def __init__(self, cover): | ||
"""Set up cover and add update callback to get data from websocket.""" | ||
self._cover = cover | ||
self._features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION | ||
|
||
async def async_added_to_hass(self): | ||
"""Subscribe to covers events.""" | ||
self._cover.register_async_callback(self.async_update_callback) | ||
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._cover.deconz_id | ||
|
||
async def async_will_remove_from_hass(self) -> None: | ||
"""Disconnect cover object when removed.""" | ||
self._cover.remove_callback(self.async_update_callback) | ||
self._cover = None | ||
|
||
@callback | ||
def async_update_callback(self, reason): | ||
"""Update the cover's state.""" | ||
self.async_schedule_update_ha_state() | ||
|
||
@property | ||
def current_cover_position(self): | ||
"""Return the current position of the cover.""" | ||
if self.is_closed: | ||
return 0 | ||
return int(self._cover.brightness / 255 * 100) | ||
|
||
@property | ||
def is_closed(self): | ||
"""Return if the cover is closed.""" | ||
return not self._cover.state | ||
|
||
@property | ||
def name(self): | ||
"""Return the name of the cover.""" | ||
return self._cover.name | ||
|
||
@property | ||
def unique_id(self): | ||
"""Return a unique identifier for this cover.""" | ||
return self._cover.uniqueid | ||
|
||
@property | ||
def device_class(self): | ||
"""Return the class of the cover.""" | ||
return 'damper' | ||
|
||
@property | ||
def supported_features(self): | ||
"""Flag supported features.""" | ||
return self._features | ||
|
||
@property | ||
def available(self): | ||
"""Return True if light is available.""" | ||
return self._cover.reachable | ||
|
||
@property | ||
def should_poll(self): | ||
"""No polling needed.""" | ||
return False | ||
|
||
async def async_set_cover_position(self, **kwargs): | ||
"""Move the cover to a specific position.""" | ||
position = kwargs[ATTR_POSITION] | ||
data = {'on': False} | ||
if position > 0: | ||
data['on'] = True | ||
data['bri'] = int(position / 100 * 255) | ||
await self._cover.async_set_state(data) | ||
|
||
async def async_open_cover(self, **kwargs): | ||
"""Open cover.""" | ||
data = {ATTR_POSITION: 100} | ||
await self.async_set_cover_position(**data) | ||
|
||
async def async_close_cover(self, **kwargs): | ||
"""Close cover.""" | ||
data = {ATTR_POSITION: 0} | ||
await self.async_set_cover_position(**data) | ||
|
||
@property | ||
def device_info(self): | ||
"""Return a device description for device registry.""" | ||
if (self._cover.uniqueid is None or | ||
self._cover.uniqueid.count(':') != 7): | ||
return None | ||
serial = self._cover.uniqueid.split('-', 1)[0] | ||
bridgeid = self.hass.data[DATA_DECONZ].config.bridgeid | ||
return { | ||
'connections': {(CONNECTION_ZIGBEE, serial)}, | ||
'identifiers': {(DECONZ_DOMAIN, serial)}, | ||
'manufacturer': self._cover.manufacturer, | ||
'model': self._cover.modelid, | ||
'name': self._cover.name, | ||
'sw_version': self._cover.swversion, | ||
'via_hub': (DECONZ_DOMAIN, bridgeid), | ||
} |
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
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,84 @@ | ||
"""deCONZ cover platform tests.""" | ||
from unittest.mock import Mock, patch | ||
|
||
from homeassistant import config_entries | ||
from homeassistant.components import deconz | ||
from homeassistant.components.deconz.const import COVER_TYPES | ||
from homeassistant.helpers.dispatcher import async_dispatcher_send | ||
|
||
from tests.common import mock_coro | ||
|
||
SUPPORTED_COVERS = { | ||
"1": { | ||
"id": "Cover 1 id", | ||
"name": "Cover 1 name", | ||
"type": "Level controllable output", | ||
"state": {} | ||
} | ||
} | ||
|
||
UNSUPPORTED_COVER = { | ||
"1": { | ||
"id": "Cover id", | ||
"name": "Unsupported switch", | ||
"type": "Not a cover", | ||
"state": {} | ||
} | ||
} | ||
|
||
|
||
async def setup_bridge(hass, data): | ||
"""Load the deCONZ cover platform.""" | ||
from pydeconz import DeconzSession | ||
loop = Mock() | ||
session = Mock() | ||
entry = Mock() | ||
entry.data = {'host': '1.2.3.4', 'port': 80, 'api_key': '1234567890ABCDEF'} | ||
bridge = DeconzSession(loop, session, **entry.data) | ||
with patch('pydeconz.DeconzSession.async_get_state', | ||
return_value=mock_coro(data)): | ||
await bridge.async_load_parameters() | ||
hass.data[deconz.DOMAIN] = bridge | ||
hass.data[deconz.DATA_DECONZ_UNSUB] = [] | ||
hass.data[deconz.DATA_DECONZ_ID] = {} | ||
config_entry = config_entries.ConfigEntry( | ||
1, deconz.DOMAIN, 'Mock Title', {'host': 'mock-host'}, 'test', | ||
config_entries.CONN_CLASS_LOCAL_PUSH) | ||
await hass.config_entries.async_forward_entry_setup(config_entry, 'cover') | ||
# To flush out the service call to update the group | ||
await hass.async_block_till_done() | ||
|
||
|
||
async def test_no_switches(hass): | ||
"""Test that no cover entities are created.""" | ||
data = {} | ||
await setup_bridge(hass, data) | ||
assert len(hass.data[deconz.DATA_DECONZ_ID]) == 0 | ||
assert len(hass.states.async_all()) == 0 | ||
|
||
|
||
async def test_cover(hass): | ||
"""Test that all supported cover entities are created.""" | ||
await setup_bridge(hass, {"lights": SUPPORTED_COVERS}) | ||
assert "cover.cover_1_name" in hass.data[deconz.DATA_DECONZ_ID] | ||
assert len(SUPPORTED_COVERS) == len(COVER_TYPES) | ||
assert len(hass.states.async_all()) == 2 | ||
|
||
|
||
async def test_add_new_cover(hass): | ||
"""Test successful creation of cover entity.""" | ||
data = {} | ||
await setup_bridge(hass, data) | ||
cover = Mock() | ||
cover.name = 'name' | ||
cover.type = "Level controllable output" | ||
cover.register_async_callback = Mock() | ||
async_dispatcher_send(hass, 'deconz_new_light', [cover]) | ||
await hass.async_block_till_done() | ||
assert "cover.name" in hass.data[deconz.DATA_DECONZ_ID] | ||
|
||
|
||
async def test_unsupported_cover(hass): | ||
"""Test that unsupported covers are not created.""" | ||
await setup_bridge(hass, {"lights": UNSUPPORTED_COVER}) | ||
assert len(hass.states.async_all()) == 0 |
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