X-Git-Url: https://git.ayoreis.com/Adafruit_Blinka-hackapet.git/blobdiff_plain/3f017a27633d001cdb30d6e36bfce3baea3ab52d..40b64d58a1a537ccc55e1c45fe6804b495c6e055:/src/adafruit_blinka/microcontroller/bcm283x/pulseio/PulseIn.py diff --git a/src/adafruit_blinka/microcontroller/bcm283x/pulseio/PulseIn.py b/src/adafruit_blinka/microcontroller/bcm283x/pulseio/PulseIn.py new file mode 100644 index 0000000..b918c86 --- /dev/null +++ b/src/adafruit_blinka/microcontroller/bcm283x/pulseio/PulseIn.py @@ -0,0 +1,151 @@ +import array +import time +import subprocess +import os, signal +import traceback +import signal +import sysv_ipc +import atexit +import random + +DEBUG = False +queues = [] +procs = [] + +# The message queues live outside of python space, and must be formally cleaned! +def final(): + """In case the program is cancelled or quit, we need to clean up the PulseIn + helper process and also the message queue, this is called at exit to do so""" + if DEBUG: + print("Cleaning up message queues", queues) + print("Cleaning up processes", procs) + for q in queues: + q.remove() + for proc in procs: + proc.terminate() +atexit.register(final) + +class PulseIn: + def __init__(self, pin, maxlen=2, idle_state=False): + """Create a PulseIn object associated with the given pin. + The object acts as a read-only sequence of pulse lengths with + a given max length. When it is active, new pulse lengths are + added to the end of the list. When there is no more room + (len() == maxlen) the oldest pulse length is removed to make room.""" + self._pin = pin + self._maxlen = maxlen + self._idle_state = idle_state + self._queue_key = random.randint(1, 9999) + try: + self._mq = sysv_ipc.MessageQueue(None, flags=sysv_ipc.IPC_CREX) + if DEBUG: + print("Message Queue Key: ", self._mq.key) + queues.append(self._mq) + except sysv_ipc.ExistentialError: + raise RuntimeError("Message queue creation failed") + + dir_path = os.path.dirname(os.path.realpath(__file__)) + cmd = [dir_path+"/libgpiod_pulsein", + "--pulses", str(maxlen), + "--queue", str(self._mq.key)] + if not idle_state: + cmd.append("-i") + cmd.append("gpiochip0") + cmd.append(str(pin)) + if DEBUG: + print(cmd) + + self._process = subprocess.Popen(cmd) + procs.append(self._process) + + # wait for it to start up + if DEBUG: + print("Waiting for startup success message from subprocess") + message = self._wait_receive_msg() + if message[0] != b'!': + raise RuntimeError("Could not establish message queue with subprocess") + self._paused = False + + def _wait_receive_msg(self, timeout=0.25, type=2): + """Internal helper that will wait for new messages of a given type, + and throw an exception on timeout""" + stamp = time.monotonic() + while (time.monotonic() - stamp) < timeout: + try: + message = self._mq.receive(block=False, type=2) + return message + except sysv_ipc.BusyError: + time.sleep(0.001) # wait a bit then retry! + # uh-oh timed out + raise RuntimeError("Timed out waiting for PulseIn message") + + def deinit(self): + """Deinitialises the PulseIn and releases any hardware and software + resources for reuse.""" + # Clean up after ourselves + self._process.terminate() + procs.remove(self._process) + self._mq.remove() + queues.remove(self._mq) + + def __enter__(self): + """No-op used by Context Managers.""" + return self + + def __exit__(self, exc_type, exc_value, tb): + """Automatically deinitializes the hardware when exiting a context.""" + self.deinit() + + def resume(self, trigger_duration=0): + """Resumes pulse capture after an optional trigger pulse.""" + if trigger_duration != 0: + self._mq.send("t%d" % trigger_duration, True, type=1) + else: + self._mq.send("r", True, type=1) + self._paused = False + + def pause(self): + """Pause pulse capture""" + self._mq.send("p", True, type=1) + self._paused = True + + @property + def paused(self): + """True when pulse capture is paused as a result of pause() or + an error during capture such as a signal that is too fast.""" + return self._paused + + @property + def maxlen(self): + """The maximum length of the PulseIn. When len() is equal to maxlen, + it is unclear which pulses are active and which are idle.""" + return self._maxlen + + def clear(self): + """Clears all captured pulses""" + self._mq.send("c", True, type=1) + + def popleft(self): + """Removes and returns the oldest read pulse.""" + self._mq.send("^", True, type=1) + message = self._wait_receive_msg() + reply = int(message[0].decode('utf-8')) + #print(reply) + if reply == -1: + raise IndexError("pop from empty list") + return reply + + def __len__(self): + """Returns the current pulse length""" + self._mq.send("l", True, type=1) + message = self._wait_receive_msg() + return int(message[0].decode('utf-8')) + + def __getitem__(self, index, type=None): + """Returns the value at the given index or values in slice.""" + self._mq.send("i%d" % index, True, type=1) + message = self._wait_receive_msg() + ret = int(message[0].decode('utf-8')) + if ret == -1: + raise IndexError("list index out of range") + return ret