-
-
Notifications
You must be signed in to change notification settings - Fork 37.7k
Mqtt cover modifications #7841
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mqtt cover modifications #7841
Changes from 2 commits
42f26bb
0d93c3c
e36e0b3
9cfc5ed
43747e5
9931659
5c71458
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) | ||
|
|
@@ -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' | ||
|
|
@@ -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, | ||
|
|
@@ -89,6 +100,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): | |
| value_template.hass = hass | ||
|
|
||
| async_add_devices([MqttCover( | ||
| hass, | ||
| config.get(CONF_NAME), | ||
| config.get(CONF_STATE_TOPIC), | ||
| config.get(CONF_COMMAND_TOPIC), | ||
|
|
@@ -109,18 +121,21 @@ 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), | ||
| config.get(CONF_SET_POSITION_TEMPLATE), | ||
| )]) | ||
|
|
||
|
|
||
| class MqttCover(CoverDevice): | ||
| """Representation of a cover that can be controlled using MQTT.""" | ||
|
|
||
| def __init__(self, name, state_topic, command_topic, tilt_command_topic, | ||
| def __init__(self, hass, name, state_topic, command_topic, | ||
| tilt_command_topic, | ||
| tilt_status_topic, qos, retain, state_open, state_closed, | ||
| 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 | ||
|
|
@@ -145,6 +160,10 @@ 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 | ||
| if set_position_template is not None: | ||
| self._set_position_template.hass = hass | ||
|
|
||
| @asyncio.coroutine | ||
| def async_added_to_hass(self): | ||
|
|
@@ -233,7 +252,12 @@ 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._position_topic is not None: | ||
| supported_features |= SUPPORT_SET_POSITION | ||
|
|
||
| if self.current_cover_position is not None: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should now be removed since we're using
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you mean remove the block starting at line 262?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that's right. |
||
| supported_features |= SUPPORT_SET_POSITION | ||
|
|
@@ -315,6 +339,21 @@ 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() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change this to |
||
| 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 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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() | ||
|
|
@@ -240,6 +242,126 @@ 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) | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. blank line contains whitespace |
||
| 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): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. too many blank lines (2) |
||
| """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' | ||
| } | ||
| })) | ||
|
|
||
| state_attributes_dict = self.hass.states.get( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. local variable 'state_attributes_dict' is assigned to but never used |
||
| 'cover.test').attributes | ||
|
|
||
| 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' | ||
| } | ||
| })) | ||
|
|
||
| state_attributes_dict = self.hass.states.get( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. local variable 'state_attributes_dict' is assigned to but never used |
||
| 'cover.test').attributes | ||
|
|
||
| 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, { | ||
|
|
@@ -455,71 +577,71 @@ def test_tilt_position_altered_range(self): | |
| def test_find_percentage_in_range_defaults(self): | ||
| """Test find percentage in range with default range.""" | ||
| mqtt_cover = MqttCover( | ||
| 'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False, | ||
| None, '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)) | ||
|
|
||
| def test_find_percentage_in_range_altered(self): | ||
| """Test find percentage in range with altered range.""" | ||
| mqtt_cover = MqttCover( | ||
| 'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False, | ||
| None, '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)) | ||
|
|
||
| def test_find_percentage_in_range_defaults_inverted(self): | ||
| """Test find percentage in range with default range but inverted.""" | ||
| mqtt_cover = MqttCover( | ||
| 'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False, | ||
| None, '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)) | ||
|
|
||
| def test_find_percentage_in_range_altered_inverted(self): | ||
| """Test find percentage in range with altered range and inverted.""" | ||
| mqtt_cover = MqttCover( | ||
| 'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False, | ||
| None, '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)) | ||
|
|
||
| def test_find_in_range_defaults(self): | ||
| """Test find in range with default range.""" | ||
| mqtt_cover = MqttCover( | ||
| 'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False, | ||
| None, '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)) | ||
|
|
||
| def test_find_in_range_altered(self): | ||
| """Test find in range with altered range.""" | ||
| mqtt_cover = MqttCover( | ||
| 'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False, | ||
| None, '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)) | ||
|
|
||
| def test_find_in_range_defaults_inverted(self): | ||
| """Test find in range with default range but inverted.""" | ||
| mqtt_cover = MqttCover( | ||
| 'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False, | ||
| None, '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)) | ||
|
|
||
| def test_find_in_range_altered_inverted(self): | ||
| """Test find in range with altered range and inverted.""" | ||
| mqtt_cover = MqttCover( | ||
| 'cover.test', 'foo', 'bar', 'fooBar', "fooBarBaz", 0, False, | ||
| None, '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)) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you put this logic in
async_setup_platformso we don't need to passhassin? Let's make it look the same asvalue_template.