provide a default for --busname
[~helmut/onoff.git] / onoff / common.py
1 """
2 Defined states:
3  * 0: The device is inactive.
4  * ST_ACTIVE|ST_TRANSITION: The device is transitioning from inactive to active.
5  * ST_ACTIVE: The device is active.
6  * ST_TRANSITION: The device is transitioning from active to inactive.
7 """
8
9 import logging
10
11 from gi.repository import GObject
12
13 logger = logging.getLogger("onoff.common")
14
15 ST_ACTIVE = 1
16 ST_TRANSITION = 2
17
18 class OnoffDevice(object):
19     """A device is a thing with two states, that can be asked to transition
20     from either state to the other. It can signal state changes to interested
21     parties.
22
23     @type notify: {int -> None}
24     @ivar notify: A set of functions taking a changed state.
25
26     @type state: int
27     @ivar state: is a read-only attribute to retrieve the current state
28     """
29     def __init__(self):
30         self.notify = set()
31
32     def changestate(self, state):
33         """Tell interested parties that the state has changed to the given
34         state."""
35         for func in self.notify:
36             func(state)
37
38     def activate(self):
39         """Ask the device to power on."""
40         raise NotImplementedError
41
42     def deactivate(self):
43         """Ask the device to power off."""
44         raise NotImplementedError
45
46     def close(self):
47         """Release resources acquired by the constructor."""
48         pass
49
50 class InvertedDevice(OnoffDevice):
51     """A device that swaps active and inactive states of a give device."""
52     def __init__(self, device):
53         OnoffDevice.__init__(self)
54         self.device = device
55         self.device.activate()
56         self.device.notify.add(self.changestate)
57
58     def changestate(self, state):
59         OnoffDevice.changestate(self, state ^ ST_ACTIVE)
60
61     @property
62     def state(self):
63         return self.device.state ^ ST_ACTIVE
64
65     def activate(self):
66         self.device.deactivate()
67
68     def deactivate(self):
69         self.device.activate()
70
71     def close(self):
72         self.device.notify.remove(self.changestate)
73         self.device.close()
74
75 class ThrottledDevice(OnoffDevice):
76     """A device that delays deactivation by a fixed amount of time. This limits
77     the rate of state transitions to two per delay."""
78     def __init__(self, device, delay):
79         """
80         @type device: OnoffDevice
81         @type delay: int or float
82         @param delay: deactivation delay in seconds
83         """
84         OnoffDevice.__init__(self)
85         self.device = device
86         self.delay = delay
87         self.desired_state = 0
88         self.stopping = None
89         self.device.notify.add(self.changestate)
90
91     @property
92     def state(self):
93         return self.device.state | (ST_TRANSITION if self.stopping else 0)
94
95     def activate(self):
96         self.desired_state = ST_ACTIVE
97         if self.stopping is not None:
98             logger.debug("cancelling pending deactivation")
99             ret = GObject.source_remove(self.stopping)
100             assert ret
101             self.stopping = None
102         self.device.activate()
103
104     def do_stop(self):
105         assert self.desired_state == 0
106         assert self.stopping is not None
107         self.stopping = None
108         logger.debug("actually deactivating")
109         self.device.deactivate()
110
111     def deactivate(self):
112         self.desired_state = 0
113         if self.stopping is None:
114             logger.debug("scheduling actual deactivate in %.1fs", self.delay)
115             self.stopping = GObject.timeout_add(1000 * self.delay, self.do_stop)
116             self.changestate(self.state)
117         else:
118             logger.debug("not issuing deactivate due to pending deactivate")
119
120     def close(self):
121         self.device.notify.remove(self.changestate)
122         self.device.close()