diff --git a/homeassistant/components/influxdb.py b/homeassistant/components/influxdb.py index 6b8cd6734dd..36a58fa8165 100644 --- a/homeassistant/components/influxdb.py +++ b/homeassistant/components/influxdb.py @@ -15,6 +15,7 @@ from homeassistant.const import ( CONF_PORT, CONF_SSL, CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD, CONF_EXCLUDE, CONF_INCLUDE, CONF_DOMAINS, CONF_ENTITIES) from homeassistant.helpers import state as state_helper +from homeassistant.helpers.entity_values import EntityValues import homeassistant.helpers.config_validation as cv REQUIREMENTS = ['influxdb==3.0.0'] @@ -25,13 +26,20 @@ CONF_DB_NAME = 'database' CONF_TAGS = 'tags' CONF_DEFAULT_MEASUREMENT = 'default_measurement' CONF_OVERRIDE_MEASUREMENT = 'override_measurement' -CONF_BLACKLIST_DOMAINS = "blacklist_domains" +CONF_TAGS_ATTRIBUTES = 'tags_attributes' +CONF_COMPONENT_CONFIG = 'component_config' +CONF_COMPONENT_CONFIG_GLOB = 'component_config_glob' +CONF_COMPONENT_CONFIG_DOMAIN = 'component_config_domain' DEFAULT_DATABASE = 'home_assistant' DEFAULT_VERIFY_SSL = True DOMAIN = 'influxdb' TIMEOUT = 5 +COMPONENT_CONFIG_SCHEMA_ENTRY = vol.Schema({ + vol.Optional(CONF_OVERRIDE_MEASUREMENT): cv.string, +}) + CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.Schema({ vol.Optional(CONF_HOST): cv.string, @@ -54,7 +62,15 @@ CONFIG_SCHEMA = vol.Schema({ vol.Optional(CONF_OVERRIDE_MEASUREMENT): cv.string, vol.Optional(CONF_TAGS, default={}): vol.Schema({cv.string: cv.string}), + vol.Optional(CONF_TAGS_ATTRIBUTES, default=[]): + vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, + vol.Optional(CONF_COMPONENT_CONFIG, default={}): + vol.Schema({cv.entity_id: COMPONENT_CONFIG_SCHEMA_ENTRY}), + vol.Optional(CONF_COMPONENT_CONFIG_GLOB, default={}): + vol.Schema({cv.string: COMPONENT_CONFIG_SCHEMA_ENTRY}), + vol.Optional(CONF_COMPONENT_CONFIG_DOMAIN, default={}): + vol.Schema({cv.string: COMPONENT_CONFIG_SCHEMA_ENTRY}), }), }, extra=vol.ALLOW_EXTRA) @@ -96,8 +112,13 @@ def setup(hass, config): blacklist_e = set(exclude.get(CONF_ENTITIES, [])) blacklist_d = set(exclude.get(CONF_DOMAINS, [])) tags = conf.get(CONF_TAGS) + tags_attributes = conf.get(CONF_TAGS_ATTRIBUTES) default_measurement = conf.get(CONF_DEFAULT_MEASUREMENT) override_measurement = conf.get(CONF_OVERRIDE_MEASUREMENT) + component_config = EntityValues( + conf[CONF_COMPONENT_CONFIG], + conf[CONF_COMPONENT_CONFIG_DOMAIN], + conf[CONF_COMPONENT_CONFIG_GLOB]) try: influx = InfluxDBClient(**kwargs) @@ -128,15 +149,18 @@ def setup(hass, config): _state = state.state _state_key = "state" - if override_measurement: - measurement = override_measurement - else: - measurement = state.attributes.get('unit_of_measurement') - if measurement in (None, ''): - if default_measurement: - measurement = default_measurement - else: - measurement = state.entity_id + measurement = component_config.get(state.entity_id).get( + CONF_OVERRIDE_MEASUREMENT) + if measurement in (None, ''): + if override_measurement: + measurement = override_measurement + else: + measurement = state.attributes.get('unit_of_measurement') + if measurement in (None, ''): + if default_measurement: + measurement = default_measurement + else: + measurement = state.entity_id json_body = [ { @@ -153,7 +177,9 @@ def setup(hass, config): ] for key, value in state.attributes.items(): - if key != 'unit_of_measurement': + if key in tags_attributes: + json_body[0]['tags'][key] = value + elif key != 'unit_of_measurement': # If the key is already in fields if key in json_body[0]['fields']: key = key + "_" diff --git a/tests/components/test_influxdb.py b/tests/components/test_influxdb.py index 896ee4699cc..f117b62fddb 100644 --- a/tests/components/test_influxdb.py +++ b/tests/components/test_influxdb.py @@ -129,7 +129,8 @@ class TestInfluxDB(unittest.TestCase): 'multi_periods': '0.120.240.2023873' } state = mock.MagicMock( - state=in_, domain='fake', object_id='entity', attributes=attrs) + state=in_, domain='fake', entity_id='fake.entity-id', + object_id='entity', attributes=attrs) event = mock.MagicMock(data={'new_state': state}, time_fired=12345) if isinstance(out, str): body = [{ @@ -198,11 +199,11 @@ class TestInfluxDB(unittest.TestCase): else: attrs = {} state = mock.MagicMock( - state=1, domain='fake', entity_id='entity-id', + state=1, domain='fake', entity_id='fake.entity-id', object_id='entity', attributes=attrs) event = mock.MagicMock(data={'new_state': state}, time_fired=12345) body = [{ - 'measurement': 'entity-id', + 'measurement': 'fake.entity-id', 'tags': { 'domain': 'fake', 'entity_id': 'entity', @@ -227,8 +228,8 @@ class TestInfluxDB(unittest.TestCase): self._setup() state = mock.MagicMock( - state=1, domain='fake', entity_id='entity-id', object_id='entity', - attributes={}) + state=1, domain='fake', entity_id='fake.entity-id', + object_id='entity', attributes={}) event = mock.MagicMock(data={'new_state': state}, time_fired=12345) mock_client.return_value.write_points.side_effect = \ influx_client.exceptions.InfluxDBClientError('foo') @@ -240,11 +241,11 @@ class TestInfluxDB(unittest.TestCase): for state_state in (1, 'unknown', '', 'unavailable'): state = mock.MagicMock( - state=state_state, domain='fake', entity_id='entity-id', + state=state_state, domain='fake', entity_id='fake.entity-id', object_id='entity', attributes={}) event = mock.MagicMock(data={'new_state': state}, time_fired=12345) body = [{ - 'measurement': 'entity-id', + 'measurement': 'fake.entity-id', 'tags': { 'domain': 'fake', 'entity_id': 'entity', @@ -424,7 +425,7 @@ class TestInfluxDB(unittest.TestCase): mock_client.return_value.write_points.reset_mock() def test_event_listener_invalid_type(self, mock_client): - """Test the event listener when an attirbute has an invalid type.""" + """Test the event listener when an attribute has an invalid type.""" self._setup() valid = { @@ -442,7 +443,8 @@ class TestInfluxDB(unittest.TestCase): 'invalid_attribute': ['value1', 'value2'] } state = mock.MagicMock( - state=in_, domain='fake', object_id='entity', attributes=attrs) + state=in_, domain='fake', entity_id='fake.entity-id', + object_id='entity', attributes=attrs) event = mock.MagicMock(data={'new_state': state}, time_fired=12345) if isinstance(out, str): body = [{ @@ -529,3 +531,108 @@ class TestInfluxDB(unittest.TestCase): else: self.assertFalse(mock_client.return_value.write_points.called) mock_client.return_value.write_points.reset_mock() + + def test_event_listener_tags_attributes(self, mock_client): + """Test the event listener when some attributes should be tags.""" + config = { + 'influxdb': { + 'host': 'host', + 'username': 'user', + 'password': 'pass', + 'tags_attributes': ['friendly_fake'] + } + } + assert setup_component(self.hass, influxdb.DOMAIN, config) + self.handler_method = self.hass.bus.listen.call_args_list[0][0][1] + + attrs = { + 'friendly_fake': 'tag_str', + 'field_fake': 'field_str', + } + state = mock.MagicMock( + state=1, domain='fake', + entity_id='fake.something', + object_id='something', attributes=attrs) + event = mock.MagicMock(data={'new_state': state}, time_fired=12345) + body = [{ + 'measurement': 'fake.something', + 'tags': { + 'domain': 'fake', + 'entity_id': 'something', + 'friendly_fake': 'tag_str' + }, + 'time': 12345, + 'fields': { + 'value': 1, + 'field_fake_str': 'field_str' + }, + }] + self.handler_method(event) + self.assertEqual( + mock_client.return_value.write_points.call_count, 1 + ) + self.assertEqual( + mock_client.return_value.write_points.call_args, + mock.call(body) + ) + mock_client.return_value.write_points.reset_mock() + + def test_event_listener_component_override_measurement(self, mock_client): + """Test the event listener with overrided measurements.""" + config = { + 'influxdb': { + 'host': 'host', + 'username': 'user', + 'password': 'pass', + 'component_config': { + 'sensor.fake_humidity': { + 'override_measurement': 'humidity' + } + }, + 'component_config_glob': { + 'binary_sensor.*motion': { + 'override_measurement': 'motion' + } + }, + 'component_config_domain': { + 'climate': { + 'override_measurement': 'hvac' + } + } + } + } + assert setup_component(self.hass, influxdb.DOMAIN, config) + self.handler_method = self.hass.bus.listen.call_args_list[0][0][1] + + test_components = [ + {'domain': 'sensor', 'id': 'fake_humidity', 'res': 'humidity'}, + {'domain': 'binary_sensor', 'id': 'fake_motion', 'res': 'motion'}, + {'domain': 'climate', 'id': 'fake_thermostat', 'res': 'hvac'}, + {'domain': 'other', 'id': 'just_fake', 'res': 'other.just_fake'}, + ] + for comp in test_components: + state = mock.MagicMock( + state=1, domain=comp['domain'], + entity_id=comp['domain'] + '.' + comp['id'], + object_id=comp['id'], attributes={}) + event = mock.MagicMock(data={'new_state': state}, time_fired=12345) + body = [{ + 'measurement': comp['res'], + 'tags': { + 'domain': comp['domain'], + 'entity_id': comp['id'] + }, + 'time': 12345, + 'fields': { + 'value': 1, + }, + }] + self.handler_method(event) + self.assertEqual( + mock_client.return_value.write_points.call_count, 1 + ) + self.assertEqual( + mock_client.return_value.write_points.call_args, + mock.call(body) + ) + mock_client.return_value.write_points.reset_mock()