Top

rstem.gpio module

#!/usr/bin/env python3
#
# Copyright (c) 2014, Scott Silver Labs, LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import os
import select
import time
from threading import Thread, Lock, Event

PINS = [2, 3, 4, 14, 15, 17, 18, 22, 23, 24, 25, 27]

# Directions
OUTPUT = 1
INPUT = 2
DISABLED = 3

# Edge Detection
NONE = "none"
RISING = "rising"
FALLING = "falling"
BOTH = "both"

ARG_PULL_DISABLE = 0
ARG_PULL_DOWN = 1
ARG_PULL_UP = 2

HIGH = 1
LOW = 0


class Pin:
    def __init__(self, pin):
        self.gpio_dir = "/sys/class/gpio/gpio%d" % pin
        self.pin = pin
        self.direction = DISABLED
        self.edge = NONE
        self.last = 1
        self.mutex = Lock()
        self.poll_thread = None
        self.poll_thread_stop = None
        if pin in PINS:
            if not os.path.exists(self.gpio_dir):
                with open("/sys/class/gpio/export", "w") as f:
                    f.write("%d\n" % pin)

    def __pullup(self, pin, enable):
        here = os.path.dirname(os.path.realpath(__file__))
        os.system(here + "/pullup.sbin %d %d" % (pin, enable))

    def __enable_pullup(self, pin):
        self.__pullup(pin, ARG_PULL_UP)

    def __disable_pullup(self, pin):
        self.__pullup(pin, ARG_PULL_DISABLE)

    def __enable_pulldown(self, pin):
        self.__pullup(pin, ARG_PULL_DOWN)

    def __disable_pulldown(self, pin):
        self.__pullup(pin, ARG_PULL_DISABLE)

    def __poll_thread_run(self, callback, bouncetime):
        """Run function used in poll_thread"""
        # NOTE: self will not change once this is called
        po = select.epoll()
        po.register(self.fvalue, select.POLLIN | select.EPOLLPRI | select.EPOLLET)
        last_time = 0
        first_time = True  # used to ignore first trigger
        # TODO: ignore second trigger too if edge = rising/falling?

        while not self.poll_thread_stop.is_set():
            event = po.poll(1)
            if len(event) == 0:
                # timeout
                continue
            self.fvalue.seek(0)
            if not first_time:
                timenow = time.time()
                if (timenow - last_time) > (bouncetime/1000) or last_time == 0:
                    callback(self.pin)
                    last_time = timenow
            else:
                first_time = False

    def __set_edge(self, edge):
        with self.mutex:
            with open(self.gpio_dir + "/edge", "w") as fedge:
                self.edge = edge
                fedge.write(edge)

    def __end_thread(self):
        if self.poll_thread and self.poll_thread.isAlive():
            # self.poll_thread_running = False
            self.poll_thread_stop.set()

            while self.poll_thread.isAlive():
                self.poll_thread.join(1)

    def remove_edge_detect(self):
        """Removes edge detect interrupt"""
        self.__set_edge(NONE)
        self.__end_thread()

    def wait_for_edge(self, edge):
        """Blocks until the given edge has happened
        @param edge: Either gpio.FALLING, gpio.RISING, gpio.BOTH
        @type edge: string
        @throws: ValueError
        """
        if self.direction != INPUT:
            raise ValueError("GPIO must be configured to be an input first.")
        if edge not in [RISING, FALLING, BOTH]:
            raise ValueError("Invalid edge!")
        self.__set_edge(edge)

        # wait for edge
        po = select.epoll()
        po.register(self.fvalue, select.POLLIN | select.EPOLLPRI | select.EPOLLET)
        # last_time = 0
        first_time = True  # used to ignore first trigger

        while True:
            event = po.poll(60)
            if len(event) == 0:
                # timeout to see if edge has changed
                if self.edge == NONE:
                    break
                else:
                    continue
            self.fvalue.seek(0)
            if not first_time:
                break
            else:
                first_time = False

    def edge_detect(self, edge, callback=None, bouncetime=200):
        """Sets up edge detection interrupt.
        @param edge: either gpio.NONE, gpio.RISING, gpio.FALLING, or gpio.BOTH
        @type edge: int
        @param callback: Function to call when given edge has been detected.
        @type callback: function
        @param bouncetime: Debounce time in milliseconds.
        @type bouncetime: int
        @note: First parameter of callback function will be the pin number of gpio that called it.
        """
        if self.direction != INPUT:
            raise ValueError("GPIO must be configured to be an input first.")
        if callback is None and edge != NONE:
            raise ValueError("Callback function must be given if edge is not NONE")
        if edge not in [NONE, RISING, FALLING, BOTH]:
            raise ValueError("Edge must be NONE, RISING, FALLING, or BOTH")

        self.__set_edge(edge)

        if edge != NONE:
            self.__end_thread()  # end any previous callback functions
            self.poll_thread_stop = Event()
            self.poll_thread = Thread(target=Pin.__poll_thread_run, args=(self, callback, bouncetime))
            self.poll_thread.start()


    def configure(self, direction):
        """Configure the GPIO pin to either be an input, output or disabled.
        @param direction: Either gpio.INPUT, gpio.OUTPUT, or gpio.DISABLED
        @type direction: int
        """
        if direction not in [INPUT, OUTPUT, DISABLED]:
            raise ValueError("Direction must be INPUT, OUTPUT or DISABLED")

        with self.mutex:
            with open(self.gpio_dir + "/direction", "w") as fdirection:
                self.direction = direction
                if direction == OUTPUT:
                    # For future use
                    fdirection.write("out")
                    self.fvalue = open(self.gpio_dir + "/value", "w")
                elif direction == INPUT:
                    self.__enable_pullup(self.pin)
                    fdirection.write("in")
                    self.fvalue = open(self.gpio_dir + "/value", "r")
                elif direction == DISABLED:
                    fdirection.write("in")
                    self.fvalue.close()

    def was_clicked(self):
        # TODO: make work for any type of edge change and rename function
        """Detects whether the GPIO has been clicked or on since the pin has been initialized or
        since the last time was_clicked() has been called.
        @returns: boolean
        """
        level = self.get_level()
        clicked = level == 1 and self.last == 0
        self.last = level
        return clicked

    def get_level(self):
        """Returns the current level of the GPIO pin.
        @returns: int (1 for HIGH, 0 for LOW)
        @note: The GPIO pins are active low.
        """
        if self.direction != INPUT:
            raise ValueError("GPIO must be configured to be an INPUT!")
        with self.mutex:
            self.fvalue.seek(0)
            return int(self.fvalue.read())

    def set_level(self, level):
        """Sets the level of the GPIO port.
        @param level: Level to set. Must be either HIGH or LOW.
        @param level: int
        """
        if self.direction != OUTPUT:
            raise ValueError("GPIO must be configured to be an OUTPUT!")
        if level != 0 and level != 1:
            raise ValueError("Level must be either 1 or 0.")
        with self.mutex:
            # write value wasn't working for some reason...
            os.system("echo %s > %s/value" % (str(level), self.gpio_dir))
            # self.fvalue.seek(0)
            # self.fvalue.write(str(level))


class Pins:
    def __init__(self):
        self.gpios = [Pin(i) for i in range(max(PINS) + 1)]

    def __validate_gpio(self, pin, direction):
        class UninitializedError(Exception):
            pass

        if not pin in PINS:
            raise ValueError("Invalid GPIO")
        if self.gpios[pin].direction != direction:
            raise UninitializedError()

    def configure(self, pin, direction):
        if not pin in PINS:
            raise ValueError("Invalid GPIO")
        self.gpios[pin].configure(direction)

    def was_clicked(self):
        inputs = [g for g in self.gpios if g.direction == INPUT]
        return [g.pin for g in inputs if g.was_clicked()]

    def get_level(self, pin):
        self.__validate_gpio(pin, INPUT)
        return self.gpios[pin].get_level()

    def set_level(self, pin, level):
        self.__validate_gpio(pin, OUTPUT)
        self.gpios[pin].set_level(level)

# Export functions in this module
g = Pins()
for name in ['configure', 'get_level', 'set_level', 'was_clicked']:
    globals()[name] = getattr(g, name)

Module variables

var ARG_PULL_DISABLE

var ARG_PULL_DOWN

var ARG_PULL_UP

var BOTH

var DISABLED

var FALLING

var HIGH

var INPUT

var LOW

var NONE

var OUTPUT

var PINS

var RISING

var g

var name

Functions

def configure(

self, pin, direction)

def configure(self, pin, direction):
    if not pin in PINS:
        raise ValueError("Invalid GPIO")
    self.gpios[pin].configure(direction)

def get_level(

self, pin)

def get_level(self, pin):
    self.__validate_gpio(pin, INPUT)
    return self.gpios[pin].get_level()

def set_level(

self, pin, level)

def set_level(self, pin, level):
    self.__validate_gpio(pin, OUTPUT)
    self.gpios[pin].set_level(level)

def was_clicked(

self)

def was_clicked(self):
    inputs = [g for g in self.gpios if g.direction == INPUT]
    return [g.pin for g in inputs if g.was_clicked()]

Classes

class Pin

class Pin:
    def __init__(self, pin):
        self.gpio_dir = "/sys/class/gpio/gpio%d" % pin
        self.pin = pin
        self.direction = DISABLED
        self.edge = NONE
        self.last = 1
        self.mutex = Lock()
        self.poll_thread = None
        self.poll_thread_stop = None
        if pin in PINS:
            if not os.path.exists(self.gpio_dir):
                with open("/sys/class/gpio/export", "w") as f:
                    f.write("%d\n" % pin)

    def __pullup(self, pin, enable):
        here = os.path.dirname(os.path.realpath(__file__))
        os.system(here + "/pullup.sbin %d %d" % (pin, enable))

    def __enable_pullup(self, pin):
        self.__pullup(pin, ARG_PULL_UP)

    def __disable_pullup(self, pin):
        self.__pullup(pin, ARG_PULL_DISABLE)

    def __enable_pulldown(self, pin):
        self.__pullup(pin, ARG_PULL_DOWN)

    def __disable_pulldown(self, pin):
        self.__pullup(pin, ARG_PULL_DISABLE)

    def __poll_thread_run(self, callback, bouncetime):
        """Run function used in poll_thread"""
        # NOTE: self will not change once this is called
        po = select.epoll()
        po.register(self.fvalue, select.POLLIN | select.EPOLLPRI | select.EPOLLET)
        last_time = 0
        first_time = True  # used to ignore first trigger
        # TODO: ignore second trigger too if edge = rising/falling?

        while not self.poll_thread_stop.is_set():
            event = po.poll(1)
            if len(event) == 0:
                # timeout
                continue
            self.fvalue.seek(0)
            if not first_time:
                timenow = time.time()
                if (timenow - last_time) > (bouncetime/1000) or last_time == 0:
                    callback(self.pin)
                    last_time = timenow
            else:
                first_time = False

    def __set_edge(self, edge):
        with self.mutex:
            with open(self.gpio_dir + "/edge", "w") as fedge:
                self.edge = edge
                fedge.write(edge)

    def __end_thread(self):
        if self.poll_thread and self.poll_thread.isAlive():
            # self.poll_thread_running = False
            self.poll_thread_stop.set()

            while self.poll_thread.isAlive():
                self.poll_thread.join(1)

    def remove_edge_detect(self):
        """Removes edge detect interrupt"""
        self.__set_edge(NONE)
        self.__end_thread()

    def wait_for_edge(self, edge):
        """Blocks until the given edge has happened
        @param edge: Either gpio.FALLING, gpio.RISING, gpio.BOTH
        @type edge: string
        @throws: ValueError
        """
        if self.direction != INPUT:
            raise ValueError("GPIO must be configured to be an input first.")
        if edge not in [RISING, FALLING, BOTH]:
            raise ValueError("Invalid edge!")
        self.__set_edge(edge)

        # wait for edge
        po = select.epoll()
        po.register(self.fvalue, select.POLLIN | select.EPOLLPRI | select.EPOLLET)
        # last_time = 0
        first_time = True  # used to ignore first trigger

        while True:
            event = po.poll(60)
            if len(event) == 0:
                # timeout to see if edge has changed
                if self.edge == NONE:
                    break
                else:
                    continue
            self.fvalue.seek(0)
            if not first_time:
                break
            else:
                first_time = False

    def edge_detect(self, edge, callback=None, bouncetime=200):
        """Sets up edge detection interrupt.
        @param edge: either gpio.NONE, gpio.RISING, gpio.FALLING, or gpio.BOTH
        @type edge: int
        @param callback: Function to call when given edge has been detected.
        @type callback: function
        @param bouncetime: Debounce time in milliseconds.
        @type bouncetime: int
        @note: First parameter of callback function will be the pin number of gpio that called it.
        """
        if self.direction != INPUT:
            raise ValueError("GPIO must be configured to be an input first.")
        if callback is None and edge != NONE:
            raise ValueError("Callback function must be given if edge is not NONE")
        if edge not in [NONE, RISING, FALLING, BOTH]:
            raise ValueError("Edge must be NONE, RISING, FALLING, or BOTH")

        self.__set_edge(edge)

        if edge != NONE:
            self.__end_thread()  # end any previous callback functions
            self.poll_thread_stop = Event()
            self.poll_thread = Thread(target=Pin.__poll_thread_run, args=(self, callback, bouncetime))
            self.poll_thread.start()


    def configure(self, direction):
        """Configure the GPIO pin to either be an input, output or disabled.
        @param direction: Either gpio.INPUT, gpio.OUTPUT, or gpio.DISABLED
        @type direction: int
        """
        if direction not in [INPUT, OUTPUT, DISABLED]:
            raise ValueError("Direction must be INPUT, OUTPUT or DISABLED")

        with self.mutex:
            with open(self.gpio_dir + "/direction", "w") as fdirection:
                self.direction = direction
                if direction == OUTPUT:
                    # For future use
                    fdirection.write("out")
                    self.fvalue = open(self.gpio_dir + "/value", "w")
                elif direction == INPUT:
                    self.__enable_pullup(self.pin)
                    fdirection.write("in")
                    self.fvalue = open(self.gpio_dir + "/value", "r")
                elif direction == DISABLED:
                    fdirection.write("in")
                    self.fvalue.close()

    def was_clicked(self):
        # TODO: make work for any type of edge change and rename function
        """Detects whether the GPIO has been clicked or on since the pin has been initialized or
        since the last time was_clicked() has been called.
        @returns: boolean
        """
        level = self.get_level()
        clicked = level == 1 and self.last == 0
        self.last = level
        return clicked

    def get_level(self):
        """Returns the current level of the GPIO pin.
        @returns: int (1 for HIGH, 0 for LOW)
        @note: The GPIO pins are active low.
        """
        if self.direction != INPUT:
            raise ValueError("GPIO must be configured to be an INPUT!")
        with self.mutex:
            self.fvalue.seek(0)
            return int(self.fvalue.read())

    def set_level(self, level):
        """Sets the level of the GPIO port.
        @param level: Level to set. Must be either HIGH or LOW.
        @param level: int
        """
        if self.direction != OUTPUT:
            raise ValueError("GPIO must be configured to be an OUTPUT!")
        if level != 0 and level != 1:
            raise ValueError("Level must be either 1 or 0.")
        with self.mutex:
            # write value wasn't working for some reason...
            os.system("echo %s > %s/value" % (str(level), self.gpio_dir))

Ancestors (in MRO)

  • Pin
  • builtins.object

Static methods

def __init__(

self, pin)

def __init__(self, pin):
    self.gpio_dir = "/sys/class/gpio/gpio%d" % pin
    self.pin = pin
    self.direction = DISABLED
    self.edge = NONE
    self.last = 1
    self.mutex = Lock()
    self.poll_thread = None
    self.poll_thread_stop = None
    if pin in PINS:
        if not os.path.exists(self.gpio_dir):
            with open("/sys/class/gpio/export", "w") as f:
                f.write("%d\n" % pin)

def configure(

self, direction)

Configure the GPIO pin to either be an input, output or disabled. @param direction: Either gpio.INPUT, gpio.OUTPUT, or gpio.DISABLED @type direction: int

def configure(self, direction):
    """Configure the GPIO pin to either be an input, output or disabled.
    @param direction: Either gpio.INPUT, gpio.OUTPUT, or gpio.DISABLED
    @type direction: int
    """
    if direction not in [INPUT, OUTPUT, DISABLED]:
        raise ValueError("Direction must be INPUT, OUTPUT or DISABLED")
    with self.mutex:
        with open(self.gpio_dir + "/direction", "w") as fdirection:
            self.direction = direction
            if direction == OUTPUT:
                # For future use
                fdirection.write("out")
                self.fvalue = open(self.gpio_dir + "/value", "w")
            elif direction == INPUT:
                self.__enable_pullup(self.pin)
                fdirection.write("in")
                self.fvalue = open(self.gpio_dir + "/value", "r")
            elif direction == DISABLED:
                fdirection.write("in")
                self.fvalue.close()

def edge_detect(

self, edge, callback=None, bouncetime=200)

Sets up edge detection interrupt. @param edge: either gpio.NONE, gpio.RISING, gpio.FALLING, or gpio.BOTH @type edge: int @param callback: Function to call when given edge has been detected. @type callback: function @param bouncetime: Debounce time in milliseconds. @type bouncetime: int @note: First parameter of callback function will be the pin number of gpio that called it.

def edge_detect(self, edge, callback=None, bouncetime=200):
    """Sets up edge detection interrupt.
    @param edge: either gpio.NONE, gpio.RISING, gpio.FALLING, or gpio.BOTH
    @type edge: int
    @param callback: Function to call when given edge has been detected.
    @type callback: function
    @param bouncetime: Debounce time in milliseconds.
    @type bouncetime: int
    @note: First parameter of callback function will be the pin number of gpio that called it.
    """
    if self.direction != INPUT:
        raise ValueError("GPIO must be configured to be an input first.")
    if callback is None and edge != NONE:
        raise ValueError("Callback function must be given if edge is not NONE")
    if edge not in [NONE, RISING, FALLING, BOTH]:
        raise ValueError("Edge must be NONE, RISING, FALLING, or BOTH")
    self.__set_edge(edge)
    if edge != NONE:
        self.__end_thread()  # end any previous callback functions
        self.poll_thread_stop = Event()
        self.poll_thread = Thread(target=Pin.__poll_thread_run, args=(self, callback, bouncetime))
        self.poll_thread.start()

def get_level(

self)

Returns the current level of the GPIO pin. @returns: int (1 for HIGH, 0 for LOW) @note: The GPIO pins are active low.

def get_level(self):
    """Returns the current level of the GPIO pin.
    @returns: int (1 for HIGH, 0 for LOW)
    @note: The GPIO pins are active low.
    """
    if self.direction != INPUT:
        raise ValueError("GPIO must be configured to be an INPUT!")
    with self.mutex:
        self.fvalue.seek(0)
        return int(self.fvalue.read())

def remove_edge_detect(

self)

Removes edge detect interrupt

def remove_edge_detect(self):
    """Removes edge detect interrupt"""
    self.__set_edge(NONE)
    self.__end_thread()

def set_level(

self, level)

Sets the level of the GPIO port. @param level: Level to set. Must be either HIGH or LOW. @param level: int

def set_level(self, level):
    """Sets the level of the GPIO port.
    @param level: Level to set. Must be either HIGH or LOW.
    @param level: int
    """
    if self.direction != OUTPUT:
        raise ValueError("GPIO must be configured to be an OUTPUT!")
    if level != 0 and level != 1:
        raise ValueError("Level must be either 1 or 0.")
    with self.mutex:
        # write value wasn't working for some reason...
        os.system("echo %s > %s/value" % (str(level), self.gpio_dir))

def wait_for_edge(

self, edge)

Blocks until the given edge has happened @param edge: Either gpio.FALLING, gpio.RISING, gpio.BOTH @type edge: string @throws: ValueError

def wait_for_edge(self, edge):
    """Blocks until the given edge has happened
    @param edge: Either gpio.FALLING, gpio.RISING, gpio.BOTH
    @type edge: string
    @throws: ValueError
    """
    if self.direction != INPUT:
        raise ValueError("GPIO must be configured to be an input first.")
    if edge not in [RISING, FALLING, BOTH]:
        raise ValueError("Invalid edge!")
    self.__set_edge(edge)
    # wait for edge
    po = select.epoll()
    po.register(self.fvalue, select.POLLIN | select.EPOLLPRI | select.EPOLLET)
    # last_time = 0
    first_time = True  # used to ignore first trigger
    while True:
        event = po.poll(60)
        if len(event) == 0:
            # timeout to see if edge has changed
            if self.edge == NONE:
                break
            else:
                continue
        self.fvalue.seek(0)
        if not first_time:
            break
        else:
            first_time = False

def was_clicked(

self)

Detects whether the GPIO has been clicked or on since the pin has been initialized or since the last time was_clicked() has been called. @returns: boolean

def was_clicked(self):
    # TODO: make work for any type of edge change and rename function
    """Detects whether the GPIO has been clicked or on since the pin has been initialized or
    since the last time was_clicked() has been called.
    @returns: boolean
    """
    level = self.get_level()
    clicked = level == 1 and self.last == 0
    self.last = level
    return clicked

Instance variables

var direction

var edge

var gpio_dir

var last

var mutex

var pin

var poll_thread

var poll_thread_stop

class Pins

class Pins:
    def __init__(self):
        self.gpios = [Pin(i) for i in range(max(PINS) + 1)]

    def __validate_gpio(self, pin, direction):
        class UninitializedError(Exception):
            pass

        if not pin in PINS:
            raise ValueError("Invalid GPIO")
        if self.gpios[pin].direction != direction:
            raise UninitializedError()

    def configure(self, pin, direction):
        if not pin in PINS:
            raise ValueError("Invalid GPIO")
        self.gpios[pin].configure(direction)

    def was_clicked(self):
        inputs = [g for g in self.gpios if g.direction == INPUT]
        return [g.pin for g in inputs if g.was_clicked()]

    def get_level(self, pin):
        self.__validate_gpio(pin, INPUT)
        return self.gpios[pin].get_level()

    def set_level(self, pin, level):
        self.__validate_gpio(pin, OUTPUT)
        self.gpios[pin].set_level(level)

Ancestors (in MRO)

  • Pins
  • builtins.object

Static methods

def __init__(

self)

def __init__(self):
    self.gpios = [Pin(i) for i in range(max(PINS) + 1)]

def configure(

self, pin, direction)

def configure(self, pin, direction):
    if not pin in PINS:
        raise ValueError("Invalid GPIO")
    self.gpios[pin].configure(direction)

def get_level(

self, pin)

def get_level(self, pin):
    self.__validate_gpio(pin, INPUT)
    return self.gpios[pin].get_level()

def set_level(

self, pin, level)

def set_level(self, pin, level):
    self.__validate_gpio(pin, OUTPUT)
    self.gpios[pin].set_level(level)

def was_clicked(

self)

def was_clicked(self):
    inputs = [g for g in self.gpios if g.direction == INPUT]
    return [g.pin for g in inputs if g.was_clicked()]

Instance variables

var gpios