change activatefd method and add ThrottledDevice
authorHelmut Grohne <helmut@subdivi.de>
Sat, 22 Jun 2013 11:33:43 +0000 (13:33 +0200)
committerHelmut Grohne <helmut@subdivi.de>
Sat, 22 Jun 2013 11:33:43 +0000 (13:33 +0200)
When activating via activatefd, there is no reason to add an additional
delay. Simply close the socket later. If a device should delay
deactivation, this is a policy decision, that should be made by the
service. This policy decision can be done using the new ThrottledDevice.

dbus_client.py
dbus_service.py
mpd_watcher.py
onoff/common.py

index 7a5cbbb..0ee6b7a 100755 (executable)
@@ -37,7 +37,7 @@ def main():
     DBusGMainLoop(set_as_default=True)
     proxy = onoff.dbusutils.get_dbus_proxy(args)
     if args.command:
-        st, fd = proxy.activatefd(args.duration)
+        st, fd = proxy.activatefd()
         fd = fd.take()
         os.dup2(fd, 254)
         os.close(fd)
index f5c8b1a..3c0c267 100755 (executable)
@@ -43,14 +43,14 @@ class OnoffControl(dbus.service.Object):
         GObject.timeout_add(duration * 1000, self.unuse)
         return self.use()
 
-    @dbus.service.method(domain, in_signature="q", out_signature="qh")
-    def activatefd(self, duration):
-        logger.info("activatefd duration %d", duration)
+    @dbus.service.method(domain, in_signature="", out_signature="qh")
+    def activatefd(self):
+        logger.info("activatefd")
         notifyfd, retfd = onoff.dbusutils.socketpair()
         def callback(fd, _):
             logger.info("fd %d completed", fd.fileno())
             fd.close()
-            GObject.timeout_add(duration * 1000, self.unuse)
+            self.unuse()
             return False
         GObject.io_add_watch(notifyfd, GObject.IO_HUP|GObject.IO_ERR, callback)
         return (self.use(), retfd)
@@ -77,6 +77,7 @@ def main():
     bus = dbus.SessionBus()
     dev = onoff.process.OnoffProcess(["redshift"], 3)
     dev = onoff.common.InvertedDevice(dev)
+    dev = onoff.common.ThrottledDevice(dev, 5)
     OnoffControl(bus, "redshift", dev)
     GObject.MainLoop().run()
 
index f085797..f72ca51 100755 (executable)
@@ -33,7 +33,7 @@ class MpdWatcher(object):
         state = self.mpdclient.status()["state"]
         if state == "play":
             if self.activatefd is None:
-                st, fd = self.onoffproxy.activatefd(3)
+                st, fd = self.onoffproxy.activatefd()
                 self.activatefd = fd.take()
         else:
             if self.activatefd is not None:
index 8db16a7..7f389c9 100644 (file)
@@ -6,6 +6,12 @@ Defined states:
  * ST_TRANSITION: The device is transitioning from active to inactive.
 """
 
+import logging
+
+from gi.repository import GObject
+
+logger = logging.getLogger("onoff.common")
+
 ST_ACTIVE = 1
 ST_TRANSITION = 2
 
@@ -65,3 +71,52 @@ class InvertedDevice(OnoffDevice):
     def close(self):
         self.device.notify.remove(self.changestate)
         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):
+        """
+        @type device: OnoffDevice
+        @type delay: int or float
+        @param delay: deactivation delay in seconds
+        """
+        OnoffDevice.__init__(self)
+        self.device = device
+        self.delay = delay
+        self.desired_state = 0
+        self.stopping = None
+        self.device.notify.add(self.changestate)
+
+    @property
+    def state(self):
+        return self.device.state | (ST_TRANSITION if self.stopping else 0)
+
+    def activate(self):
+        self.desired_state = ST_ACTIVE
+        if self.stopping is not None:
+            logger.debug("cancelling pending deactivation")
+            ret = GObject.source_remove(self.stopping)
+            assert ret
+            self.stopping = None
+        self.device.activate()
+
+    def do_stop(self):
+        assert self.desired_state == 0
+        assert self.stopping is not None
+        self.stopping = 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)
+            self.changestate(self.state)
+        else:
+            logger.debug("not issuing deactivate due to pending deactivate")
+
+    def close(self):
+        self.device.notify.remove(self.changestate)
+        self.device.close()