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

Adding expire_after to mqtt sensor to expire outdated values #6708

Merged
merged 12 commits into from Mar 23, 2017
22 changes: 19 additions & 3 deletions homeassistant/components/sensor/mqtt.py
Expand Up @@ -6,6 +6,8 @@
"""
import asyncio
import logging
import time
from datetime import timedelta

import voluptuous as vol

Expand All @@ -17,9 +19,11 @@
import homeassistant.components.mqtt as mqtt
import homeassistant.helpers.config_validation as cv


_LOGGER = logging.getLogger(__name__)

CONF_FORCE_UPDATE = 'force_update'
CONF_EXPIRE_AFTER = 'expire_after'

DEFAULT_NAME = 'MQTT Sensor'
DEFAULT_FORCE_UPDATE = False
Expand All @@ -31,6 +35,7 @@
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
})

SCAN_INTERVAL = timedelta(seconds=1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this.


@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
Expand All @@ -48,6 +53,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_QOS),
config.get(CONF_UNIT_OF_MEASUREMENT),
config.get(CONF_FORCE_UPDATE),
config.get(CONF_EXPIRE_AFTER),
value_template,
)])

Expand All @@ -56,7 +62,7 @@ class MqttSensor(Entity):
"""Representation of a sensor that can be updated using MQTT."""

def __init__(self, name, state_topic, qos, unit_of_measurement,
force_update, value_template):
force_update, expire_after, value_template):
"""Initialize the sensor."""
self._state = STATE_UNKNOWN
self._name = name
Expand All @@ -65,6 +71,8 @@ def __init__(self, name, state_topic, qos, unit_of_measurement,
self._unit_of_measurement = unit_of_measurement
self._force_update = force_update
self._template = value_template
self._expire_after = int(expire_after or 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the config schema to convert it to an integer and set default value

self._value_expiration_at = 0

def async_added_to_hass(self):
"""Subscribe mqtt events.
Expand All @@ -73,6 +81,9 @@ def async_added_to_hass(self):
"""
@callback
def message_received(topic, payload, qos):
"""Reset expiration time."""
self._value_expiration_at = time.time() + self._expire_after
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the event helper async_track_point_in_utc_time to call an async function to set the state to None. async_track_point_in_utc_time will return a function that can be called to remove the listener. So on message received, remove the last listener and add a new time listener.


"""A new MQTT message has been received."""
if self._template is not None:
payload = self._template.async_render_with_possible_json_value(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you change this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line was 80 chars long (not my edit but wanted to have it fixed).

Expand All @@ -85,8 +96,13 @@ def message_received(topic, payload, qos):

@property
def should_poll(self):
"""No polling needed."""
return False
"""Polling needed only for auto-expire."""
return self._expire_after > 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be implemented with polling


def update(self):
"""Check if value is expired."""
if self._expire_after > 0 and time.time() > self._value_expiration_at:
self._state = STATE_UNKNOWN

@property
def name(self):
Expand Down
41 changes: 40 additions & 1 deletion tests/components/sensor/test_mqtt.py
@@ -1,6 +1,7 @@
"""The tests for the MQTT sensor platform."""
import unittest

import time
import homeassistant.core as ha
from homeassistant.setup import setup_component
import homeassistant.components.sensor as sensor
Expand Down Expand Up @@ -37,11 +38,49 @@ def test_setting_sensor_value_via_mqtt_message(self):
fire_mqtt_message(self.hass, 'test-topic', '100')
self.hass.block_till_done()
state = self.hass.states.get('sensor.test')

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blank line contains whitespace

self.assertEqual('100', state.state)
self.assertEqual('fav unit',
state.attributes.get('unit_of_measurement'))

def test_setting_sensor_value_expires(self):
"""Test the expiration of the value."""
mock_component(self.hass, 'mqtt')
assert setup_component(self.hass, sensor.DOMAIN, {
sensor.DOMAIN: {
'platform': 'mqtt',
'name': 'test',
'state_topic': 'test-topic',
'unit_of_measurement': 'fav unit',
'expire_after': '2',
'force_update': True
}
})

state = self.hass.states.get('sensor.test')
self.assertEqual('unknown', state.state)

fire_mqtt_message(self.hass, 'test-topic', '100')
self.hass.block_till_done()

state = self.hass.states.get('sensor.test')
self.assertEqual('100', state.state)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blank line contains whitespace

time.sleep(1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are never allowed to call time.sleep in tests. Please use the mock_fire_time_changed helper.

# FIXME: how to call update() on the sensor?

""" Not yet expired """
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments in Python start with a #

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please prefix comments with # . Only doc strings (first comment in a function) should contain triple quotes.

state = self.hass.states.get('sensor.test')
self.assertEqual('100', state.state)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blank line contains whitespace

time.sleep(2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️

# FIXME: how to call update() on the sensor?

""" Expired """
state = self.hass.states.get('sensor.test')
# FIXME: this will fail unless the fixmes above are fixed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Soo, fixed now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, forgot that one ;) Yes, the test is fixed now.

# self.assertEqual('unknown', state.state)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stale comment.


def test_setting_sensor_value_via_mqtt_json_message(self):
"""Test the setting of the value via MQTT with JSON playload."""
mock_component(self.hass, 'mqtt')
Expand Down