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.
11 from .gobject import ScheduledFunction
13 logger = logging.getLogger("onoff.common")
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
23 @type notify: {int -> None}
24 @ivar notify: A set of functions taking a changed state.
27 @ivar state: is a read-only attribute to retrieve the current state
32 def changestate(self, state):
33 """Tell interested parties that the state has changed to the given
35 for func in self.notify:
39 """Ask the device to power on."""
40 raise NotImplementedError
43 """Ask the device to power off."""
44 raise NotImplementedError
47 """Release resources acquired by the constructor."""
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)
55 self.device.activate()
56 self.device.notify.add(self.changestate)
58 def changestate(self, state):
59 OnoffDevice.changestate(self, state ^ ST_ACTIVE)
63 return self.device.state ^ ST_ACTIVE
66 self.device.deactivate()
69 self.device.activate()
72 self.device.notify.remove(self.changestate)
75 class ThrottledDevice(OnoffDevice):
76 """A device that delays the activation signal and the actual deactivation
77 by a fixed amounts of time. This limits the rate of state transitions to
79 def __init__(self, device, ondelay, offdelay):
81 @type device: OnoffDevice
82 @type ondelay: int or float
83 @param ondelay: delay the report of ST_ACTIVE by this many seconds
84 @type offdelay: int or float
85 @param offdelay: delay the actual deactivation by this many seconds
87 OnoffDevice.__init__(self)
89 self.ondelay = ondelay
90 self.offdelay = offdelay
91 self.desired_state = 0
92 self.transition = None
93 self.device.notify.add(self.changestate)
97 if self.transition is None:
98 return self.device.state
99 return self.desired_state | ST_TRANSITION
101 def _schedule_transition(self, delay, func):
102 assert self.transition is None
103 self.transition = ScheduledFunction(delay, func)
105 def _cancel_transition(self):
106 assert self.transition is not None
107 self.transition.cancel()
108 self.transition = None
110 def changestate(self, state):
111 if state != ST_ACTIVE:
112 OnoffDevice.changestate(self, state)
114 if self.desired_state == 0:
115 logger.warning("device became active but we want inactive," +
117 elif self.transition is None:
118 logger.debug("scheduling report of activation in %.1fs",
120 self._schedule_transition(self.ondelay, self._report_active)
122 logger.debug("suppressing duplicate activation signal")
124 def _report_active(self):
125 assert self.desired_state == ST_ACTIVE
126 assert self.transition is not None
127 self.transition = None
128 logger.debug("delivering activation signal")
129 OnoffDevice.changestate(self, ST_ACTIVE)
132 if self.desired_state == 0 and self.transition is not None:
133 logger.debug("cancelling pending deactivation")
134 self._cancel_transition()
135 curstate = self.device.state
136 if curstate == ST_ACTIVE:
137 self.desired_state = ST_ACTIVE
138 OnoffDevice.changestate(self, ST_ACTIVE)
140 logger.warning("device should be active during delayed " +
141 "deactivation, but is in state %d", curstate)
142 self.desired_state = ST_ACTIVE
143 self.device.activate()
144 self.changestate(self.device.state)
147 assert self.desired_state == 0
148 assert self.transition is not None
149 self.transition = None
150 logger.debug("actually deactivating")
151 self.device.deactivate()
153 def deactivate(self):
154 if self.desired_state == ST_ACTIVE and self.transition is not None:
155 logger.debug("cancelling pending activation report")
156 self._cancel_transition()
157 self.desired_state = 0
158 if self.transition is None:
159 logger.debug("scheduling actual deactivate in %.1fs",
161 self._schedule_transition(self.offdelay, self._do_stop)
162 self.changestate(self.state)
164 logger.debug("not issuing deactivate due to pending deactivate")
167 if self.transition is not None:
168 logger.info("cancelling pending transition")
169 self._cancel_transition()
170 if self.desired_state == 0:
171 logger.info("invoking pending deactivate early during close")
172 self.device.deactivate()
173 self.device.notify.remove(self.changestate)