Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 41 additions & 5 deletions homeassistant/components/cover/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
from homeassistant.components.cover import (
CoverDevice, ATTR_TILT_POSITION, SUPPORT_OPEN_TILT,
SUPPORT_CLOSE_TILT, SUPPORT_STOP_TILT, SUPPORT_SET_TILT_POSITION,
SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP, SUPPORT_SET_POSITION)
SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP, SUPPORT_SET_POSITION,
ATTR_POSITION)
from homeassistant.exceptions import TemplateError
from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_OPTIMISTIC, STATE_OPEN,
STATE_CLOSED, STATE_UNKNOWN)
Expand All @@ -29,6 +31,8 @@

CONF_TILT_COMMAND_TOPIC = 'tilt_command_topic'
CONF_TILT_STATUS_TOPIC = 'tilt_status_topic'
CONF_POSITION_TOPIC = 'set_position_topic'
CONF_SET_POSITION_TEMPLATE = 'set_position_template'

CONF_PAYLOAD_OPEN = 'payload_open'
CONF_PAYLOAD_CLOSE = 'payload_close'
Expand All @@ -55,10 +59,17 @@
DEFAULT_TILT_OPTIMISTIC = False
DEFAULT_TILT_INVERT_STATE = False

OPEN_CLOSE_FEATURES = (SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP)
TILT_FEATURES = (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT |
SUPPORT_SET_TILT_POSITION)

PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_COMMAND_TOPIC, default=None): valid_publish_topic,
vol.Optional(CONF_POSITION_TOPIC, default=None): valid_publish_topic,
vol.Optional(CONF_SET_POSITION_TEMPLATE, default=None): cv.template,
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
vol.Optional(CONF_STATE_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_OPEN, default=DEFAULT_PAYLOAD_OPEN): cv.string,
vol.Optional(CONF_PAYLOAD_CLOSE, default=DEFAULT_PAYLOAD_CLOSE): cv.string,
Expand Down Expand Up @@ -87,6 +98,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
set_position_template = config.get(CONF_SET_POSITION_TEMPLATE)
if set_position_template is not None:
set_position_template.hass = hass

async_add_devices([MqttCover(
config.get(CONF_NAME),
Expand All @@ -109,6 +123,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_TILT_MAX),
config.get(CONF_TILT_STATE_OPTIMISTIC),
config.get(CONF_TILT_INVERT_STATE),
config.get(CONF_POSITION_TOPIC),
set_position_template,
)])


Expand All @@ -120,7 +136,7 @@ def __init__(self, name, state_topic, command_topic, tilt_command_topic,
payload_open, payload_close, payload_stop,
optimistic, value_template, tilt_open_position,
tilt_closed_position, tilt_min, tilt_max, tilt_optimistic,
tilt_invert):
tilt_invert, position_topic, set_position_template):
"""Initialize the cover."""
self._position = None
self._state = None
Expand All @@ -145,6 +161,8 @@ def __init__(self, name, state_topic, command_topic, tilt_command_topic,
self._tilt_max = tilt_max
self._tilt_optimistic = tilt_optimistic
self._tilt_invert = tilt_invert
self._position_topic = position_topic
self._set_position_template = set_position_template

@asyncio.coroutine
def async_added_to_hass(self):
Expand Down Expand Up @@ -233,9 +251,11 @@ def current_cover_tilt_position(self):
@property
def supported_features(self):
"""Flag supported features."""
supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP
supported_features = 0
if self._command_topic is not None:
supported_features = OPEN_CLOSE_FEATURES

if self.current_cover_position is not None:
if self._position_topic is not None:
supported_features |= SUPPORT_SET_POSITION

if self._tilt_command_topic is not None:
Expand Down Expand Up @@ -315,6 +335,22 @@ def async_set_cover_tilt_position(self, **kwargs):
mqtt.async_publish(self.hass, self._tilt_command_topic,
level, self._qos, self._retain)

@asyncio.coroutine
def async_set_cover_position(self, **kwargs):
"""Move the cover to a specific position."""
if ATTR_POSITION in kwargs:
position = kwargs[ATTR_POSITION]
if self._set_position_template is not None:
try:
position = self._set_position_template.async_render(
**kwargs)
except TemplateError as ex:
_LOGGER.error(ex)
self._state = None

mqtt.async_publish(self.hass, self._position_topic,
position, self._qos, self._retain)

def find_percentage_in_range(self, position):
"""Find the 0-100% value within the specified range."""
# the range of motion as defined by the min max values
Expand Down
131 changes: 123 additions & 8 deletions tests/components/cover/test_mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ def test_current_cover_position(self):
'cover.test').attributes
self.assertFalse('current_position' in state_attributes_dict)
self.assertFalse('current_tilt_position' in state_attributes_dict)
self.assertFalse(4 & self.hass.states.get(
'cover.test').attributes['supported_features'] == 4)

fire_mqtt_message(self.hass, 'state-topic', '0')
self.hass.block_till_done()
Expand All @@ -240,6 +242,119 @@ def test_current_cover_position(self):
'cover.test').attributes['current_position']
self.assertEqual(50, current_cover_position)

def test_set_cover_position(self):
"""Test setting cover position."""
self.assertTrue(setup_component(self.hass, cover.DOMAIN, {
cover.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'set_position_topic': 'position-topic',
'payload_open': 'OPEN',
'payload_close': 'CLOSE',
'payload_stop': 'STOP'
}
}))

state_attributes_dict = self.hass.states.get(
'cover.test').attributes
self.assertFalse('current_position' in state_attributes_dict)
self.assertFalse('current_tilt_position' in state_attributes_dict)

self.assertTrue(4 & self.hass.states.get(
'cover.test').attributes['supported_features'] == 4)

fire_mqtt_message(self.hass, 'state-topic', '22')
self.hass.block_till_done()
state_attributes_dict = self.hass.states.get(
'cover.test').attributes
self.assertTrue('current_position' in state_attributes_dict)
self.assertFalse('current_tilt_position' in state_attributes_dict)
current_cover_position = self.hass.states.get(
'cover.test').attributes['current_position']
self.assertEqual(22, current_cover_position)

def test_set_position_templated(self):
"""Test setting cover position via template."""
self.assertTrue(setup_component(self.hass, cover.DOMAIN, {
cover.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'set_position_topic': 'position-topic',
'set_position_template': '{{100-62}}',
'payload_open': 'OPEN',
'payload_close': 'CLOSE',
'payload_stop': 'STOP'
}
}))

cover.set_cover_position(self.hass, 100, 'cover.test')
self.hass.block_till_done()

self.assertEqual(('position-topic', '38', 0, False),
self.mock_publish.mock_calls[-2][1])

def test_set_position_untemplated(self):
"""Test setting cover position via template."""
self.assertTrue(setup_component(self.hass, cover.DOMAIN, {
cover.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'set_position_topic': 'position-topic',
'payload_open': 'OPEN',
'payload_close': 'CLOSE',
'payload_stop': 'STOP'
}
}))

cover.set_cover_position(self.hass, 62, 'cover.test')
self.hass.block_till_done()

self.assertEqual(('position-topic', 62, 0, False),
self.mock_publish.mock_calls[-2][1])

def test_no_command_topic(self):
"""Test with no command topic."""
self.assertTrue(setup_component(self.hass, cover.DOMAIN, {
cover.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'qos': 0,
'payload_open': 'OPEN',
'payload_close': 'CLOSE',
'payload_stop': 'STOP',
'tilt_command_topic': 'tilt-command',
'tilt_status_topic': 'tilt-status'
}
}))

self.assertEqual(240, self.hass.states.get(
'cover.test').attributes['supported_features'])

def test_with_command_topic_and_tilt(self):
"""Test with command topic and tilt config."""
self.assertTrue(setup_component(self.hass, cover.DOMAIN, {
cover.DOMAIN: {
'command_topic': 'test',
'platform': 'mqtt',
'name': 'test',
'qos': 0,
'payload_open': 'OPEN',
'payload_close': 'CLOSE',
'payload_stop': 'STOP',
'tilt_command_topic': 'tilt-command',
'tilt_status_topic': 'tilt-status'
}
}))

self.assertEqual(251, self.hass.states.get(
'cover.test').attributes['supported_features'])

def test_tilt_defaults(self):
"""Test the defaults."""
self.assertTrue(setup_component(self.hass, cover.DOMAIN, {
Expand Down Expand Up @@ -457,7 +572,7 @@ def test_find_percentage_in_range_defaults(self):
mqtt_cover = MqttCover(
'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', False, None,
100, 0, 0, 100, False, False)
100, 0, 0, 100, False, False, None, None)

self.assertEqual(44, mqtt_cover.find_percentage_in_range(44))

Expand All @@ -466,7 +581,7 @@ def test_find_percentage_in_range_altered(self):
mqtt_cover = MqttCover(
'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', False, None,
180, 80, 80, 180, False, False)
180, 80, 80, 180, False, False, None, None)

self.assertEqual(40, mqtt_cover.find_percentage_in_range(120))

Expand All @@ -475,7 +590,7 @@ def test_find_percentage_in_range_defaults_inverted(self):
mqtt_cover = MqttCover(
'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', False, None,
100, 0, 0, 100, False, True)
100, 0, 0, 100, False, True, None, None)

self.assertEqual(56, mqtt_cover.find_percentage_in_range(44))

Expand All @@ -484,7 +599,7 @@ def test_find_percentage_in_range_altered_inverted(self):
mqtt_cover = MqttCover(
'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', False, None,
180, 80, 80, 180, False, True)
180, 80, 80, 180, False, True, None, None)

self.assertEqual(60, mqtt_cover.find_percentage_in_range(120))

Expand All @@ -493,7 +608,7 @@ def test_find_in_range_defaults(self):
mqtt_cover = MqttCover(
'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', False, None,
100, 0, 0, 100, False, False)
100, 0, 0, 100, False, False, None, None)

self.assertEqual(44, mqtt_cover.find_in_range_from_percent(44))

Expand All @@ -502,7 +617,7 @@ def test_find_in_range_altered(self):
mqtt_cover = MqttCover(
'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', False, None,
180, 80, 80, 180, False, False)
180, 80, 80, 180, False, False, None, None)

self.assertEqual(120, mqtt_cover.find_in_range_from_percent(40))

Expand All @@ -511,7 +626,7 @@ def test_find_in_range_defaults_inverted(self):
mqtt_cover = MqttCover(
'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', False, None,
100, 0, 0, 100, False, True)
100, 0, 0, 100, False, True, None, None)

self.assertEqual(44, mqtt_cover.find_in_range_from_percent(56))

Expand All @@ -520,6 +635,6 @@ def test_find_in_range_altered_inverted(self):
mqtt_cover = MqttCover(
'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', False, None,
180, 80, 80, 180, False, True)
180, 80, 80, 180, False, True, None, None)

self.assertEqual(120, mqtt_cover.find_in_range_from_percent(60))