diff --git a/homeassistant/components/sensor/mqtt.py b/homeassistant/components/sensor/mqtt.py index d191b9a22e8..c4f64e9e015 100644 --- a/homeassistant/components/sensor/mqtt.py +++ b/homeassistant/components/sensor/mqtt.py @@ -8,6 +8,7 @@ import asyncio import logging import json from datetime import timedelta +from typing import Optional import voluptuous as vol @@ -28,6 +29,7 @@ _LOGGER = logging.getLogger(__name__) CONF_EXPIRE_AFTER = 'expire_after' CONF_JSON_ATTRS = 'json_attributes' +CONF_UNIQUE_ID = 'unique_id' DEFAULT_NAME = 'MQTT Sensor' DEFAULT_FORCE_UPDATE = False @@ -40,6 +42,9 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({ vol.Optional(CONF_JSON_ATTRS, default=[]): cv.ensure_list_csv, vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, + # Integrations shouldn't never expose unique_id through configuration + # this here is an exception because MQTT is a msg transport, not a protocol + vol.Optional(CONF_UNIQUE_ID): cv.string, }).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) @@ -63,6 +68,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None): config.get(CONF_ICON), value_template, config.get(CONF_JSON_ATTRS), + config.get(CONF_UNIQUE_ID), config.get(CONF_AVAILABILITY_TOPIC), config.get(CONF_PAYLOAD_AVAILABLE), config.get(CONF_PAYLOAD_NOT_AVAILABLE), @@ -74,7 +80,8 @@ class MqttSensor(MqttAvailability, Entity): def __init__(self, name, state_topic, qos, unit_of_measurement, force_update, expire_after, icon, value_template, - json_attributes, availability_topic, payload_available, + json_attributes, unique_id: Optional[str], + availability_topic, payload_available, payload_not_available): """Initialize the sensor.""" super().__init__(availability_topic, qos, payload_available, @@ -90,6 +97,7 @@ class MqttSensor(MqttAvailability, Entity): self._icon = icon self._expiration_trigger = None self._json_attributes = set(json_attributes) + self._unique_id = unique_id self._attributes = None @asyncio.coroutine @@ -174,6 +182,11 @@ class MqttSensor(MqttAvailability, Entity): """Return the state attributes.""" return self._attributes + @property + def unique_id(self): + """Return a unique ID.""" + return self._unique_id + @property def icon(self): """Return the icon.""" diff --git a/tests/components/sensor/test_mqtt.py b/tests/components/sensor/test_mqtt.py index b23d89e3057..88e74e11008 100644 --- a/tests/components/sensor/test_mqtt.py +++ b/tests/components/sensor/test_mqtt.py @@ -329,3 +329,24 @@ class TestSensorMQTT(unittest.TestCase): self.assertEqual('100', state.attributes.get('val')) self.assertEqual('100', state.state) + + def test_unique_id(self): + """Test unique id option only creates one sensor per unique_id.""" + assert setup_component(self.hass, sensor.DOMAIN, { + sensor.DOMAIN: [{ + 'platform': 'mqtt', + 'name': 'Test 1', + 'state_topic': 'test-topic', + 'unique_id': 'TOTALLY_UNIQUE' + }, { + 'platform': 'mqtt', + 'name': 'Test 2', + 'state_topic': 'test-topic', + 'unique_id': 'TOTALLY_UNIQUE' + }] + }) + + fire_mqtt_message(self.hass, 'test-topic', 'payload') + self.hass.block_till_done() + + assert len(self.hass.states.all()) == 1