]> Repositories - Adafruit_Blinka-hackapet.git/blob - src/adafruit_blinka/microcontroller/raspi_23/pulseio/PulseIn.py
dbe6152773e9f54c1120a6bfe203aba0f78ac6a6
[Adafruit_Blinka-hackapet.git] / src / adafruit_blinka / microcontroller / raspi_23 / pulseio / PulseIn.py
1 import array
2 import time
3 import subprocess
4 import os, signal
5 import traceback
6 import signal
7 import sysv_ipc
8 import atexit
9 import random
10
11 DEBUG = False
12 queues = []
13 procs = []
14
15 # The message queues live outside of python space, and must be formally cleaned!
16 def final():
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"""
19     if DEBUG:
20         print("Cleaning up message queues", queues)
21         print("Cleaning up processes", procs)
22     for q in queues:
23         q.remove()
24     for proc in procs:
25         proc.terminate()
26 atexit.register(final)
27
28 class PulseIn:
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."""
35         self._pin = pin
36         self._maxlen = maxlen
37         self._idle_state = idle_state
38         self._queue_key = random.randint(1, 9999)
39         try:
40             self._mq = sysv_ipc.MessageQueue(None, flags=sysv_ipc.IPC_CREX)
41             if DEBUG:
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")
46
47         cmd = ["/home/pi/libgpiod_pulsein/src/libgpiod_pulsein",
48                "--pulses", str(maxlen),
49                "--queue", str(self._mq.key)]
50         if not idle_state:
51             cmd.append("-i")
52         cmd.append("gpiochip0")
53         cmd.append(str(pin))
54         if DEBUG:
55             print(cmd)
56         
57         self._process = subprocess.Popen(cmd)
58         procs.append(self._process)
59
60         # wait for it to start up
61         if DEBUG:
62             print("Waiting for startup success message from subprocess")
63         message = self._wait_receive_msg()
64         if message[0] != b'!':
65             raise RuntimeError("Could not establish message queue with subprocess")
66         self._paused = False
67
68     def _wait_receive_msg(self, timeout=0.25, type=2):
69         """Internal helper that will wait for new messages of a given type,
70         and throw an exception on timeout"""
71         stamp = time.monotonic()
72         while (time.monotonic() - stamp) < timeout:
73             try:
74                 message = self._mq.receive(block=False, type=2)
75                 return message
76             except sysv_ipc.BusyError:
77                 time.sleep(0.001) # wait a bit then retry!
78         # uh-oh timed out
79         raise RuntimeError("Timed out waiting for PulseIn message")
80
81     def deinit(self):
82         """Deinitialises the PulseIn and releases any hardware and software
83         resources for reuse."""
84         # Clean up after ourselves
85         self._process.terminate()
86         procs.remove(self._process)
87         self._mq.remove()
88         queues.remove(self._mq)
89
90     def __enter__(self):
91         """No-op used by Context Managers."""
92         return self
93
94     def __exit__(self, exc_type, exc_value, tb):
95         """Automatically deinitializes the hardware when exiting a context."""
96         self.deinit()
97
98     def resume(self, trigger_duration=0):
99         """Resumes pulse capture after an optional trigger pulse."""
100         if trigger_duration != 0:
101             self._mq.send("t%d" % trigger_duration, True, type=1)
102         else:
103             self._mq.send("r", True, type=1)
104         self._paused = False
105
106     def pause(self):
107         """Pause pulse capture"""
108         self._mq.send("p", True, type=1)
109         self._paused = True
110
111     @property
112     def paused(self):
113         """True when pulse capture is paused as a result of pause() or
114         an error during capture such as a signal that is too fast."""
115         return self._paused
116
117     @property
118     def maxlen(self):
119         """The maximum length of the PulseIn. When len() is equal to maxlen,
120         it is unclear which pulses are active and which are idle."""
121         return self._maxlen
122
123     def clear(self):
124         """Clears all captured pulses"""
125         self._mq.send("c", True, type=1)
126
127     def popleft(self):
128         """Removes and returns the oldest read pulse."""
129         self._mq.send("^", True, type=1)
130         message = self._wait_receive_msg()
131         reply = int(message[0].decode('utf-8'))
132         #print(reply)
133         if reply == -1:
134             raise IndexError("pop from empty list")
135         return reply
136
137     def __len__(self):
138         """Returns the current pulse length"""
139         self._mq.send("l", True, type=1)
140         message = self._wait_receive_msg()
141         return int(message[0].decode('utf-8'))
142
143     def __getitem__(self, index, type=None):
144         """Returns the value at the given index or values in slice."""
145         self._mq.send("i%d" % index, True, type=1)
146         message = self._wait_receive_msg()
147         ret = int(message[0].decode('utf-8'))
148         if ret == -1:
149             raise IndexError("list index out of range")
150         return ret