More support for availability reporting on MQTT components (#11336)

* Abstract MQTT availability from individual components

- Moved availability topic and payloads to MQTT base schema.
- Updated components that already report availability:
  - Switch
  - Binary sensor
  - Cover

* Add availability reporting to additional MQTT components

- Light
- JSON light
- Template light
- Lock
- Fan
- HVAC
- Sensor
- Vacuum
- Alarm control panel

* Annotate MQTT platform coroutines
pull/10479/merge
Dan Nixon 2018-01-02 02:32:29 +00:00 committed by Paulus Schoutsen
parent 541707c3e7
commit f0bf7b0def
25 changed files with 530 additions and 193 deletions

View File

@ -17,7 +17,9 @@ from homeassistant.const import (
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN,
CONF_NAME, CONF_CODE)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS)
CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS,
MqttAvailability)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -54,15 +56,21 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_PAYLOAD_DISARM),
config.get(CONF_PAYLOAD_ARM_HOME),
config.get(CONF_PAYLOAD_ARM_AWAY),
config.get(CONF_CODE))])
config.get(CONF_CODE),
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE))])
class MqttAlarm(alarm.AlarmControlPanel):
class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
"""Representation of a MQTT alarm status."""
def __init__(self, name, state_topic, command_topic, qos, payload_disarm,
payload_arm_home, payload_arm_away, code):
payload_arm_home, payload_arm_away, code, availability_topic,
payload_available, payload_not_available):
"""Init the MQTT Alarm Control Panel."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
self._state = STATE_UNKNOWN
self._name = name
self._state_topic = state_topic
@ -73,11 +81,11 @@ class MqttAlarm(alarm.AlarmControlPanel):
self._payload_arm_away = payload_arm_away
self._code = code
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe mqtt events.
"""Subscribe mqtt events."""
yield from super().async_added_to_hass()
This method must be run in the event loop and returns a coroutine.
"""
@callback
def message_received(topic, payload, qos):
"""Run when new MQTT message has been received."""
@ -89,7 +97,7 @@ class MqttAlarm(alarm.AlarmControlPanel):
self._state = payload
self.async_schedule_update_ha_state()
return mqtt.async_subscribe(
yield from mqtt.async_subscribe(
self.hass, self._state_topic, message_received, self._qos)
@property

View File

@ -17,19 +17,15 @@ from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF,
CONF_DEVICE_CLASS)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC, CONF_QOS, valid_subscribe_topic)
CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC, CONF_PAYLOAD_AVAILABLE,
CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, MqttAvailability)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
CONF_PAYLOAD_AVAILABLE = 'payload_available'
CONF_PAYLOAD_NOT_AVAILABLE = 'payload_not_available'
DEFAULT_NAME = 'MQTT Binary sensor'
DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_PAYLOAD_AVAILABLE = 'online'
DEFAULT_PAYLOAD_NOT_AVAILABLE = 'offline'
DEPENDENCIES = ['mqtt']
@ -38,12 +34,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_AVAILABILITY_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_PAYLOAD_AVAILABLE,
default=DEFAULT_PAYLOAD_AVAILABLE): cv.string,
vol.Optional(CONF_PAYLOAD_NOT_AVAILABLE,
default=DEFAULT_PAYLOAD_NOT_AVAILABLE): cv.string,
})
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@asyncio.coroutine
@ -70,31 +61,29 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
)])
class MqttBinarySensor(BinarySensorDevice):
class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
"""Representation a binary sensor that is updated by MQTT."""
def __init__(self, name, state_topic, availability_topic, device_class,
qos, payload_on, payload_off, payload_available,
payload_not_available, value_template):
"""Initialize the MQTT binary sensor."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
self._name = name
self._state = None
self._state_topic = state_topic
self._availability_topic = availability_topic
self._available = True if availability_topic is None else False
self._device_class = device_class
self._payload_on = payload_on
self._payload_off = payload_off
self._payload_available = payload_available
self._payload_not_available = payload_not_available
self._qos = qos
self._template = value_template
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe mqtt events.
"""Subscribe mqtt events."""
yield from super().async_added_to_hass()
This method must be run in the event loop and returns a coroutine.
"""
@callback
def state_message_received(topic, payload, qos):
"""Handle a new received MQTT state message."""
@ -111,21 +100,6 @@ class MqttBinarySensor(BinarySensorDevice):
yield from mqtt.async_subscribe(
self.hass, self._state_topic, state_message_received, self._qos)
@callback
def availability_message_received(topic, payload, qos):
"""Handle a new received MQTT availability message."""
if payload == self._payload_available:
self._available = True
elif payload == self._payload_not_available:
self._available = False
self.async_schedule_update_ha_state()
if self._availability_topic is not None:
yield from mqtt.async_subscribe(
self.hass, self._availability_topic,
availability_message_received, self._qos)
@property
def should_poll(self):
"""Return the polling state."""
@ -136,11 +110,6 @@ class MqttBinarySensor(BinarySensorDevice):
"""Return the name of the binary sensor."""
return self._name
@property
def available(self) -> bool:
"""Return if the binary sensor is available."""
return self._available
@property
def is_on(self):
"""Return true if the binary sensor is on."""

View File

@ -60,11 +60,9 @@ class MqttCamera(Camera):
"""Return the name of this camera."""
return self._name
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe MQTT events.
This method must be run in the event loop and returns a coroutine.
"""
"""Subscribe MQTT events."""
@callback
def message_received(topic, payload, qos):
"""Handle new MQTT messages."""

View File

@ -20,8 +20,9 @@ from homeassistant.components.climate import (
SUPPORT_AUX_HEAT)
from homeassistant.const import (
STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME)
from homeassistant.components.mqtt import (CONF_QOS, CONF_RETAIN,
MQTT_BASE_PLATFORM_SCHEMA)
from homeassistant.components.mqtt import (
CONF_AVAILABILITY_TOPIC, CONF_QOS, CONF_RETAIN, CONF_PAYLOAD_AVAILABLE,
CONF_PAYLOAD_NOT_AVAILABLE, MQTT_BASE_PLATFORM_SCHEMA, MqttAvailability)
import homeassistant.helpers.config_validation as cv
from homeassistant.components.fan import (SPEED_LOW, SPEED_MEDIUM,
SPEED_HIGH)
@ -93,7 +94,7 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean,
vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string,
})
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@asyncio.coroutine
@ -134,19 +135,25 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
STATE_OFF, STATE_OFF, False,
config.get(CONF_SEND_IF_OFF),
config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF))
config.get(CONF_PAYLOAD_OFF),
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE))
])
class MqttClimate(ClimateDevice):
class MqttClimate(MqttAvailability, ClimateDevice):
"""Representation of a demo climate device."""
def __init__(self, hass, name, topic, qos, retain, mode_list,
fan_mode_list, swing_mode_list, target_temperature, away,
hold, current_fan_mode, current_swing_mode,
current_operation, aux, send_if_off, payload_on,
payload_off):
payload_off, availability_topic, payload_available,
payload_not_available):
"""Initialize the climate device."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
self.hass = hass
self._name = name
self._topic = topic
@ -169,8 +176,11 @@ class MqttClimate(ClimateDevice):
self._payload_on = payload_on
self._payload_off = payload_off
@asyncio.coroutine
def async_added_to_hass(self):
"""Handle being added to home assistant."""
yield from super().async_added_to_hass()
@callback
def handle_current_temp_received(topic, payload, qos):
"""Handle current temperature coming via MQTT."""

View File

@ -21,8 +21,9 @@ from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_OPTIMISTIC, STATE_OPEN,
STATE_CLOSED, STATE_UNKNOWN)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_AVAILABILITY_TOPIC,
CONF_QOS, CONF_RETAIN, valid_publish_topic, valid_subscribe_topic)
CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN,
valid_publish_topic, valid_subscribe_topic, MqttAvailability)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -37,8 +38,6 @@ CONF_SET_POSITION_TEMPLATE = 'set_position_template'
CONF_PAYLOAD_OPEN = 'payload_open'
CONF_PAYLOAD_CLOSE = 'payload_close'
CONF_PAYLOAD_STOP = 'payload_stop'
CONF_PAYLOAD_AVAILABLE = 'payload_available'
CONF_PAYLOAD_NOT_AVAILABLE = 'payload_not_available'
CONF_STATE_OPEN = 'state_open'
CONF_STATE_CLOSED = 'state_closed'
CONF_TILT_CLOSED_POSITION = 'tilt_closed_value'
@ -52,8 +51,6 @@ DEFAULT_NAME = 'MQTT Cover'
DEFAULT_PAYLOAD_OPEN = 'OPEN'
DEFAULT_PAYLOAD_CLOSE = 'CLOSE'
DEFAULT_PAYLOAD_STOP = 'STOP'
DEFAULT_PAYLOAD_AVAILABLE = 'online'
DEFAULT_PAYLOAD_NOT_AVAILABLE = 'offline'
DEFAULT_OPTIMISTIC = False
DEFAULT_RETAIN = False
DEFAULT_TILT_CLOSED_POSITION = 0
@ -73,16 +70,11 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
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_AVAILABILITY_TOPIC, default=None): 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,
vol.Optional(CONF_PAYLOAD_STOP, default=DEFAULT_PAYLOAD_STOP): cv.string,
vol.Optional(CONF_PAYLOAD_AVAILABLE,
default=DEFAULT_PAYLOAD_AVAILABLE): cv.string,
vol.Optional(CONF_PAYLOAD_NOT_AVAILABLE,
default=DEFAULT_PAYLOAD_NOT_AVAILABLE): cv.string,
vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string,
vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
@ -98,7 +90,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
default=DEFAULT_TILT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_TILT_INVERT_STATE,
default=DEFAULT_TILT_INVERT_STATE): cv.boolean,
})
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@asyncio.coroutine
@ -143,7 +135,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
)])
class MqttCover(CoverDevice):
class MqttCover(MqttAvailability, CoverDevice):
"""Representation of a cover that can be controlled using MQTT."""
def __init__(self, name, state_topic, command_topic, availability_topic,
@ -154,21 +146,19 @@ class MqttCover(CoverDevice):
tilt_closed_position, tilt_min, tilt_max, tilt_optimistic,
tilt_invert, position_topic, set_position_template):
"""Initialize the cover."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
self._position = None
self._state = None
self._name = name
self._state_topic = state_topic
self._command_topic = command_topic
self._availability_topic = availability_topic
self._available = True if availability_topic is None else False
self._tilt_command_topic = tilt_command_topic
self._tilt_status_topic = tilt_status_topic
self._qos = qos
self._payload_open = payload_open
self._payload_close = payload_close
self._payload_stop = payload_stop
self._payload_available = payload_available
self._payload_not_available = payload_not_available
self._state_open = state_open
self._state_closed = state_closed
self._retain = retain
@ -186,10 +176,9 @@ class MqttCover(CoverDevice):
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe MQTT events.
"""Subscribe MQTT events."""
yield from super().async_added_to_hass()
This method is a coroutine.
"""
@callback
def tilt_updated(topic, payload, qos):
"""Handle tilt updates."""
@ -266,11 +255,6 @@ class MqttCover(CoverDevice):
"""Return the name of the cover."""
return self._name
@property
def available(self) -> bool:
"""Return if cover is available."""
return self._available
@property
def is_closed(self):
"""Return if the cover is closed."""

View File

@ -15,7 +15,9 @@ from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_STATE, STATE_ON, STATE_OFF,
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN,
MqttAvailability)
import homeassistant.helpers.config_validation as cv
from homeassistant.components.fan import (SPEED_LOW, SPEED_MEDIUM,
SPEED_HIGH, FanEntity,
@ -72,7 +74,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
default=[SPEED_OFF, SPEED_LOW,
SPEED_MEDIUM, SPEED_HIGH]): cv.ensure_list,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
})
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@asyncio.coroutine
@ -111,15 +113,21 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
},
config.get(CONF_SPEED_LIST),
config.get(CONF_OPTIMISTIC),
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
)])
class MqttFan(FanEntity):
class MqttFan(MqttAvailability, FanEntity):
"""A MQTT fan component."""
def __init__(self, name, topic, templates, qos, retain, payload,
speed_list, optimistic):
speed_list, optimistic, availability_topic, payload_available,
payload_not_available):
"""Initialize the MQTT fan."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
self._name = name
self._topic = topic
self._qos = qos
@ -143,10 +151,9 @@ class MqttFan(FanEntity):
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe to MQTT events.
"""Subscribe to MQTT events."""
yield from super().async_added_to_hass()
This method is a coroutine.
"""
templates = {}
for key, tpl in list(self._templates.items()):
if tpl is None:

View File

@ -21,7 +21,9 @@ from homeassistant.const import (
CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON,
CONF_RGB, CONF_STATE, CONF_VALUE_TEMPLATE, CONF_WHITE_VALUE, CONF_XY)
from homeassistant.components.mqtt import (
CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC)
CONF_AVAILABILITY_TOPIC, CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE,
CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC,
MqttAvailability)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -95,7 +97,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_XY_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_ON_COMMAND_TYPE, default=DEFAULT_ON_COMMAND_TYPE):
vol.In(VALUES_ON_COMMAND_TYPE),
})
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@asyncio.coroutine
@ -148,16 +150,22 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_BRIGHTNESS_SCALE),
config.get(CONF_WHITE_VALUE_SCALE),
config.get(CONF_ON_COMMAND_TYPE),
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
)])
class MqttLight(Light):
class MqttLight(MqttAvailability, Light):
"""Representation of a MQTT light."""
def __init__(self, name, effect_list, topic, templates, qos,
retain, payload, optimistic, brightness_scale,
white_value_scale, on_command_type):
white_value_scale, on_command_type, availability_topic,
payload_available, payload_not_available):
"""Initialize MQTT light."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
self._name = name
self._effect_list = effect_list
self._topic = topic
@ -208,10 +216,9 @@ class MqttLight(Light):
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe to MQTT events.
"""Subscribe to MQTT events."""
yield from super().async_added_to_hass()
This method is a coroutine.
"""
templates = {}
for key, tpl in list(self._templates.items()):
if tpl is None:

View File

@ -21,7 +21,9 @@ from homeassistant.const import (
CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_EFFECT,
CONF_NAME, CONF_OPTIMISTIC, CONF_RGB, CONF_WHITE_VALUE, CONF_XY)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN,
MqttAvailability)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -66,7 +68,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_WHITE_VALUE, default=DEFAULT_WHITE_VALUE): cv.boolean,
vol.Optional(CONF_XY, default=DEFAULT_XY): cv.boolean,
vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
})
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@asyncio.coroutine
@ -97,17 +99,23 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
CONF_FLASH_TIME_SHORT,
CONF_FLASH_TIME_LONG
)
}
},
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE)
)])
class MqttJson(Light):
class MqttJson(MqttAvailability, Light):
"""Representation of a MQTT JSON light."""
def __init__(self, name, effect_list, topic, qos, retain, optimistic,
brightness, color_temp, effect, rgb, white_value, xy,
flash_times):
flash_times, availability_topic, payload_available,
payload_not_available):
"""Initialize MQTT JSON light."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
self._name = name
self._effect_list = effect_list
self._topic = topic
@ -157,10 +165,9 @@ class MqttJson(Light):
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe to MQTT events.
"""Subscribe to MQTT events."""
yield from super().async_added_to_hass()
This method is a coroutine.
"""
@callback
def state_received(topic, payload, qos):
"""Handle new MQTT messages."""

View File

@ -17,7 +17,9 @@ from homeassistant.components.light import (
SUPPORT_RGB_COLOR, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE)
from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, STATE_ON, STATE_OFF
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN,
MqttAvailability)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@ -60,7 +62,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_QOS, default=mqtt.DEFAULT_QOS):
vol.All(vol.Coerce(int), vol.In([0, 1, 2])),
})
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@asyncio.coroutine
@ -95,16 +97,22 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
},
config.get(CONF_OPTIMISTIC),
config.get(CONF_QOS),
config.get(CONF_RETAIN)
config.get(CONF_RETAIN),
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
)])
class MqttTemplate(Light):
class MqttTemplate(MqttAvailability, Light):
"""Representation of a MQTT Template light."""
def __init__(self, hass, name, effect_list, topics, templates, optimistic,
qos, retain):
qos, retain, availability_topic, payload_available,
payload_not_available):
"""Initialize a MQTT Template light."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
self._name = name
self._effect_list = effect_list
self._topics = topics
@ -145,10 +153,9 @@ class MqttTemplate(Light):
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe to MQTT events.
"""Subscribe to MQTT events."""
yield from super().async_added_to_hass()
This method is a coroutine.
"""
@callback
def state_received(topic, payload, qos):
"""Handle new MQTT messages."""

View File

@ -12,7 +12,9 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components.lock import LockDevice
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN)
CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN,
MqttAvailability)
from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE)
import homeassistant.components.mqtt as mqtt
@ -36,7 +38,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PAYLOAD_UNLOCK, default=DEFAULT_PAYLOAD_UNLOCK):
cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
})
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@asyncio.coroutine
@ -56,15 +58,21 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_PAYLOAD_UNLOCK),
config.get(CONF_OPTIMISTIC),
value_template,
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE)
)])
class MqttLock(LockDevice):
class MqttLock(MqttAvailability, LockDevice):
"""Representation of a lock that can be toggled using MQTT."""
def __init__(self, name, state_topic, command_topic, qos, retain,
payload_lock, payload_unlock, optimistic, value_template):
payload_lock, payload_unlock, optimistic, value_template,
availability_topic, payload_available, payload_not_available):
"""Initialize the lock."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
self._state = False
self._name = name
self._state_topic = state_topic
@ -78,10 +86,9 @@ class MqttLock(LockDevice):
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe to MQTT events.
"""Subscribe to MQTT events."""
yield from super().async_added_to_hass()
This method is a coroutine.
"""
@callback
def message_received(topic, payload, qos):
"""Handle new MQTT messages."""

View File

@ -23,6 +23,7 @@ from homeassistant.loader import bind_hass
from homeassistant.helpers import template, config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, dispatcher_send)
from homeassistant.helpers.entity import Entity
from homeassistant.util.async import (
run_coroutine_threadsafe, run_callback_threadsafe)
from homeassistant.const import (
@ -59,6 +60,8 @@ CONF_WILL_MESSAGE = 'will_message'
CONF_STATE_TOPIC = 'state_topic'
CONF_COMMAND_TOPIC = 'command_topic'
CONF_AVAILABILITY_TOPIC = 'availability_topic'
CONF_PAYLOAD_AVAILABLE = 'payload_available'
CONF_PAYLOAD_NOT_AVAILABLE = 'payload_not_available'
CONF_QOS = 'qos'
CONF_RETAIN = 'retain'
@ -73,6 +76,8 @@ DEFAULT_PROTOCOL = PROTOCOL_311
DEFAULT_DISCOVERY = False
DEFAULT_DISCOVERY_PREFIX = 'homeassistant'
DEFAULT_TLS_PROTOCOL = 'auto'
DEFAULT_PAYLOAD_AVAILABLE = 'online'
DEFAULT_PAYLOAD_NOT_AVAILABLE = 'offline'
ATTR_TOPIC = 'topic'
ATTR_PAYLOAD = 'payload'
@ -145,6 +150,14 @@ SCHEMA_BASE = {
vol.Optional(CONF_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA,
}
MQTT_AVAILABILITY_SCHEMA = vol.Schema({
vol.Optional(CONF_AVAILABILITY_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_PAYLOAD_AVAILABLE,
default=DEFAULT_PAYLOAD_AVAILABLE): cv.string,
vol.Optional(CONF_PAYLOAD_NOT_AVAILABLE,
default=DEFAULT_PAYLOAD_NOT_AVAILABLE): cv.string,
})
MQTT_BASE_PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(SCHEMA_BASE)
# Sensor type platforms subscribe to MQTT events
@ -653,3 +666,41 @@ def _match_topic(subscription, topic):
reg = re.compile(reg_ex)
return reg.match(topic) is not None
class MqttAvailability(Entity):
"""Mixin used for platforms that report availability."""
def __init__(self, availability_topic, qos, payload_available,
payload_not_available):
"""Initialize the availability mixin."""
self._availability_topic = availability_topic
self._availability_qos = qos
self._available = availability_topic is None
self._payload_available = payload_available
self._payload_not_available = payload_not_available
def async_added_to_hass(self):
"""Subscribe mqtt events.
This method must be run in the event loop and returns a coroutine.
"""
@callback
def availability_message_received(topic, payload, qos):
"""Handle a new received MQTT availability message."""
if payload == self._payload_available:
self._available = True
elif payload == self._payload_not_available:
self._available = False
self.async_schedule_update_ha_state()
if self._availability_topic is not None:
yield from async_subscribe(
self.hass, self._availability_topic,
availability_message_received, self. _availability_qos)
@property
def available(self) -> bool:
"""Return if the device is available."""
return self._available

View File

@ -11,7 +11,9 @@ from datetime import timedelta
import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components.mqtt import CONF_STATE_TOPIC, CONF_QOS
from homeassistant.components.mqtt import (
CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_PAYLOAD_AVAILABLE,
CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, MqttAvailability)
from homeassistant.const import (
CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN,
CONF_UNIT_OF_MEASUREMENT)
@ -34,7 +36,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int,
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
})
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@asyncio.coroutine
@ -55,15 +57,21 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_FORCE_UPDATE),
config.get(CONF_EXPIRE_AFTER),
value_template,
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
)])
class MqttSensor(Entity):
class MqttSensor(MqttAvailability, Entity):
"""Representation of a sensor that can be updated using MQTT."""
def __init__(self, name, state_topic, qos, unit_of_measurement,
force_update, expire_after, value_template):
force_update, expire_after, value_template,
availability_topic, payload_available, payload_not_available):
"""Initialize the sensor."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
self._state = STATE_UNKNOWN
self._name = name
self._state_topic = state_topic
@ -74,11 +82,11 @@ class MqttSensor(Entity):
self._expire_after = expire_after
self._expiration_trigger = None
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe to MQTT events.
"""Subscribe to MQTT events."""
yield from super().async_added_to_hass()
This method must be run in the event loop and returns a coroutine.
"""
@callback
def message_received(topic, payload, qos):
"""Handle new MQTT messages."""
@ -102,7 +110,7 @@ class MqttSensor(Entity):
self._state = payload
self.async_schedule_update_ha_state()
return mqtt.async_subscribe(
yield from mqtt.async_subscribe(
self.hass, self._state_topic, message_received, self._qos)
@callback

View File

@ -80,11 +80,9 @@ class MQTTRoomSensor(Entity):
self._distance = None
self._updated = None
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe to MQTT events.
This method must be run in the event loop and returns a coroutine.
"""
"""Subscribe to MQTT events."""
@callback
def update_state(device_id, room, distance):
"""Update the sensor state."""

View File

@ -11,8 +11,9 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_AVAILABILITY_TOPIC, CONF_QOS,
CONF_RETAIN)
CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, CONF_AVAILABILITY_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN,
MqttAvailability)
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_OFF,
@ -24,26 +25,17 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mqtt']
CONF_PAYLOAD_AVAILABLE = 'payload_available'
CONF_PAYLOAD_NOT_AVAILABLE = 'payload_not_available'
DEFAULT_NAME = 'MQTT Switch'
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_OPTIMISTIC = False
DEFAULT_PAYLOAD_AVAILABLE = 'ON'
DEFAULT_PAYLOAD_NOT_AVAILABLE = 'OFF'
PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_PAYLOAD_AVAILABLE,
default=DEFAULT_PAYLOAD_AVAILABLE): cv.string,
vol.Optional(CONF_PAYLOAD_NOT_AVAILABLE,
default=DEFAULT_PAYLOAD_NOT_AVAILABLE): cv.string,
})
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@asyncio.coroutine
@ -72,34 +64,31 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
)])
class MqttSwitch(SwitchDevice):
class MqttSwitch(MqttAvailability, SwitchDevice):
"""Representation of a switch that can be toggled using MQTT."""
def __init__(self, name, state_topic, command_topic, availability_topic,
qos, retain, payload_on, payload_off, optimistic,
payload_available, payload_not_available, value_template):
"""Initialize the MQTT switch."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
self._state = False
self._name = name
self._state_topic = state_topic
self._command_topic = command_topic
self._availability_topic = availability_topic
self._available = True if availability_topic is None else False
self._qos = qos
self._retain = retain
self._payload_on = payload_on
self._payload_off = payload_off
self._optimistic = optimistic
self._template = value_template
self._payload_available = payload_available
self._payload_not_available = payload_not_available
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe to MQTT events.
"""Subscribe to MQTT events."""
yield from super().async_added_to_hass()
This method is a coroutine.
"""
@callback
def state_message_received(topic, payload, qos):
"""Handle new MQTT state messages."""
@ -113,16 +102,6 @@ class MqttSwitch(SwitchDevice):
self.async_schedule_update_ha_state()
@callback
def availability_message_received(topic, payload, qos):
"""Handle new MQTT availability messages."""
if payload == self._payload_available:
self._available = True
elif payload == self._payload_not_available:
self._available = False
self.async_schedule_update_ha_state()
if self._state_topic is None:
# Force into optimistic mode.
self._optimistic = True
@ -131,11 +110,6 @@ class MqttSwitch(SwitchDevice):
self.hass, self._state_topic, state_message_received,
self._qos)
if self._availability_topic is not None:
yield from mqtt.async_subscribe(
self.hass, self._availability_topic,
availability_message_received, self._qos)
@property
def should_poll(self):
"""Return the polling state."""
@ -146,11 +120,6 @@ class MqttSwitch(SwitchDevice):
"""Return the name of the switch."""
return self._name
@property
def available(self) -> bool:
"""Return if switch is available."""
return self._available
@property
def is_on(self):
"""Return true if device is on."""

View File

@ -11,6 +11,7 @@ import voluptuous as vol
import homeassistant.components.mqtt as mqtt
import homeassistant.helpers.config_validation as cv
from homeassistant.components.mqtt import MqttAvailability
from homeassistant.components.vacuum import (
DEFAULT_ICON, SUPPORT_BATTERY, SUPPORT_CLEAN_SPOT, SUPPORT_FAN_SPEED,
SUPPORT_LOCATE, SUPPORT_PAUSE, SUPPORT_RETURN_HOME, SUPPORT_SEND_COMMAND,
@ -135,7 +136,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FAN_SPEED_LIST, default=[]):
vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_SEND_COMMAND_TOPIC): mqtt.valid_publish_topic,
})
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@asyncio.coroutine
@ -187,6 +188,10 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
send_command_topic = config.get(CONF_SEND_COMMAND_TOPIC)
availability_topic = config.get(mqtt.CONF_AVAILABILITY_TOPIC)
payload_available = config.get(mqtt.CONF_PAYLOAD_AVAILABLE)
payload_not_available = config.get(mqtt.CONF_PAYLOAD_NOT_AVAILABLE)
async_add_devices([
MqttVacuum(
name, supported_features, qos, retain, command_topic,
@ -196,12 +201,13 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
charging_topic, charging_template, cleaning_topic,
cleaning_template, docked_topic, docked_template, fan_speed_topic,
fan_speed_template, set_fan_speed_topic, fan_speed_list,
send_command_topic
send_command_topic, availability_topic, payload_available,
payload_not_available
),
])
class MqttVacuum(VacuumDevice):
class MqttVacuum(MqttAvailability, VacuumDevice):
"""Representation of a MQTT-controlled vacuum."""
# pylint: disable=no-self-use
@ -213,8 +219,12 @@ class MqttVacuum(VacuumDevice):
charging_topic, charging_template, cleaning_topic,
cleaning_template, docked_topic, docked_template, fan_speed_topic,
fan_speed_template, set_fan_speed_topic, fan_speed_list,
send_command_topic):
send_command_topic, availability_topic, payload_available,
payload_not_available):
"""Initialize the vacuum."""
super().__init__(availability_topic, qos, payload_available,
payload_not_available)
self._name = name
self._supported_features = supported_features
self._qos = qos
@ -257,10 +267,9 @@ class MqttVacuum(VacuumDevice):
@asyncio.coroutine
def async_added_to_hass(self):
"""Subscribe MQTT events.
"""Subscribe MQTT events."""
yield from super().async_added_to_hass()
This method is a coroutine.
"""
@callback
def message_received(topic, payload, qos):
"""Handle new MQTT message."""

View File

@ -4,7 +4,8 @@ import unittest
from homeassistant.setup import setup_component
from homeassistant.const import (
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN)
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNAVAILABLE,
STATE_UNKNOWN)
from homeassistant.components import alarm_control_panel
from tests.common import (
@ -190,3 +191,33 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
alarm_control_panel.alarm_disarm(self.hass, 'abcd')
self.hass.block_till_done()
self.assertEqual(call_count, self.mock_publish.call_count)
def test_custom_availability_payload(self):
"""Test availability by custom payload with defined topic."""
assert setup_component(self.hass, alarm_control_panel.DOMAIN, {
alarm_control_panel.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'alarm/state',
'command_topic': 'alarm/command',
'code': '1234',
'availability_topic': 'availability-topic',
'payload_available': 'good',
'payload_not_available': 'nogood'
}
})
state = self.hass.states.get('alarm_control_panel.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'good')
self.hass.block_till_done()
state = self.hass.states.get('alarm_control_panel.test')
self.assertNotEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'nogood')
self.hass.block_till_done()
state = self.hass.states.get('alarm_control_panel.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)

View File

@ -7,7 +7,7 @@ from homeassistant.util.unit_system import (
)
from homeassistant.setup import setup_component
from homeassistant.components import climate
from homeassistant.const import STATE_OFF
from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE
from homeassistant.components.climate import (
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE, SUPPORT_HOLD_MODE,
@ -432,3 +432,27 @@ class TestMQTTClimate(unittest.TestCase):
self.mock_publish.mock_calls[-2][1])
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('off', state.attributes.get('aux_heat'))
def test_custom_availability_payload(self):
"""Test availability by custom payload with defined topic."""
config = copy.deepcopy(DEFAULT_CONFIG)
config['climate']['availability_topic'] = 'availability-topic'
config['climate']['payload_available'] = 'good'
config['climate']['payload_not_available'] = 'nogood'
assert setup_component(self.hass, climate.DOMAIN, config)
state = self.hass.states.get('climate.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'good')
self.hass.block_till_done()
state = self.hass.states.get('climate.test')
self.assertNotEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'nogood')
self.hass.block_till_done()
state = self.hass.states.get('climate.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)

View File

@ -0,0 +1,64 @@
"""Test MQTT fans."""
import unittest
from homeassistant.setup import setup_component
from homeassistant.components import fan
from homeassistant.const import ATTR_ASSUMED_STATE, STATE_UNAVAILABLE
from tests.common import (
mock_mqtt_component, fire_mqtt_message, get_test_home_assistant)
class TestMqttFan(unittest.TestCase):
"""Test the MQTT fan platform."""
def setUp(self): # pylint: disable=invalid-name
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.mock_publish = mock_mqtt_component(self.hass)
def tearDown(self): # pylint: disable=invalid-name
""""Stop everything that was started."""
self.hass.stop()
def test_custom_availability_payload(self):
"""Test the availability payload."""
assert setup_component(self.hass, fan.DOMAIN, {
fan.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'availability_topic': 'availability_topic',
'payload_available': 'good',
'payload_not_available': 'nogood'
}
})
state = self.hass.states.get('fan.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability_topic', 'good')
self.hass.block_till_done()
state = self.hass.states.get('fan.test')
self.assertNotEqual(STATE_UNAVAILABLE, state.state)
self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE))
fire_mqtt_message(self.hass, 'availability_topic', 'nogood')
self.hass.block_till_done()
state = self.hass.states.get('fan.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'state-topic', '1')
self.hass.block_till_done()
state = self.hass.states.get('fan.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability_topic', 'good')
self.hass.block_till_done()
state = self.hass.states.get('fan.test')
self.assertNotEqual(STATE_UNAVAILABLE, state.state)

View File

@ -142,7 +142,8 @@ import unittest
from unittest import mock
from homeassistant.setup import setup_component
from homeassistant.const import STATE_ON, STATE_OFF, ATTR_ASSUMED_STATE
from homeassistant.const import (
STATE_ON, STATE_OFF, STATE_UNAVAILABLE, ATTR_ASSUMED_STATE)
import homeassistant.components.light as light
from tests.common import (
assert_setup_component, get_test_home_assistant, mock_mqtt_component,
@ -794,3 +795,33 @@ class TestLightMQTT(unittest.TestCase):
self.mock_publish.mock_calls[-4][1])
self.assertEqual(('test_light/bright', 50, 0, False),
self.mock_publish.mock_calls[-2][1])
def test_custom_availability_payload(self):
"""Test availability by custom payload with defined topic."""
self.assertTrue(setup_component(self.hass, light.DOMAIN, {
light.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'command_topic': 'test_light/set',
'brightness_command_topic': 'test_light/bright',
'rgb_command_topic': "test_light/rgb",
'availability_topic': 'availability-topic',
'payload_available': 'good',
'payload_not_available': 'nogood'
}
}))
state = self.hass.states.get('light.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'good')
self.hass.block_till_done()
state = self.hass.states.get('light.test')
self.assertNotEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'nogood')
self.hass.block_till_done()
state = self.hass.states.get('light.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)

View File

@ -82,7 +82,8 @@ import unittest
from homeassistant.setup import setup_component
from homeassistant.const import (
STATE_ON, STATE_OFF, ATTR_ASSUMED_STATE, ATTR_SUPPORTED_FEATURES)
STATE_ON, STATE_OFF, STATE_UNAVAILABLE, ATTR_ASSUMED_STATE,
ATTR_SUPPORTED_FEATURES)
import homeassistant.components.light as light
from tests.common import (
get_test_home_assistant, mock_mqtt_component, fire_mqtt_message,
@ -472,3 +473,32 @@ class TestLightMQTTJSON(unittest.TestCase):
state = self.hass.states.get('light.test')
self.assertEqual(STATE_ON, state.state)
self.assertEqual(255, state.attributes.get('white_value'))
def test_custom_availability_payload(self):
"""Test availability by custom payload with defined topic."""
self.assertTrue(setup_component(self.hass, light.DOMAIN, {
light.DOMAIN: {
'platform': 'mqtt_json',
'name': 'test',
'state_topic': 'test_light_rgb',
'command_topic': 'test_light_rgb/set',
'availability_topic': 'availability-topic',
'payload_available': 'good',
'payload_not_available': 'nogood'
}
}))
state = self.hass.states.get('light.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'good')
self.hass.block_till_done()
state = self.hass.states.get('light.test')
self.assertNotEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'nogood')
self.hass.block_till_done()
state = self.hass.states.get('light.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)

View File

@ -29,7 +29,8 @@ If your light doesn't support RGB feature, omit `(red|green|blue)_template`.
import unittest
from homeassistant.setup import setup_component
from homeassistant.const import STATE_ON, STATE_OFF, ATTR_ASSUMED_STATE
from homeassistant.const import (
STATE_ON, STATE_OFF, STATE_UNAVAILABLE, ATTR_ASSUMED_STATE)
import homeassistant.components.light as light
from tests.common import (
get_test_home_assistant, mock_mqtt_component, fire_mqtt_message,
@ -463,3 +464,33 @@ class TestLightMQTTTemplate(unittest.TestCase):
# effect should not have changed
state = self.hass.states.get('light.test')
self.assertEqual('rainbow', state.attributes.get('effect'))
def test_custom_availability_payload(self):
"""Test availability by custom payload with defined topic."""
self.assertTrue(setup_component(self.hass, light.DOMAIN, {
light.DOMAIN: {
'platform': 'mqtt_template',
'name': 'test',
'command_topic': 'test_light_rgb/set',
'command_on_template': 'on,{{ transition }}',
'command_off_template': 'off,{{ transition|d }}',
'availability_topic': 'availability-topic',
'payload_available': 'good',
'payload_not_available': 'nogood'
}
}))
state = self.hass.states.get('light.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'good')
self.hass.block_till_done()
state = self.hass.states.get('light.test')
self.assertNotEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'nogood')
self.hass.block_till_done()
state = self.hass.states.get('light.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)

View File

@ -2,8 +2,8 @@
import unittest
from homeassistant.setup import setup_component
from homeassistant.const import (STATE_LOCKED, STATE_UNLOCKED,
ATTR_ASSUMED_STATE)
from homeassistant.const import (
STATE_LOCKED, STATE_UNLOCKED, STATE_UNAVAILABLE, ATTR_ASSUMED_STATE)
import homeassistant.components.lock as lock
from tests.common import (
mock_mqtt_component, fire_mqtt_message, get_test_home_assistant)
@ -111,3 +111,34 @@ class TestLockMQTT(unittest.TestCase):
state = self.hass.states.get('lock.test')
self.assertEqual(STATE_UNLOCKED, state.state)
def test_custom_availability_payload(self):
"""Test availability by custom payload with defined topic."""
self.assertTrue(setup_component(self.hass, lock.DOMAIN, {
lock.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'state-topic',
'command_topic': 'command-topic',
'payload_lock': 'LOCK',
'payload_unlock': 'UNLOCK',
'availability_topic': 'availability-topic',
'payload_available': 'good',
'payload_not_available': 'nogood'
}
}))
state = self.hass.states.get('lock.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'good')
self.hass.block_till_done()
state = self.hass.states.get('lock.test')
self.assertNotEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'nogood')
self.hass.block_till_done()
state = self.hass.states.get('lock.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)

View File

@ -7,7 +7,7 @@ from unittest.mock import patch
import homeassistant.core as ha
from homeassistant.setup import setup_component
import homeassistant.components.sensor as sensor
from homeassistant.const import EVENT_STATE_CHANGED
from homeassistant.const import EVENT_STATE_CHANGED, STATE_UNAVAILABLE
import homeassistant.util.dt as dt_util
from tests.common import mock_mqtt_component, fire_mqtt_message
@ -185,6 +185,34 @@ class TestSensorMQTT(unittest.TestCase):
self.hass.block_till_done()
self.assertEqual(2, len(events))
def test_custom_availability_payload(self):
"""Test availability by custom payload with defined topic."""
self.assertTrue(setup_component(self.hass, sensor.DOMAIN, {
sensor.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'test-topic',
'availability_topic': 'availability-topic',
'payload_available': 'good',
'payload_not_available': 'nogood'
}
}))
state = self.hass.states.get('sensor.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'good')
self.hass.block_till_done()
state = self.hass.states.get('sensor.test')
self.assertNotEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'nogood')
self.hass.block_till_done()
state = self.hass.states.get('sensor.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
def _send_time_changed(self, now):
"""Send a time changed event."""
self.hass.bus.fire(ha.EVENT_TIME_CHANGED, {ha.ATTR_NOW: now})

View File

@ -167,22 +167,22 @@ class TestSwitchMQTT(unittest.TestCase):
'availability_topic': 'availability_topic',
'payload_on': 1,
'payload_off': 0,
'payload_available': 'online',
'payload_not_available': 'offline'
'payload_available': 'good',
'payload_not_available': 'nogood'
}
})
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability_topic', 'online')
fire_mqtt_message(self.hass, 'availability_topic', 'good')
self.hass.block_till_done()
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_OFF, state.state)
self.assertFalse(state.attributes.get(ATTR_ASSUMED_STATE))
fire_mqtt_message(self.hass, 'availability_topic', 'offline')
fire_mqtt_message(self.hass, 'availability_topic', 'nogood')
self.hass.block_till_done()
state = self.hass.states.get('switch.test')
@ -194,7 +194,7 @@ class TestSwitchMQTT(unittest.TestCase):
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability_topic', 'online')
fire_mqtt_message(self.hass, 'availability_topic', 'good')
self.hass.block_till_done()
state = self.hass.states.get('switch.test')

View File

@ -6,7 +6,8 @@ from homeassistant.components.vacuum import (
ATTR_BATTERY_LEVEL, ATTR_BATTERY_ICON, ATTR_STATUS,
ATTR_FAN_SPEED, mqtt)
from homeassistant.components.mqtt import CONF_COMMAND_TOPIC
from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON, CONF_NAME
from homeassistant.const import (
CONF_PLATFORM, STATE_OFF, STATE_ON, STATE_UNAVAILABLE, CONF_NAME)
from homeassistant.setup import setup_component
from tests.common import (
fire_mqtt_message, get_test_home_assistant, mock_mqtt_component)
@ -197,3 +198,30 @@ class TestVacuumMQTT(unittest.TestCase):
state = self.hass.states.get('vacuum.mqtttest')
self.assertEqual(STATE_OFF, state.state)
self.assertEqual("Stopped", state.attributes.get(ATTR_STATUS))
def test_custom_availability_payload(self):
"""Test availability by custom payload with defined topic."""
self.default_config.update({
'availability_topic': 'availability-topic',
'payload_available': 'good',
'payload_not_available': 'nogood'
})
self.assertTrue(setup_component(self.hass, vacuum.DOMAIN, {
vacuum.DOMAIN: self.default_config,
}))
state = self.hass.states.get('vacuum.mqtttest')
self.assertEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'good')
self.hass.block_till_done()
state = self.hass.states.get('vacuum.mqtttest')
self.assertNotEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'nogood')
self.hass.block_till_done()
state = self.hass.states.get('vacuum.mqtttest')
self.assertEqual(STATE_UNAVAILABLE, state.state)