Inverted rflink cover ()

* Added InvertedRflinkCover class to support COCO/KAKU ASUN-650 devices

* Rename TYPE_NORMAL to TYPE_STANDARD

* Cleaning up code and removed unused imports

* Added unit tests for InvertedRflinkCover

* less if/else statements

* Autoresolve type for newkaku

* Updated tests for InvertedRflinkCover

* Added unit test for standard cover without specifying type

* Updated comments in unit tests

* Updated unit test configuration and comments to be more explanatory

* Restore variable names in first part of the unit test that have been changed during a search and replace

* Reformated the code according to 4de97ab

* remove blank lines at end of rflink test_cover.py

* Replace single with double quote in test_cover.py

* Replaced single quotes with double qoutes and fixed formatting

* Black improvements

* Reformated the code of the unit test.

* entity_type_for_device_id should return 'TYPE_STANDARD' instead of 'None'
pull/26034/head
fmartens 2019-09-01 17:52:43 +02:00 committed by Martin Hjelmare
parent 597cd3e886
commit 5b77a357e6
2 changed files with 466 additions and 2 deletions
homeassistant/components/rflink
tests/components/rflink

View File

@ -4,7 +4,7 @@ import logging
import voluptuous as vol
from homeassistant.components.cover import PLATFORM_SCHEMA, CoverDevice
from homeassistant.const import CONF_NAME, STATE_OPEN
from homeassistant.const import CONF_NAME, CONF_TYPE, STATE_OPEN
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.restore_state import RestoreEntity
@ -23,6 +23,8 @@ from . import (
_LOGGER = logging.getLogger(__name__)
TYPE_STANDARD = "standard"
TYPE_INVERTED = "inverted"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
@ -33,6 +35,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
cv.string: {
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_TYPE): vol.Any(TYPE_STANDARD, TYPE_INVERTED),
vol.Optional(CONF_ALIASES, default=[]): vol.All(
cv.ensure_list, [cv.string]
),
@ -52,12 +55,51 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
)
def entity_type_for_device_id(device_id):
"""Return entity class for protocol of a given device_id.
Async friendly.
"""
entity_type_mapping = {
# KlikAanKlikUit cover have the controls inverted
"newkaku": TYPE_INVERTED
}
protocol = device_id.split("_")[0]
return entity_type_mapping.get(protocol, TYPE_STANDARD)
def entity_class_for_type(entity_type):
"""Translate entity type to entity class.
Async friendly.
"""
entity_device_mapping = {
# default cover implementation
TYPE_STANDARD: RflinkCover,
# cover with open/close commands inverted
# like KAKU/COCO ASUN-650
TYPE_INVERTED: InvertedRflinkCover,
}
return entity_device_mapping.get(entity_type, RflinkCover)
def devices_from_config(domain_config):
"""Parse configuration and add Rflink cover devices."""
devices = []
for device_id, config in domain_config[CONF_DEVICES].items():
# Determine what kind of entity to create, RflinkCover
# or InvertedRflinkCover
if CONF_TYPE in config:
# Remove type from config to not pass it as and argument
# to entity instantiation
entity_type = config.pop(CONF_TYPE)
else:
entity_type = entity_type_for_device_id(device_id)
entity_class = entity_class_for_type(entity_type)
device_config = dict(domain_config[CONF_DEVICE_DEFAULTS], **config)
device = RflinkCover(device_id, **device_config)
device = entity_class(device_id, **device_config)
devices.append(device)
return devices
@ -115,3 +157,13 @@ class RflinkCover(RflinkCommand, CoverDevice, RestoreEntity):
def async_stop_cover(self, **kwargs):
"""Turn the device stop."""
return self._async_handle_command("stop_cover")
class InvertedRflinkCover(RflinkCover):
"""Rflink cover that has inverted open/close commands."""
async def _async_send_command(self, cmd, repetitions):
"""Will invert only the UP/DOWN commands."""
_LOGGER.debug("Getting command: %s for Rflink device: %s", cmd, self._device_id)
cmd_inv = {"UP": "DOWN", "DOWN": "UP"}
await super()._async_send_command(cmd_inv.get(cmd, cmd), repetitions)

View File

@ -390,3 +390,415 @@ async def test_restore_state(hass, monkeypatch):
assert state
assert state.state == STATE_CLOSED
assert state.attributes["assumed_state"]
# The code checks the ID, it will use the
# 'inverted' class when the name starts with
# 'newkaku'
async def test_inverted_cover(hass, monkeypatch):
"""Ensure states are restored on startup."""
config = {
"rflink": {"port": "/dev/ttyABC0"},
DOMAIN: {
"platform": "rflink",
"devices": {
"nonkaku_device_1": {
"name": "nonkaku_type_standard",
"type": "standard",
},
"nonkaku_device_2": {"name": "nonkaku_type_none"},
"nonkaku_device_3": {
"name": "nonkaku_type_inverted",
"type": "inverted",
},
"newkaku_device_4": {
"name": "newkaku_type_standard",
"type": "standard",
},
"newkaku_device_5": {"name": "newkaku_type_none"},
"newkaku_device_6": {
"name": "newkaku_type_inverted",
"type": "inverted",
},
},
},
}
# setup mocking rflink module
event_callback, _, protocol, _ = await mock_rflink(
hass, config, DOMAIN, monkeypatch
)
# test default state of cover loaded from config
standard_cover = hass.states.get(DOMAIN + ".nonkaku_type_standard")
assert standard_cover.state == STATE_CLOSED
assert standard_cover.attributes["assumed_state"]
# mock incoming up command event for nonkaku_device_1
event_callback({"id": "nonkaku_device_1", "command": "up"})
await hass.async_block_till_done()
standard_cover = hass.states.get(DOMAIN + ".nonkaku_type_standard")
assert standard_cover.state == STATE_OPEN
assert standard_cover.attributes.get("assumed_state")
# mock incoming up command event for nonkaku_device_2
event_callback({"id": "nonkaku_device_2", "command": "up"})
await hass.async_block_till_done()
standard_cover = hass.states.get(DOMAIN + ".nonkaku_type_none")
assert standard_cover.state == STATE_OPEN
assert standard_cover.attributes.get("assumed_state")
# mock incoming up command event for nonkaku_device_3
event_callback({"id": "nonkaku_device_3", "command": "up"})
await hass.async_block_till_done()
inverted_cover = hass.states.get(DOMAIN + ".nonkaku_type_inverted")
assert inverted_cover.state == STATE_OPEN
assert inverted_cover.attributes.get("assumed_state")
# mock incoming up command event for newkaku_device_4
event_callback({"id": "newkaku_device_4", "command": "up"})
await hass.async_block_till_done()
inverted_cover = hass.states.get(DOMAIN + ".newkaku_type_standard")
assert inverted_cover.state == STATE_OPEN
assert inverted_cover.attributes.get("assumed_state")
# mock incoming up command event for newkaku_device_5
event_callback({"id": "newkaku_device_5", "command": "up"})
await hass.async_block_till_done()
inverted_cover = hass.states.get(DOMAIN + ".newkaku_type_none")
assert inverted_cover.state == STATE_OPEN
assert inverted_cover.attributes.get("assumed_state")
# mock incoming up command event for newkaku_device_6
event_callback({"id": "newkaku_device_6", "command": "up"})
await hass.async_block_till_done()
inverted_cover = hass.states.get(DOMAIN + ".newkaku_type_inverted")
assert inverted_cover.state == STATE_OPEN
assert inverted_cover.attributes.get("assumed_state")
# mock incoming down command event for nonkaku_device_1
event_callback({"id": "nonkaku_device_1", "command": "down"})
await hass.async_block_till_done()
standard_cover = hass.states.get(DOMAIN + ".nonkaku_type_standard")
assert standard_cover.state == STATE_CLOSED
assert standard_cover.attributes.get("assumed_state")
# mock incoming down command event for nonkaku_device_2
event_callback({"id": "nonkaku_device_2", "command": "down"})
await hass.async_block_till_done()
standard_cover = hass.states.get(DOMAIN + ".nonkaku_type_none")
assert standard_cover.state == STATE_CLOSED
assert standard_cover.attributes.get("assumed_state")
# mock incoming down command event for nonkaku_device_3
event_callback({"id": "nonkaku_device_3", "command": "down"})
await hass.async_block_till_done()
inverted_cover = hass.states.get(DOMAIN + ".nonkaku_type_inverted")
assert inverted_cover.state == STATE_CLOSED
assert inverted_cover.attributes.get("assumed_state")
# mock incoming down command event for newkaku_device_4
event_callback({"id": "newkaku_device_4", "command": "down"})
await hass.async_block_till_done()
inverted_cover = hass.states.get(DOMAIN + ".newkaku_type_standard")
assert inverted_cover.state == STATE_CLOSED
assert inverted_cover.attributes.get("assumed_state")
# mock incoming down command event for newkaku_device_5
event_callback({"id": "newkaku_device_5", "command": "down"})
await hass.async_block_till_done()
inverted_cover = hass.states.get(DOMAIN + ".newkaku_type_none")
assert inverted_cover.state == STATE_CLOSED
assert inverted_cover.attributes.get("assumed_state")
# mock incoming down command event for newkaku_device_6
event_callback({"id": "newkaku_device_6", "command": "down"})
await hass.async_block_till_done()
inverted_cover = hass.states.get(DOMAIN + ".newkaku_type_inverted")
assert inverted_cover.state == STATE_CLOSED
assert inverted_cover.attributes.get("assumed_state")
# We are only testing the 'inverted' devices, the 'standard' devices
# are already covered by other test cases.
# should respond to group command
event_callback({"id": "nonkaku_device_3", "command": "alloff"})
await hass.async_block_till_done()
inverted_cover = hass.states.get(DOMAIN + ".nonkaku_type_inverted")
assert inverted_cover.state == STATE_CLOSED
# should respond to group command
event_callback({"id": "nonkaku_device_3", "command": "allon"})
await hass.async_block_till_done()
inverted_cover = hass.states.get(DOMAIN + ".nonkaku_type_inverted")
assert inverted_cover.state == STATE_OPEN
# should respond to group command
event_callback({"id": "newkaku_device_4", "command": "alloff"})
await hass.async_block_till_done()
inverted_cover = hass.states.get(DOMAIN + ".newkaku_type_standard")
assert inverted_cover.state == STATE_CLOSED
# should respond to group command
event_callback({"id": "newkaku_device_4", "command": "allon"})
await hass.async_block_till_done()
inverted_cover = hass.states.get(DOMAIN + ".newkaku_type_standard")
assert inverted_cover.state == STATE_OPEN
# should respond to group command
event_callback({"id": "newkaku_device_5", "command": "alloff"})
await hass.async_block_till_done()
inverted_cover = hass.states.get(DOMAIN + ".newkaku_type_none")
assert inverted_cover.state == STATE_CLOSED
# should respond to group command
event_callback({"id": "newkaku_device_5", "command": "allon"})
await hass.async_block_till_done()
inverted_cover = hass.states.get(DOMAIN + ".newkaku_type_none")
assert inverted_cover.state == STATE_OPEN
# should respond to group command
event_callback({"id": "newkaku_device_6", "command": "alloff"})
await hass.async_block_till_done()
inverted_cover = hass.states.get(DOMAIN + ".newkaku_type_inverted")
assert inverted_cover.state == STATE_CLOSED
# should respond to group command
event_callback({"id": "newkaku_device_6", "command": "allon"})
await hass.async_block_till_done()
inverted_cover = hass.states.get(DOMAIN + ".newkaku_type_inverted")
assert inverted_cover.state == STATE_OPEN
# Sending the close command from HA should result
# in an 'DOWN' command sent to a non-newkaku device
# that has its type set to 'standard'.
hass.async_create_task(
hass.services.async_call(
DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: DOMAIN + ".nonkaku_type_standard"},
)
)
await hass.async_block_till_done()
assert hass.states.get(DOMAIN + ".nonkaku_type_standard").state == STATE_CLOSED
assert protocol.send_command_ack.call_args_list[0][0][0] == "nonkaku_device_1"
assert protocol.send_command_ack.call_args_list[0][0][1] == "DOWN"
# Sending the open command from HA should result
# in an 'UP' command sent to a non-newkaku device
# that has its type set to 'standard'.
hass.async_create_task(
hass.services.async_call(
DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: DOMAIN + ".nonkaku_type_standard"},
)
)
await hass.async_block_till_done()
assert hass.states.get(DOMAIN + ".nonkaku_type_standard").state == STATE_OPEN
assert protocol.send_command_ack.call_args_list[1][0][0] == "nonkaku_device_1"
assert protocol.send_command_ack.call_args_list[1][0][1] == "UP"
# Sending the close command from HA should result
# in an 'DOWN' command sent to a non-newkaku device
# that has its type not specified.
hass.async_create_task(
hass.services.async_call(
DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: DOMAIN + ".nonkaku_type_none"}
)
)
await hass.async_block_till_done()
assert hass.states.get(DOMAIN + ".nonkaku_type_none").state == STATE_CLOSED
assert protocol.send_command_ack.call_args_list[2][0][0] == "nonkaku_device_2"
assert protocol.send_command_ack.call_args_list[2][0][1] == "DOWN"
# Sending the open command from HA should result
# in an 'UP' command sent to a non-newkaku device
# that has its type not specified.
hass.async_create_task(
hass.services.async_call(
DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: DOMAIN + ".nonkaku_type_none"}
)
)
await hass.async_block_till_done()
assert hass.states.get(DOMAIN + ".nonkaku_type_none").state == STATE_OPEN
assert protocol.send_command_ack.call_args_list[3][0][0] == "nonkaku_device_2"
assert protocol.send_command_ack.call_args_list[3][0][1] == "UP"
# Sending the close command from HA should result
# in an 'UP' command sent to a non-newkaku device
# that has its type set to 'inverted'.
hass.async_create_task(
hass.services.async_call(
DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: DOMAIN + ".nonkaku_type_inverted"},
)
)
await hass.async_block_till_done()
assert hass.states.get(DOMAIN + ".nonkaku_type_inverted").state == STATE_CLOSED
assert protocol.send_command_ack.call_args_list[4][0][0] == "nonkaku_device_3"
assert protocol.send_command_ack.call_args_list[4][0][1] == "UP"
# Sending the open command from HA should result
# in an 'DOWN' command sent to a non-newkaku device
# that has its type set to 'inverted'.
hass.async_create_task(
hass.services.async_call(
DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: DOMAIN + ".nonkaku_type_inverted"},
)
)
await hass.async_block_till_done()
assert hass.states.get(DOMAIN + ".nonkaku_type_inverted").state == STATE_OPEN
assert protocol.send_command_ack.call_args_list[5][0][0] == "nonkaku_device_3"
assert protocol.send_command_ack.call_args_list[5][0][1] == "DOWN"
# Sending the close command from HA should result
# in an 'DOWN' command sent to a newkaku device
# that has its type set to 'standard'.
hass.async_create_task(
hass.services.async_call(
DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: DOMAIN + ".newkaku_type_standard"},
)
)
await hass.async_block_till_done()
assert hass.states.get(DOMAIN + ".newkaku_type_standard").state == STATE_CLOSED
assert protocol.send_command_ack.call_args_list[6][0][0] == "newkaku_device_4"
assert protocol.send_command_ack.call_args_list[6][0][1] == "DOWN"
# Sending the open command from HA should result
# in an 'UP' command sent to a newkaku device
# that has its type set to 'standard'.
hass.async_create_task(
hass.services.async_call(
DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: DOMAIN + ".newkaku_type_standard"},
)
)
await hass.async_block_till_done()
assert hass.states.get(DOMAIN + ".newkaku_type_standard").state == STATE_OPEN
assert protocol.send_command_ack.call_args_list[7][0][0] == "newkaku_device_4"
assert protocol.send_command_ack.call_args_list[7][0][1] == "UP"
# Sending the close command from HA should result
# in an 'UP' command sent to a newkaku device
# that has its type not specified.
hass.async_create_task(
hass.services.async_call(
DOMAIN, SERVICE_CLOSE_COVER, {ATTR_ENTITY_ID: DOMAIN + ".newkaku_type_none"}
)
)
await hass.async_block_till_done()
assert hass.states.get(DOMAIN + ".newkaku_type_none").state == STATE_CLOSED
assert protocol.send_command_ack.call_args_list[8][0][0] == "newkaku_device_5"
assert protocol.send_command_ack.call_args_list[8][0][1] == "UP"
# Sending the open command from HA should result
# in an 'DOWN' command sent to a newkaku device
# that has its type not specified.
hass.async_create_task(
hass.services.async_call(
DOMAIN, SERVICE_OPEN_COVER, {ATTR_ENTITY_ID: DOMAIN + ".newkaku_type_none"}
)
)
await hass.async_block_till_done()
assert hass.states.get(DOMAIN + ".newkaku_type_none").state == STATE_OPEN
assert protocol.send_command_ack.call_args_list[9][0][0] == "newkaku_device_5"
assert protocol.send_command_ack.call_args_list[9][0][1] == "DOWN"
# Sending the close command from HA should result
# in an 'UP' command sent to a newkaku device
# that has its type set to 'inverted'.
hass.async_create_task(
hass.services.async_call(
DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: DOMAIN + ".newkaku_type_inverted"},
)
)
await hass.async_block_till_done()
assert hass.states.get(DOMAIN + ".newkaku_type_inverted").state == STATE_CLOSED
assert protocol.send_command_ack.call_args_list[10][0][0] == "newkaku_device_6"
assert protocol.send_command_ack.call_args_list[10][0][1] == "UP"
# Sending the open command from HA should result
# in an 'DOWN' command sent to a newkaku device
# that has its type set to 'inverted'.
hass.async_create_task(
hass.services.async_call(
DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: DOMAIN + ".newkaku_type_inverted"},
)
)
await hass.async_block_till_done()
assert hass.states.get(DOMAIN + ".newkaku_type_inverted").state == STATE_OPEN
assert protocol.send_command_ack.call_args_list[11][0][0] == "newkaku_device_6"
assert protocol.send_command_ack.call_args_list[11][0][1] == "DOWN"