"""
Defined states:
- * 0: The device is inactive.
- * ST_ACTIVE|ST_TRANSITION: The device is transitioning from inactive to active.
- * ST_ACTIVE: The device is active.
- * ST_TRANSITION: The device is transitioning from active to inactive.
+ - 0: The device is inactive.
+ - ST_ACTIVE|ST_TRANSITION: The device is transitioning from inactive to active.
+ - ST_ACTIVE: The device is active.
+ - ST_TRANSITION: The device is transitioning from active to inactive.
"""
import logging
self.device.close()
class ThrottledDevice(OnoffDevice):
- """A device that delays deactivation by a fixed amount of time. This limits
- the rate of state transitions to two per delay."""
- def __init__(self, device, delay):
+ """A device that delays the activation signal and the actual deactivation
+ by a fixed amounts of time. This limits the rate of state transitions to
+ two per offdelay."""
+ def __init__(self, device, ondelay, offdelay):
"""
@type device: OnoffDevice
- @type delay: int or float
- @param delay: deactivation delay in seconds
+ @type ondelay: int or float
+ @param ondelay: delay the report of ST_ACTIVE by this many seconds
+ @type offdelay: int or float
+ @param offdelay: delay the actual deactivation by this many seconds
"""
OnoffDevice.__init__(self)
self.device = device
- self.delay = delay
+ self.ondelay = ondelay
+ self.offdelay = offdelay
self.desired_state = 0
- self.stopping = None
+ self.transition = None
self.device.notify.add(self.changestate)
@property
def state(self):
- return self.device.state | (ST_TRANSITION if self.stopping else 0)
+ if self.transition is None:
+ return self.device.state
+ return self.desired_state | ST_TRANSITION
+
+ def _schedule_transition(self, delay, func):
+ assert self.transition is None
+ self.transition = GObject.timeout_add(int(1000 * delay), func)
+
+ def _cancel_transition(self):
+ assert self.transition is not None
+ ret = GObject.source_remove(self.transition)
+ assert ret
+ self.transition = None
+
+ def changestate(self, state):
+ if state != ST_ACTIVE:
+ OnoffDevice.changestate(self, state)
+ else:
+ if self.desired_state == 0:
+ logger.warn("device became active but we want inactive," +
+ "suppresing signal")
+ elif self.transition is None:
+ logger.debug("scheduling report of activation in %.1fs",
+ self.ondelay)
+ self._schedule_transition(self.ondelay, self._report_active)
+ else:
+ logger.debug("suppressing duplicate activation signal")
+
+ def _report_active(self):
+ assert self.desired_state == ST_ACTIVE
+ assert self.transition is not None
+ self.transition = None
+ logger.debug("delivering activation signal")
+ OnoffDevice.changestate(self, ST_ACTIVE)
def activate(self):
- self.desired_state = ST_ACTIVE
- if self.stopping is not None:
+ if self.desired_state == 0 and self.transition is not None:
logger.debug("cancelling pending deactivation")
- ret = GObject.source_remove(self.stopping)
- assert ret
- self.stopping = None
+ self._cancel_transition()
+ curstate = self.device.state
+ if curstate == ST_ACTIVE:
+ self.desired_state = ST_ACTIVE
+ OnoffDevice.changestate(self, ST_ACTIVE)
+ return
+ logger.warn("device should be active during delayed deactivation" +
+ ", but is in state %d", curstate)
+ self.desired_state = ST_ACTIVE
self.device.activate()
+ self.changestate(self.device.state)
- def do_stop(self):
+ def _do_stop(self):
assert self.desired_state == 0
- assert self.stopping is not None
- self.stopping = None
+ assert self.transition is not None
+ self.transition = None
logger.debug("actually deactivating")
self.device.deactivate()
def deactivate(self):
+ if self.desired_state == ST_ACTIVE and self.transition is not None:
+ logger.debug("cancelling pending activation report")
+ self._cancel_transition()
self.desired_state = 0
- if self.stopping is None:
- logger.debug("scheduling actual deactivate in %.1fs", self.delay)
- self.stopping = GObject.timeout_add(1000 * self.delay, self.do_stop)
+ if self.transition is None:
+ logger.debug("scheduling actual deactivate in %.1fs",
+ self.offdelay)
+ self._schedule_transition(self.offdelay, self._do_stop)
self.changestate(self.state)
else:
logger.debug("not issuing deactivate due to pending deactivate")
def close(self):
+ if self.transition is not None:
+ logger.info("cancelling pending transition")
+ self._cancel_transition()
+ if self.desired_state == 0:
+ logger.info("invoking pending deactivate early during close")
+ self.device.deactivate()
self.device.notify.remove(self.changestate)
self.device.close()