X-Git-Url: https://git.linta.de/?p=~helmut%2Fonoff.git;a=blobdiff_plain;f=onoff%2Fcommon.py;h=5d9565cd553c144a2815f171a811389c020bd343;hp=7f389c950d329f79fd4c07b687110ccb452061a2;hb=873cb2015c559466d35bda40f0fba8abbb4c8cb5;hpb=23ebf5211f73c7944908fd44913c2f645ae4f0f9 diff --git a/onoff/common.py b/onoff/common.py index 7f389c9..5d9565c 100644 --- a/onoff/common.py +++ b/onoff/common.py @@ -1,19 +1,18 @@ -""" -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. -""" - +import enum import logging -from gi.repository import GObject +from .gobject import ScheduledFunction logger = logging.getLogger("onoff.common") -ST_ACTIVE = 1 -ST_TRANSITION = 2 + +class OnoffState(enum.Flag): + inactive = 0 + active = 1 + deactivating = 2 + activating = 3 + transition = 2 + class OnoffDevice(object): """A device is a thing with two states, that can be asked to transition @@ -56,11 +55,11 @@ class InvertedDevice(OnoffDevice): self.device.notify.add(self.changestate) def changestate(self, state): - OnoffDevice.changestate(self, state ^ ST_ACTIVE) + OnoffDevice.changestate(self, state ^ OnoffState.active) @property def state(self): - return self.device.state ^ ST_ACTIVE + return self.device.state ^ OnoffState.active def activate(self): self.device.deactivate() @@ -73,50 +72,105 @@ class InvertedDevice(OnoffDevice): 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 OnoffState.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.desired_state = 0 - self.stopping = None + self.ondelay = ondelay + self.offdelay = offdelay + self.desired_state = OnoffState.inactive + 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 | OnoffState.transition + + def _schedule_transition(self, delay, func): + assert self.transition is None + self.transition = ScheduledFunction(delay, func) + + def _cancel_transition(self): + assert self.transition is not None + self.transition.cancel() + self.transition = None + + def changestate(self, state): + if state != OnoffState.active: + OnoffDevice.changestate(self, state) + else: + if self.desired_state == OnoffState.inactive: + logger.warning("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 == OnoffState.active + assert self.transition is not None + self.transition = None + logger.debug("delivering activation signal") + OnoffDevice.changestate(self, OnoffState.active) def activate(self): - self.desired_state = ST_ACTIVE - if self.stopping is not None: + if self.desired_state == OnoffState.inactive 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 == OnoffState.active: + self.desired_state = OnoffState.active + OnoffDevice.changestate(self, OnoffState.active) + return + logger.warning("device should be active during delayed " + + "deactivation, but is in state %s", curstate.name) + self.desired_state = OnoffState.active self.device.activate() + self.changestate(self.device.state) - def do_stop(self): - assert self.desired_state == 0 - assert self.stopping is not None - self.stopping = None + def _do_stop(self): + assert self.desired_state == OnoffState.inactive + assert self.transition is not None + self.transition = None logger.debug("actually deactivating") self.device.deactivate() def deactivate(self): - 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.desired_state == OnoffState.active and \ + self.transition is not None: + logger.debug("cancelling pending activation report") + self._cancel_transition() + self.desired_state = OnoffState.inactive + 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 == OnoffState.inactive: + logger.info("invoking pending deactivate early during close") + self.device.deactivate() self.device.notify.remove(self.changestate) self.device.close()