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
13 changes: 9 additions & 4 deletions homeassistant/components/light/mqtt/schema_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
ATTR_WHITE_VALUE, Light, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP,
SUPPORT_EFFECT, SUPPORT_COLOR, SUPPORT_WHITE_VALUE)
from homeassistant.const import (
CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_EFFECT, CONF_HS, CONF_NAME,
CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, STATE_ON,
CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_DEVICE, CONF_EFFECT, CONF_HS,
CONF_NAME, CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, STATE_ON,
CONF_RGB, CONF_STATE, CONF_VALUE_TEMPLATE, CONF_WHITE_VALUE, CONF_XY)
from homeassistant.components.mqtt import (
CONF_AVAILABILITY_TOPIC, CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE,
CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC,
MqttAvailability, MqttDiscoveryUpdate, subscription)
MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription)
from homeassistant.helpers.restore_state import RestoreEntity
import homeassistant.helpers.config_validation as cv
import homeassistant.util.color as color_util
Expand Down Expand Up @@ -105,6 +105,7 @@
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),
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)


Expand All @@ -117,7 +118,9 @@ async def async_setup_entity_basic(hass, config, async_add_entities,
async_add_entities([MqttLight(config, discovery_hash)])


class MqttLight(MqttAvailability, MqttDiscoveryUpdate, Light, RestoreEntity):
# pylint: disable=too-many-ancestors
class MqttLight(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
Light, RestoreEntity):
"""Representation of a MQTT light."""

def __init__(self, config, discovery_hash):
Expand Down Expand Up @@ -151,11 +154,13 @@ def __init__(self, config, discovery_hash):
payload_available = config.get(CONF_PAYLOAD_AVAILABLE)
payload_not_available = config.get(CONF_PAYLOAD_NOT_AVAILABLE)
qos = config.get(CONF_QOS)
device_config = config.get(CONF_DEVICE)

MqttAvailability.__init__(self, availability_topic, qos,
payload_available, payload_not_available)
MqttDiscoveryUpdate.__init__(self, discovery_hash,
self.discovery_update)
MqttEntityDeviceInfo.__init__(self, device_config)

async def async_added_to_hass(self):
"""Subscribe to MQTT events."""
Expand Down
14 changes: 9 additions & 5 deletions homeassistant/components/light/mqtt/schema_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
from homeassistant.components.mqtt import (
CONF_AVAILABILITY_TOPIC, CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE,
CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC,
MqttAvailability, MqttDiscoveryUpdate, subscription)
MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription)
from homeassistant.const import (
CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_EFFECT, CONF_NAME, CONF_OPTIMISTIC,
CONF_RGB, CONF_WHITE_VALUE, CONF_XY, STATE_ON)
CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_DEVICE, CONF_EFFECT, CONF_NAME,
CONF_OPTIMISTIC, CONF_RGB, CONF_WHITE_VALUE, CONF_XY, STATE_ON)
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.restore_state import RestoreEntity
Expand Down Expand Up @@ -80,6 +80,7 @@
vol.Optional(CONF_XY, default=DEFAULT_XY): cv.boolean,
vol.Optional(CONF_HS, default=DEFAULT_HS): cv.boolean,
vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)


Expand All @@ -89,8 +90,9 @@ async def async_setup_entity_json(hass: HomeAssistantType, config: ConfigType,
async_add_entities([MqttLightJson(config, discovery_hash)])


class MqttLightJson(MqttAvailability, MqttDiscoveryUpdate, Light,
RestoreEntity):
# pylint: disable=too-many-ancestors
class MqttLightJson(MqttAvailability, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, Light, RestoreEntity):
"""Representation of a MQTT JSON light."""

def __init__(self, config, discovery_hash):
Expand All @@ -116,11 +118,13 @@ def __init__(self, config, discovery_hash):
payload_available = config.get(CONF_PAYLOAD_AVAILABLE)
payload_not_available = config.get(CONF_PAYLOAD_NOT_AVAILABLE)
qos = config.get(CONF_QOS)
device_config = config.get(CONF_DEVICE)

MqttAvailability.__init__(self, availability_topic, qos,
payload_available, payload_not_available)
MqttDiscoveryUpdate.__init__(self, discovery_hash,
self.discovery_update)
MqttEntityDeviceInfo.__init__(self, device_config)

async def async_added_to_hass(self):
"""Subscribe to MQTT events."""
Expand Down
21 changes: 17 additions & 4 deletions homeassistant/components/light/mqtt/schema_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
ATTR_HS_COLOR, ATTR_TRANSITION, ATTR_WHITE_VALUE, Light,
SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_FLASH,
SUPPORT_COLOR, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE)
from homeassistant.const import CONF_NAME, CONF_OPTIMISTIC, STATE_ON, STATE_OFF
from homeassistant.const import (
CONF_DEVICE, CONF_NAME, CONF_OPTIMISTIC, STATE_ON, STATE_OFF)
from homeassistant.components.mqtt import (
CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN,
MqttAvailability, MqttDiscoveryUpdate, subscription)
MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo, subscription)
import homeassistant.helpers.config_validation as cv
import homeassistant.util.color as color_util
from homeassistant.helpers.restore_state import RestoreEntity
Expand All @@ -43,6 +44,7 @@
CONF_RED_TEMPLATE = 'red_template'
CONF_STATE_TEMPLATE = 'state_template'
CONF_WHITE_VALUE_TEMPLATE = 'white_value_template'
CONF_UNIQUE_ID = 'unique_id'

PLATFORM_SCHEMA_TEMPLATE = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_BLUE_TEMPLATE): cv.template,
Expand All @@ -63,6 +65,8 @@
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])),
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)


Expand All @@ -72,8 +76,9 @@ async def async_setup_entity_template(hass, config, async_add_entities,
async_add_entities([MqttTemplate(config, discovery_hash)])


class MqttTemplate(MqttAvailability, MqttDiscoveryUpdate, Light,
RestoreEntity):
# pylint: disable=too-many-ancestors
class MqttTemplate(MqttAvailability, MqttDiscoveryUpdate, MqttEntityDeviceInfo,
Light, RestoreEntity):
"""Representation of a MQTT Template light."""

def __init__(self, config, discovery_hash):
Expand All @@ -91,6 +96,7 @@ def __init__(self, config, discovery_hash):
self._white_value = None
self._hs = None
self._effect = None
self._unique_id = config.get(CONF_UNIQUE_ID)

# Load config
self._setup_from_config(config)
Expand All @@ -99,11 +105,13 @@ def __init__(self, config, discovery_hash):
payload_available = config.get(CONF_PAYLOAD_AVAILABLE)
payload_not_available = config.get(CONF_PAYLOAD_NOT_AVAILABLE)
qos = config.get(CONF_QOS)
device_config = config.get(CONF_DEVICE)

MqttAvailability.__init__(self, availability_topic, qos,
payload_available, payload_not_available)
MqttDiscoveryUpdate.__init__(self, discovery_hash,
self.discovery_update)
MqttEntityDeviceInfo.__init__(self, device_config)

async def async_added_to_hass(self):
"""Subscribe to MQTT events."""
Expand Down Expand Up @@ -302,6 +310,11 @@ def name(self):
"""Return the name of the entity."""
return self._config.get(CONF_NAME)

@property
def unique_id(self):
"""Return a unique ID."""
return self._unique_id

@property
def is_on(self):
"""Return True if entity is on."""
Expand Down
65 changes: 64 additions & 1 deletion tests/components/light/test_mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@
payload_off: "off"

"""
import json
from unittest import mock
from unittest.mock import patch

Expand All @@ -165,7 +166,7 @@

from tests.common import (
assert_setup_component, async_fire_mqtt_message,
mock_coro, MockConfigEntry)
async_mock_mqtt_component, mock_coro, MockConfigEntry)
from tests.components.light import common


Expand Down Expand Up @@ -1038,6 +1039,29 @@ async def test_custom_availability_payload(hass, mqtt_mock):
assert STATE_UNAVAILABLE == state.state


async def test_unique_id(hass):
"""Test unique id option only creates one light per unique_id."""
await async_mock_mqtt_component(hass)
assert await async_setup_component(hass, light.DOMAIN, {
light.DOMAIN: [{
'platform': 'mqtt',
'name': 'Test 1',
'status_topic': 'test-topic',
'command_topic': 'test_topic',
'unique_id': 'TOTALLY_UNIQUE'
}, {
'platform': 'mqtt',
'name': 'Test 2',
'status_topic': 'test-topic',
'command_topic': 'test_topic',
'unique_id': 'TOTALLY_UNIQUE'
}]
})
async_fire_mqtt_message(hass, 'test-topic', 'payload')
await hass.async_block_till_done()
assert len(hass.states.async_entity_ids(light.DOMAIN)) == 1


async def test_discovery_removal_light(hass, mqtt_mock, caplog):
"""Test removal of discovered light."""
entry = MockConfigEntry(domain=mqtt.DOMAIN)
Expand Down Expand Up @@ -1117,3 +1141,42 @@ async def test_discovery_update_light(hass, mqtt_mock, caplog):
assert state.name == 'Milk'
state = hass.states.get('light.milk')
assert state is None


async def test_entity_device_info_with_identifier(hass, mqtt_mock):
"""Test MQTT light device registry integration."""
entry = MockConfigEntry(domain=mqtt.DOMAIN)
entry.add_to_hass(hass)
await async_start(hass, 'homeassistant', {}, entry)
registry = await hass.helpers.device_registry.async_get_registry()

data = json.dumps({
'platform': 'mqtt',
'name': 'Test 1',
'state_topic': 'test-topic',
'command_topic': 'test-topic',
'device': {
'identifiers': ['helloworld'],
'connections': [
["mac", "02:5b:26:a8:dc:12"],
],
'manufacturer': 'Whatever',
'name': 'Beer',
'model': 'Glass',
'sw_version': '0.1-beta',
},
'unique_id': 'veryunique'
})
async_fire_mqtt_message(hass, 'homeassistant/light/bla/config',
data)
await hass.async_block_till_done()
await hass.async_block_till_done()

device = registry.async_get_device({('mqtt', 'helloworld')}, set())
assert device is not None
assert device.identifiers == {('mqtt', 'helloworld')}
assert device.connections == {('mac', "02:5b:26:a8:dc:12")}
assert device.manufacturer == 'Whatever'
assert device.name == 'Beer'
assert device.model == 'Glass'
assert device.sw_version == '0.1-beta'
70 changes: 69 additions & 1 deletion tests/components/light/test_mqtt_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
brightness: true
brightness_scale: 99
"""
import json
from unittest.mock import patch

from homeassistant.setup import async_setup_component
Expand All @@ -97,7 +98,9 @@
from homeassistant.components.mqtt.discovery import async_start
import homeassistant.core as ha

from tests.common import mock_coro, async_fire_mqtt_message, MockConfigEntry
from tests.common import (
mock_coro, async_fire_mqtt_message, async_mock_mqtt_component,
MockConfigEntry)


async def test_fail_setup_if_no_command_topic(hass, mqtt_mock):
Expand Down Expand Up @@ -533,6 +536,31 @@ async def test_custom_availability_payload(hass, mqtt_mock):
assert STATE_UNAVAILABLE == state.state


async def test_unique_id(hass):
"""Test unique id option only creates one light per unique_id."""
await async_mock_mqtt_component(hass)
assert await async_setup_component(hass, light.DOMAIN, {
light.DOMAIN: [{
'platform': 'mqtt',
'name': 'Test 1',
'schema': 'json',
'status_topic': 'test-topic',
'command_topic': 'test_topic',
'unique_id': 'TOTALLY_UNIQUE'
}, {
'platform': 'mqtt',
'name': 'Test 2',
'schema': 'json',
'status_topic': 'test-topic',
'command_topic': 'test_topic',
'unique_id': 'TOTALLY_UNIQUE'
}]
})
async_fire_mqtt_message(hass, 'test-topic', 'payload')
await hass.async_block_till_done()
assert len(hass.states.async_entity_ids(light.DOMAIN)) == 1


async def test_discovery_removal(hass, mqtt_mock, caplog):
"""Test removal of discovered mqtt_json lights."""
entry = MockConfigEntry(domain=mqtt.DOMAIN)
Expand Down Expand Up @@ -609,3 +637,43 @@ async def test_discovery_update_light(hass, mqtt_mock, caplog):
assert state.name == 'Milk'
state = hass.states.get('light.milk')
assert state is None


async def test_entity_device_info_with_identifier(hass, mqtt_mock):
"""Test MQTT light device registry integration."""
entry = MockConfigEntry(domain=mqtt.DOMAIN)
entry.add_to_hass(hass)
await async_start(hass, 'homeassistant', {}, entry)
registry = await hass.helpers.device_registry.async_get_registry()

data = json.dumps({
'platform': 'mqtt',
'name': 'Test 1',
'schema': 'json',
'state_topic': 'test-topic',
'command_topic': 'test-topic',
'device': {
'identifiers': ['helloworld'],
'connections': [
["mac", "02:5b:26:a8:dc:12"],
],
'manufacturer': 'Whatever',
'name': 'Beer',
'model': 'Glass',
'sw_version': '0.1-beta',
},
'unique_id': 'veryunique'
})
async_fire_mqtt_message(hass, 'homeassistant/light/bla/config',
data)
await hass.async_block_till_done()
await hass.async_block_till_done()

device = registry.async_get_device({('mqtt', 'helloworld')}, set())
assert device is not None
assert device.identifiers == {('mqtt', 'helloworld')}
assert device.connections == {('mac', "02:5b:26:a8:dc:12")}
assert device.manufacturer == 'Whatever'
assert device.name == 'Beer'
assert device.model == 'Glass'
assert device.sw_version == '0.1-beta'
Loading