Adding expire_after to mqtt sensor to expire outdated values#6708
Conversation
| """ Not yet expired """ | ||
| state = self.hass.states.get('sensor.test') | ||
| self.assertEqual('100', state.state) | ||
|
|
|
|
||
| state = self.hass.states.get('sensor.test') | ||
| self.assertEqual('100', state.state) | ||
|
|
| self.hass.block_till_done() | ||
| state = self.hass.states.get('sensor.test') | ||
|
|
||
| self._unit_of_measurement = unit_of_measurement | ||
| self._force_update = force_update | ||
| self._template = value_template | ||
| self._expire_after = int(expire_after or 0) |
There was a problem hiding this comment.
Please use the config schema to convert it to an integer and set default value
| vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, | ||
| }) | ||
|
|
||
| SCAN_INTERVAL = timedelta(seconds=1) |
| @callback | ||
| def message_received(topic, payload, qos): | ||
| """Reset expiration time.""" | ||
| self._value_expiration_at = time.time() + self._expire_after |
There was a problem hiding this comment.
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.
| """No polling needed.""" | ||
| return False | ||
| """Polling needed only for auto-expire.""" | ||
| return self._expire_after > 0 |
There was a problem hiding this comment.
This should not be implemented with polling
| state = self.hass.states.get('sensor.test') | ||
| self.assertEqual('100', state.state) | ||
|
|
||
| time.sleep(1) |
There was a problem hiding this comment.
You are never allowed to call time.sleep in tests. Please use the mock_fire_time_changed helper.
| time.sleep(1) | ||
| # FIXME: how to call update() on the sensor? | ||
|
|
||
| """ Not yet expired """ |
There was a problem hiding this comment.
Comments in Python start with a #
| state = self.hass.states.get('sensor.test') | ||
| self.assertEqual('100', state.state) | ||
|
|
||
| time.sleep(2) |
| """ Expired """ | ||
| state = self.hass.states.get('sensor.test') | ||
| # FIXME: this will fail unless the fixmes above are fixed | ||
| # self.assertEqual('unknown', state.state) |
|
@balloob Thank you very much for the good feedback. I'm new to python and new to home assistant development as well, so I'm glad that you point me in the right direction. I'll revise my code later today. Regards, |
|
@balloob I tried to change everything according your comments. Coding part was easy, the lint stuff was the really hard part. Hope it's good now. |
| dt_util.utcnow() + | ||
| timedelta(seconds=self._expire_after) | ||
| ) | ||
| async_track_point_in_utc_time(self.hass, |
There was a problem hiding this comment.
async_track_point_in_utc_time returns a function that will unsubscribe the just subscribed listener. So instead of storing a timestamp when to expire, unsubscribe the old listener if it exists and subscribe a new one.
Then in the callback, set the unsubscribe function to None
There was a problem hiding this comment.
Will this be thread-safe? E.g. no race-condition between unsubscribing a (probably in exactly in this moment executed callback) and a new message?
Flow with new code:
- Callback is executed in the moment the message comes in
- Unsubscribe is executed -> too late to have effect
- new value is set
- already running Callback resets the value
The more I think about it, the more I have the impression that I can also run in that with my current code:
Flow with my old code:
- Callback is executed in the moment the message comes in
- Callback checks time, detects that the value is to expire
- new expire time and value is set
- already running Callback resets the value
Any thoughts about the thread safety here?
There was a problem hiding this comment.
To answer this question, the mqtt component is written using Python's asyncio library, which means that only one of these callbacks can be running at a time. If you do some searching for asyncio you can find some good literature.
| timedelta(seconds=1)) | ||
|
|
||
| if self._template is not None: | ||
| payload = self._template.async_render_with_possible_json_value( |
There was a problem hiding this comment.
Line was 80 chars long (not my edit but wanted to have it fixed).
| time = time + timedelta(seconds=1) | ||
| fire_time_changed(self.hass, time) | ||
|
|
||
| """ Not yet expired """ |
There was a problem hiding this comment.
Please prefix comments with # . Only doc strings (first comment in a function) should contain triple quotes.
|
|
||
| """ Expired """ | ||
| state = self.hass.states.get('sensor.test') | ||
| # FIXME: this will fail unless the fixmes above are fixed |
There was a problem hiding this comment.
Oops, forgot that one ;) Yes, the test is fixed now.
|
|
||
| # Expired | ||
| state = self.hass.states.get('sensor.test') | ||
| # FIXME: I have no idea why this does not work. Got stuck here, help plz! :-( |
There was a problem hiding this comment.
line too long (85 > 79 characters)
| PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({ | ||
| vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, | ||
| vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, | ||
| vol.Optional(CONF_EXPIRE_AFTER, default=0): cv.positive_int, |
There was a problem hiding this comment.
Can we remove the default and just check if self._expire_after is not None: below?
There was a problem hiding this comment.
I can but i'd still have to check 0 and None because with 0 it would cause strange behaviour otherwise.
There was a problem hiding this comment.
Fair enough. Sounds good to me.
There was a problem hiding this comment.
Which one? default or check 0 and None?
There was a problem hiding this comment.
Sorry, you can keep it as is. keep the default zero.
| if self._template is not None: | ||
| payload = self._template.async_render_with_possible_json_value( | ||
| template = self._template | ||
| payload = template.async_render_with_possible_json_value( |
There was a problem hiding this comment.
Line was >79 chars long. I wonder how it passed the checks
There was a problem hiding this comment.
It looked to me that it was exactly 79 characters long. It should be fine. If it was in there it is OK for length.
There was a problem hiding this comment.
You're right, don't know why I got an error there. Maybe I accidently idented one more. Reverted.
There was a problem hiding this comment.
Yeah, I figured it probably got indented while working on it, then reverted.
|
Thanks for the contribution! 🎉 |
Description:
Related issue (if applicable): fixes #6705
Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#2297
Example entry for
configuration.yaml(if applicable):Checklist:
If user exposed functionality or configuration variables are added/changed:
If the code communicates with devices, web services, or third-party tools:
toxrun successfully. Your PR cannot be merged unless tests passI extended the sensors/mqtt_test.py to cover the new feature.