15 # The message queues live outside of python space, and must be formally cleaned!
17 """In case the program is cancelled or quit, we need to clean up the PulseIn
18 helper process and also the message queue, this is called at exit to do so"""
20 print("Cleaning up message queues", queues)
21 print("Cleaning up processes", procs)
26 atexit.register(final)
29 def __init__(self, pin, maxlen=2, idle_state=False):
30 """Create a PulseIn object associated with the given pin.
31 The object acts as a read-only sequence of pulse lengths with
32 a given max length. When it is active, new pulse lengths are
33 added to the end of the list. When there is no more room
34 (len() == maxlen) the oldest pulse length is removed to make room."""
37 self._idle_state = idle_state
38 self._queue_key = random.randint(1, 9999)
40 self._mq = sysv_ipc.MessageQueue(None, flags=sysv_ipc.IPC_CREX)
42 print("Message Queue Key: ", self._mq.key)
43 queues.append(self._mq)
44 except sysv_ipc.ExistentialError:
45 raise RuntimeError("Message queue creation failed")
47 dir_path = os.path.dirname(os.path.realpath(__file__))
48 cmd = [dir_path+"/libgpiod_pulsein",
49 "--pulses", str(maxlen),
50 "--queue", str(self._mq.key)]
53 cmd.append("gpiochip0")
58 self._process = subprocess.Popen(cmd)
59 procs.append(self._process)
61 # wait for it to start up
63 print("Waiting for startup success message from subprocess")
64 message = self._wait_receive_msg()
65 if message[0] != b'!':
66 raise RuntimeError("Could not establish message queue with subprocess")
69 def _wait_receive_msg(self, timeout=0.25, type=2):
70 """Internal helper that will wait for new messages of a given type,
71 and throw an exception on timeout"""
72 stamp = time.monotonic()
73 while (time.monotonic() - stamp) < timeout:
75 message = self._mq.receive(block=False, type=2)
77 except sysv_ipc.BusyError:
78 time.sleep(0.001) # wait a bit then retry!
80 raise RuntimeError("Timed out waiting for PulseIn message")
83 """Deinitialises the PulseIn and releases any hardware and software
84 resources for reuse."""
85 # Clean up after ourselves
86 self._process.terminate()
87 procs.remove(self._process)
89 queues.remove(self._mq)
92 """No-op used by Context Managers."""
95 def __exit__(self, exc_type, exc_value, tb):
96 """Automatically deinitializes the hardware when exiting a context."""
99 def resume(self, trigger_duration=0):
100 """Resumes pulse capture after an optional trigger pulse."""
101 if trigger_duration != 0:
102 self._mq.send("t%d" % trigger_duration, True, type=1)
104 self._mq.send("r", True, type=1)
108 """Pause pulse capture"""
109 self._mq.send("p", True, type=1)
114 """True when pulse capture is paused as a result of pause() or
115 an error during capture such as a signal that is too fast."""
120 """The maximum length of the PulseIn. When len() is equal to maxlen,
121 it is unclear which pulses are active and which are idle."""
125 """Clears all captured pulses"""
126 self._mq.send("c", True, type=1)
129 """Removes and returns the oldest read pulse."""
130 self._mq.send("^", True, type=1)
131 message = self._wait_receive_msg()
132 reply = int(message[0].decode('utf-8'))
135 raise IndexError("pop from empty list")
139 """Returns the current pulse length"""
140 self._mq.send("l", True, type=1)
141 message = self._wait_receive_msg()
142 return int(message[0].decode('utf-8'))
144 def __getitem__(self, index, type=None):
145 """Returns the value at the given index or values in slice."""
146 self._mq.send("i%d" % index, True, type=1)
147 message = self._wait_receive_msg()
148 ret = int(message[0].decode('utf-8'))
150 raise IndexError("list index out of range")