A new epheleral_condition sensor type + fix issue with dangling callback
This commit is contained in:
1 parent
3b0c24e795
commit
b97445ce92
4 files changed
+369
-13
No files matched your search
+85
-8
@@ -406,6 +406,7 @@ class ValueSelector(VirtualSensorBase):
|
||||
class ContinuousCondition(VirtualSensorBase):
|
||||
def initialize(self):
|
||||
self.cb_handle = None
|
||||
self._timer_generation = 0
|
||||
self.time = self.yaml_block['time']
|
||||
try: self.ignore_unavailable_condition = self.yaml_block['ignore_unavailable_condition']
|
||||
except KeyError: self.ignore_unavailable_condition = False
|
||||
@@ -452,14 +453,22 @@ class ContinuousCondition(VirtualSensorBase):
|
||||
|
||||
def register_callback(self):
|
||||
if self.cb_handle == None:
|
||||
self.cb_handle = self.ad_api.run_in(self.on_time_elapse, self.time)
|
||||
self._timer_generation += 1
|
||||
self.cb_handle = self.ad_api.run_in(
|
||||
self.on_time_elapse, self.time, generation=self._timer_generation)
|
||||
|
||||
def cancel_callback(self):
|
||||
if self.cb_handle != None:
|
||||
self.ad_api.cancel_timer(self.cb_handle)
|
||||
self.cb_handle = None
|
||||
# An expired timer may already have a callback queued in the worker.
|
||||
# Invalidate it before cancellation, even if it is no longer scheduled.
|
||||
self._timer_generation += 1
|
||||
handle = self.cb_handle
|
||||
self.cb_handle = None
|
||||
if handle is not None:
|
||||
self.ad_api.cancel_timer(handle, silent=True)
|
||||
|
||||
def on_time_elapse(self, kwargs):
|
||||
if kwargs.get('generation') != self._timer_generation:
|
||||
return
|
||||
self.cb_handle = None
|
||||
value = 'off' if self.invert else 'on'
|
||||
if self.output_sensor.get_state() != value:
|
||||
@@ -474,9 +483,68 @@ class ContinuousCondition(VirtualSensorBase):
|
||||
if self.output_sensor.get_state() != value:
|
||||
self.output_sensor.set_state(state = value)
|
||||
|
||||
class EphemeralCondition(ContinuousCondition):
|
||||
"""True during the first `time` seconds of each true condition.
|
||||
|
||||
condition_started_at is a Unix timestamp retained after expiry, until
|
||||
the condition becomes false. Reloads resume this window when the entity
|
||||
attributes survive; without a timestamp, a true condition starts anew.
|
||||
"""
|
||||
|
||||
def initialize(self):
|
||||
self._condition_active = False
|
||||
if self.yaml_block.get('invert', False):
|
||||
raise ValueError("ephemeral_condition does not support invert; negate conditions instead")
|
||||
started_at = self.ad_api.get_state(
|
||||
self.sensor_name, attribute='condition_started_at')
|
||||
try:
|
||||
started_at = float(started_at)
|
||||
if not 0 <= started_at <= time.time():
|
||||
started_at = None
|
||||
except (TypeError, ValueError):
|
||||
started_at = None
|
||||
self._started_at = started_at
|
||||
self.attributes['condition_started_at'] = started_at
|
||||
# Unknown inputs during startup must not erase a restored window.
|
||||
self.yaml_block = dict(self.yaml_block)
|
||||
self.yaml_block.setdefault('ignore_unavailable_condition', True)
|
||||
super().initialize()
|
||||
|
||||
def on_condition_succeed(self):
|
||||
if self._condition_active:
|
||||
return
|
||||
self._condition_active = True
|
||||
if self._started_at is None:
|
||||
self._started_at = time.time()
|
||||
self.attributes['condition_started_at'] = self._started_at
|
||||
remaining = self._started_at + self.time - time.time()
|
||||
self.output_sensor.set_state(
|
||||
state='on' if remaining > 0 else 'off',
|
||||
attributes={'condition_started_at': self._started_at})
|
||||
if remaining > 0:
|
||||
self._timer_generation += 1
|
||||
self.cb_handle = self.ad_api.run_in(
|
||||
self.on_time_elapse, remaining, generation=self._timer_generation)
|
||||
|
||||
def on_condition_fail(self):
|
||||
self._condition_active = False
|
||||
self._started_at = None
|
||||
self.attributes['condition_started_at'] = None
|
||||
self.cancel_callback()
|
||||
self.output_sensor.set_state(
|
||||
state='off', attributes={'condition_started_at': None})
|
||||
|
||||
def on_time_elapse(self, kwargs):
|
||||
if kwargs.get('generation') != self._timer_generation:
|
||||
return
|
||||
self.cb_handle = None
|
||||
self.output_sensor.set_state(state='off')
|
||||
|
||||
|
||||
class RetainCondition(VirtualSensorBase):
|
||||
def initialize(self):
|
||||
self.cb_handle = None
|
||||
self._timer_generation = 0
|
||||
self.retain_time = self.yaml_block['retain_time']
|
||||
try: self.reset_retain_time_on_new_success = self.yaml_block['reset_retain_time_on_new_success']
|
||||
except KeyError: self.reset_retain_time_on_new_success = True
|
||||
@@ -510,14 +578,21 @@ class RetainCondition(VirtualSensorBase):
|
||||
|
||||
def register_callback(self):
|
||||
if self.cb_handle == None:
|
||||
self.cb_handle = self.ad_api.run_in(self.on_time_elapse, self.retain_time)
|
||||
self._timer_generation += 1
|
||||
self.cb_handle = self.ad_api.run_in(
|
||||
self.on_time_elapse, self.retain_time, generation=self._timer_generation)
|
||||
|
||||
def cancel_callback(self):
|
||||
if self.cb_handle != None:
|
||||
self.ad_api.cancel_timer(self.cb_handle)
|
||||
self.cb_handle = None
|
||||
# Invalidate callbacks already queued by an expired timer as well.
|
||||
self._timer_generation += 1
|
||||
handle = self.cb_handle
|
||||
self.cb_handle = None
|
||||
if handle is not None:
|
||||
self.ad_api.cancel_timer(handle, silent=True)
|
||||
|
||||
def on_time_elapse(self, kwargs):
|
||||
if kwargs.get('generation') != self._timer_generation:
|
||||
return
|
||||
self.cb_handle = None
|
||||
if self.condition.evaluate() == SmartCondition.Result.Failed:
|
||||
if self.output_sensor.get_state() != 'off':
|
||||
@@ -697,6 +772,8 @@ class VirtualSensors():
|
||||
self.virtual_sensors[f"sensor.{splitted_sensor[1]}"] = Averager(self.ad_api,self.logger_interface,splitted_sensor[1],super_entity_id = super_entity_id,yaml_block = yaml_block['sensors'][sensor],constants = constants,templates_library = templates_library,app_name = app_name)
|
||||
elif splitted_sensor[0] == 'value_selector':
|
||||
self.virtual_sensors[f"sensor.{splitted_sensor[1]}"] = ValueSelector(self.ad_api,self.logger_interface,splitted_sensor[1],super_entity_id = super_entity_id,yaml_block = yaml_block['sensors'][sensor],constants = constants,templates_library = templates_library,app_name = app_name)
|
||||
elif splitted_sensor[0] == 'ephemeral_condition':
|
||||
self.virtual_sensors[f"binary_sensor.{splitted_sensor[1]}"] = EphemeralCondition(self.ad_api,self.logger_interface,splitted_sensor[1],super_entity_id = super_entity_id,yaml_block = yaml_block['sensors'][sensor],constants = constants,templates_library = templates_library,app_name = app_name)
|
||||
elif splitted_sensor[0] == 'retain_condition':
|
||||
self.virtual_sensors[f"binary_sensor.{splitted_sensor[1]}"] = RetainCondition(self.ad_api,self.logger_interface,splitted_sensor[1],super_entity_id = super_entity_id,yaml_block = yaml_block['sensors'][sensor],constants = constants,templates_library = templates_library,app_name = app_name)
|
||||
elif splitted_sensor[0] == 'event_sensor':
|
||||
|
||||
Reference in new issue
Block a user