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 gi.repository import GObject
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 = GObject.timeout_add(int(1000 * delay), func)
105 def _cancel_transition(self):
106 assert self.transition is not None
107 ret = GObject.source_remove(self.transition)
109 self.transition = None
111 def changestate(self, state):
112 if state != ST_ACTIVE:
113 OnoffDevice.changestate(self, state)
115 if self.desired_state == 0:
116 logger.warn("device became active but we want inactive," +
118 elif self.transition is None:
119 logger.debug("scheduling report of activation in %.1fs",
121 self._schedule_transition(self.ondelay, self._report_active)
123 logger.debug("suppressing duplicate activation signal")
125 def _report_active(self):
126 assert self.desired_state == ST_ACTIVE
127 assert self.transition is not None
128 self.transition = None
129 logger.debug("delivering activation signal")
130 OnoffDevice.changestate(self, ST_ACTIVE)
133 if self.desired_state == 0 and self.transition is not None:
134 logger.debug("cancelling pending deactivation")
135 self._cancel_transition()
136 self.desired_state = ST_ACTIVE
137 self.device.activate()
138 self.changestate(self.device.state)
141 assert self.desired_state == 0
142 assert self.transition is not None
143 self.transition = None
144 logger.debug("actually deactivating")
145 self.device.deactivate()
147 def deactivate(self):
148 if self.desired_state == ST_ACTIVE and self.transition is not None:
149 logger.debug("cancelling pending activation report")
150 self._cancel_transition()
151 self.desired_state = 0
152 if self.transition is None:
153 logger.debug("scheduling actual deactivate in %.1fs",
155 self._schedule_transition(self.offdelay, self._do_stop)
156 self.changestate(self.state)
158 logger.debug("not issuing deactivate due to pending deactivate")
161 if self.transition is not None:
162 logger.info("cancelling pending transition")
163 self._cancel_transition()
164 if self.desired_state == 0:
165 logger.info("invoking pending deactivate early during close")
166 self.device.deactivate()
167 self.device.notify.remove(self.changestate)