Skip to content
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

More support for availability reporting on MQTT components #11336

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
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
  • Loading branch information
DanNixon committed Dec 28, 2017
commit a29388e9987931934bf526715649340a05480eeb
45 changes: 8 additions & 37 deletions homeassistant/components/binary_sensor/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,15 @@
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']

Expand All @@ -38,12 +34,7 @@
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
Expand All @@ -70,23 +61,21 @@ 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

Expand All @@ -95,6 +84,8 @@ def async_added_to_hass(self):

This method must be run in the event loop and returns a coroutine.
"""
yield from super().async_added_to_hass()

@callback
def state_message_received(topic, payload, qos):
"""Handle a new received MQTT state message."""
Expand All @@ -111,21 +102,6 @@ def state_message_received(topic, payload, qos):
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."""
Expand All @@ -136,11 +112,6 @@ def name(self):
"""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."""
Expand Down
31 changes: 9 additions & 22 deletions homeassistant/components/cover/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
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__)
Expand All @@ -37,8 +38,6 @@
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'
Expand All @@ -52,8 +51,6 @@
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
Expand All @@ -73,16 +70,11 @@
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,
Expand All @@ -98,7 +90,7 @@
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
Expand Down Expand Up @@ -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,
Expand All @@ -154,21 +146,19 @@ def __init__(self, name, state_topic, command_topic, availability_topic,
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
Expand All @@ -190,6 +180,8 @@ def async_added_to_hass(self):

This method is a coroutine.
"""
yield from super().async_added_to_hass()

@callback
def tilt_updated(topic, payload, qos):
"""Handle tilt updates."""
Expand Down Expand Up @@ -266,11 +258,6 @@ def name(self):
"""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."""
Expand Down
51 changes: 51 additions & 0 deletions homeassistant/components/mqtt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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 (
Expand Down Expand Up @@ -59,6 +60,8 @@
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'

Expand All @@ -73,6 +76,8 @@
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'
Expand Down Expand Up @@ -145,6 +150,14 @@ def valid_discovery_topic(value):
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
Expand Down Expand Up @@ -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
Loading