From 01ae605491dc8632ab17ba1bbad32fb3c35a1048 Mon Sep 17 00:00:00 2001 From: xtacocorex Date: Wed, 24 Feb 2016 21:48:40 -0600 Subject: [PATCH] Initial commit, working GPIO for all available CHIP GPIO pins, have not tested edge detection and callbacks --- CHANGELOG.rst | 7 + CHIP_IO/__init__.py | 0 Makefile | 19 + README.md | 2 - README.rst | 94 +++ distribute_setup.py | 556 +++++++++++++++ fix_py_compile.py | 22 + setup.py | 36 + source/common.c | 309 ++++++++ source/common.h | 59 ++ source/constants.c | 81 +++ source/constants.h | 14 + source/event_gpio.c | 660 ++++++++++++++++++ source/event_gpio.h | 74 ++ source/py_gpio.c | 554 +++++++++++++++ source/setup.py | 36 + .../test_gpio_input.cpython-27-PYTEST.pyc | Bin 0 -> 2748 bytes .../test_gpio_output.cpython-27-PYTEST.pyc | Bin 0 -> 4347 bytes .../test_gpio_setup.cpython-27-PYTEST.pyc | Bin 0 -> 10541 bytes test/gptest.py | 35 + test/test_gpio_input.py | 24 + test/test_gpio_output.py | 44 ++ test/test_gpio_setup.py | 72 ++ tox.ini | 11 + 24 files changed, 2707 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.rst create mode 100644 CHIP_IO/__init__.py create mode 100644 Makefile delete mode 100644 README.md create mode 100644 README.rst create mode 100644 distribute_setup.py create mode 100644 fix_py_compile.py create mode 100644 setup.py create mode 100644 source/common.c create mode 100644 source/common.h create mode 100644 source/constants.c create mode 100644 source/constants.h create mode 100644 source/event_gpio.c create mode 100644 source/event_gpio.h create mode 100644 source/py_gpio.c create mode 100644 source/setup.py create mode 100644 test/__pycache__/test_gpio_input.cpython-27-PYTEST.pyc create mode 100644 test/__pycache__/test_gpio_output.cpython-27-PYTEST.pyc create mode 100644 test/__pycache__/test_gpio_setup.cpython-27-PYTEST.pyc create mode 100755 test/gptest.py create mode 100755 test/test_gpio_input.py create mode 100644 test/test_gpio_output.py create mode 100644 test/test_gpio_setup.py create mode 100644 tox.ini diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..81159cd --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,7 @@ +0.0.4 +____ +* Initial Commit +* GPIO working - untested callback and edge detection +* Initial GPIO unit tests + + diff --git a/CHIP_IO/__init__.py b/CHIP_IO/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8ac7781 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +time: + /usr/bin/ntpdate -b -s -u pool.ntp.org + +publish: clean + python setup.py sdist upload + +clean: + rm -rf CHIP_IO.* build dist + rm -f *.pyo + rm -f *.egg + rm -f overlays/*.pyo overlays/*.pyc +tests: + py.test + +build: + python setup.py build --force + +install: build + python setup.py install --force diff --git a/README.md b/README.md deleted file mode 100644 index 247ce24..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# CHIP_IO -A CHIP IO library diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..6f572fa --- /dev/null +++ b/README.rst @@ -0,0 +1,94 @@ +# CHIP_IO +A CHIP GPIO library + +Manual:: + + sudo ntpdate pool.ntp.org + sudo apt-get update + sudo apt-get install build-essential python-dev python-pip -y + git clone git://github.com/xtacocorex/CHIP_IO.git + cd CHIP_IO + sudo python setup.py install + cd .. + sudo rm -rf CHIP_IO + +**Usage** + +Using the library is very similar to the excellent RPi.GPIO library used on the Raspberry Pi. Below are some examples. + +**GPIO Setup** + +Import the library, and setup as GPIO.OUT or GPIO.IN:: + + import CHIP_IO.GPIO as GPIO + GPIO.setup("CSID0", GPIO.OUT) + +You can also refer to the pin number:: + + GPIO.setup("U14_31", GPIO.OUT) + +**GPIO Output** + +Setup the pin for output, and write GPIO.HIGH or GPIO.LOW. Or you can use 1 or 0.:: + + import CHIP_IO.GPIO as GPIO + GPIO.setup("CSID0", GPIO.OUT) + GPIO.output("CSID0", GPIO.HIGH) + +**GPIO Input** + +Inputs work similarly to outputs.:: + + import CHIP_IO.GPIO as GPIO + GPIO.setup("CSID0", GPIO.IN) + +Polling inputs:: + + if GPIO.input("CSID0"): + print("HIGH") + else: + print("LOW") + +Waiting for an edge (GPIO.RISING, GPIO.FALLING, or GPIO.BOTH:: + + GPIO.wait_for_edge(channel, GPIO.RISING) + +Detecting events:: + + GPIO.add_event_detect("CSID0", GPIO.FALLING) + #your amazing code here + #detect wherever: + if GPIO.event_detected("CSID0"): + print "event detected!" + +**PWM**:: + + Not implemented yet + +**ADC**:: + + Not Implemented yet + +**Running tests** + +Install py.test to run the tests. You'll also need the python compiler package for py.test.:: + + opkg update && opkg install python-compiler + #Either pip or easy_install + pip install -U pytest + easy_install -U pytest + +Execute the following in the root of the project:: + + py.test + +**Credits** + +The CHIP IO Python library was originally forked from the Adafruit Beaglebone IO Python Library. +The BeagleBone IO Python library was originally forked from the excellent MIT Licensed [RPi.GPIO](https://code.google.com/p/raspberry-gpio-python) library written by Ben Croston. + +**License** + +CHIP IO port by Robert Wolterman, released under the MIT License. +Beaglebone IO Library Written by Justin Cooper, Adafruit Industries. BeagleBone IO Python library is released under the MIT License. + diff --git a/distribute_setup.py b/distribute_setup.py new file mode 100644 index 0000000..cffd56a --- /dev/null +++ b/distribute_setup.py @@ -0,0 +1,556 @@ +#!python +"""Bootstrap distribute installation + +If you want to use setuptools in your package's setup.py, just include this +file in the same directory with it, and add this to the top of your setup.py:: + + from distribute_setup import use_setuptools + use_setuptools() + +If you want to require a specific version of setuptools, set a download +mirror, or use an alternate download directory, you can do so by supplying +the appropriate options to ``use_setuptools()``. + +This file can also be run as a script to install or upgrade setuptools. +""" +import os +import shutil +import sys +import time +import fnmatch +import tempfile +import tarfile +import optparse + +from distutils import log + +try: + from site import USER_SITE +except ImportError: + USER_SITE = None + +try: + import subprocess + + def _python_cmd(*args): + args = (sys.executable,) + args + return subprocess.call(args) == 0 + +except ImportError: + # will be used for python 2.3 + def _python_cmd(*args): + args = (sys.executable,) + args + # quoting arguments if windows + if sys.platform == 'win32': + def quote(arg): + if ' ' in arg: + return '"%s"' % arg + return arg + args = [quote(arg) for arg in args] + return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 + +DEFAULT_VERSION = "0.0.1" +DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" +SETUPTOOLS_FAKED_VERSION = "0.6c11" + +SETUPTOOLS_PKG_INFO = """\ +Metadata-Version: 1.0 +Name: setuptools +Version: %s +Summary: xxxx +Home-page: xxx +Author: xxx +Author-email: xxx +License: xxx +Description: xxx +""" % SETUPTOOLS_FAKED_VERSION + + +def _install(tarball, install_args=()): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # installing + log.warn('Installing Distribute') + if not _python_cmd('setup.py', 'install', *install_args): + log.warn('Something went wrong during the installation.') + log.warn('See the error message above.') + # exitcode will be 2 + return 2 + finally: + os.chdir(old_wd) + shutil.rmtree(tmpdir) + + +def _build_egg(egg, tarball, to_dir): + # extracting the tarball + tmpdir = tempfile.mkdtemp() + log.warn('Extracting in %s', tmpdir) + old_wd = os.getcwd() + try: + os.chdir(tmpdir) + tar = tarfile.open(tarball) + _extractall(tar) + tar.close() + + # going in the directory + subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) + os.chdir(subdir) + log.warn('Now working in %s', subdir) + + # building an egg + log.warn('Building a Distribute egg in %s', to_dir) + _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) + + finally: + os.chdir(old_wd) + shutil.rmtree(tmpdir) + # returning the result + log.warn(egg) + if not os.path.exists(egg): + raise IOError('Could not build the egg.') + + +def _do_download(version, download_base, to_dir, download_delay): + egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' + % (version, sys.version_info[0], sys.version_info[1])) + if not os.path.exists(egg): + tarball = download_setuptools(version, download_base, + to_dir, download_delay) + _build_egg(egg, tarball, to_dir) + sys.path.insert(0, egg) + import setuptools + setuptools.bootstrap_install_from = egg + + +def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, download_delay=15, no_fake=True): + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + was_imported = 'pkg_resources' in sys.modules or \ + 'setuptools' in sys.modules + try: + try: + import pkg_resources + + # Setuptools 0.7b and later is a suitable (and preferable) + # substitute for any Distribute version. + try: + pkg_resources.require("setuptools>=0.7b") + return + except (pkg_resources.DistributionNotFound, + pkg_resources.VersionConflict): + pass + + if not hasattr(pkg_resources, '_distribute'): + if not no_fake: + _fake_setuptools() + raise ImportError + except ImportError: + return _do_download(version, download_base, to_dir, download_delay) + try: + pkg_resources.require("distribute>=" + version) + return + except pkg_resources.VersionConflict: + e = sys.exc_info()[1] + if was_imported: + sys.stderr.write( + "The required version of distribute (>=%s) is not available,\n" + "and can't be installed while this script is running. Please\n" + "install a more recent version first, using\n" + "'easy_install -U distribute'." + "\n\n(Currently using %r)\n" % (version, e.args[0])) + sys.exit(2) + else: + del pkg_resources, sys.modules['pkg_resources'] # reload ok + return _do_download(version, download_base, to_dir, + download_delay) + except pkg_resources.DistributionNotFound: + return _do_download(version, download_base, to_dir, + download_delay) + finally: + if not no_fake: + _create_fake_setuptools_pkg_info(to_dir) + + +def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, + to_dir=os.curdir, delay=15): + """Download distribute from a specified location and return its filename + + `version` should be a valid distribute version number that is available + as an egg for download under the `download_base` URL (which should end + with a '/'). `to_dir` is the directory where the egg will be downloaded. + `delay` is the number of seconds to pause before an actual download + attempt. + """ + # making sure we use the absolute path + to_dir = os.path.abspath(to_dir) + try: + from urllib.request import urlopen + except ImportError: + from urllib2 import urlopen + tgz_name = "distribute-%s.tar.gz" % version + url = download_base + tgz_name + saveto = os.path.join(to_dir, tgz_name) + src = dst = None + if not os.path.exists(saveto): # Avoid repeated downloads + try: + log.warn("Downloading %s", url) + src = urlopen(url) + # Read/write all in one block, so we don't create a corrupt file + # if the download is interrupted. + data = src.read() + dst = open(saveto, "wb") + dst.write(data) + finally: + if src: + src.close() + if dst: + dst.close() + return os.path.realpath(saveto) + + +def _no_sandbox(function): + def __no_sandbox(*args, **kw): + try: + from setuptools.sandbox import DirectorySandbox + if not hasattr(DirectorySandbox, '_old'): + def violation(*args): + pass + DirectorySandbox._old = DirectorySandbox._violation + DirectorySandbox._violation = violation + patched = True + else: + patched = False + except ImportError: + patched = False + + try: + return function(*args, **kw) + finally: + if patched: + DirectorySandbox._violation = DirectorySandbox._old + del DirectorySandbox._old + + return __no_sandbox + + +def _patch_file(path, content): + """Will backup the file then patch it""" + f = open(path) + existing_content = f.read() + f.close() + if existing_content == content: + # already patched + log.warn('Already patched.') + return False + log.warn('Patching...') + _rename_path(path) + f = open(path, 'w') + try: + f.write(content) + finally: + f.close() + return True + +_patch_file = _no_sandbox(_patch_file) + + +def _same_content(path, content): + f = open(path) + existing_content = f.read() + f.close() + return existing_content == content + + +def _rename_path(path): + new_name = path + '.OLD.%s' % time.time() + log.warn('Renaming %s to %s', path, new_name) + os.rename(path, new_name) + return new_name + + +def _remove_flat_installation(placeholder): + if not os.path.isdir(placeholder): + log.warn('Unkown installation at %s', placeholder) + return False + found = False + for file in os.listdir(placeholder): + if fnmatch.fnmatch(file, 'setuptools*.egg-info'): + found = True + break + if not found: + log.warn('Could not locate setuptools*.egg-info') + return + + log.warn('Moving elements out of the way...') + pkg_info = os.path.join(placeholder, file) + if os.path.isdir(pkg_info): + patched = _patch_egg_dir(pkg_info) + else: + patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) + + if not patched: + log.warn('%s already patched.', pkg_info) + return False + # now let's move the files out of the way + for element in ('setuptools', 'pkg_resources.py', 'site.py'): + element = os.path.join(placeholder, element) + if os.path.exists(element): + _rename_path(element) + else: + log.warn('Could not find the %s element of the ' + 'Setuptools distribution', element) + return True + +_remove_flat_installation = _no_sandbox(_remove_flat_installation) + + +def _after_install(dist): + log.warn('After install bootstrap.') + placeholder = dist.get_command_obj('install').install_purelib + _create_fake_setuptools_pkg_info(placeholder) + + +def _create_fake_setuptools_pkg_info(placeholder): + if not placeholder or not os.path.exists(placeholder): + log.warn('Could not find the install location') + return + pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) + setuptools_file = 'setuptools-%s-py%s.egg-info' % \ + (SETUPTOOLS_FAKED_VERSION, pyver) + pkg_info = os.path.join(placeholder, setuptools_file) + if os.path.exists(pkg_info): + log.warn('%s already exists', pkg_info) + return + + log.warn('Creating %s', pkg_info) + try: + f = open(pkg_info, 'w') + except EnvironmentError: + log.warn("Don't have permissions to write %s, skipping", pkg_info) + return + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + + pth_file = os.path.join(placeholder, 'setuptools.pth') + log.warn('Creating %s', pth_file) + f = open(pth_file, 'w') + try: + f.write(os.path.join(os.curdir, setuptools_file)) + finally: + f.close() + +_create_fake_setuptools_pkg_info = _no_sandbox( + _create_fake_setuptools_pkg_info +) + + +def _patch_egg_dir(path): + # let's check if it's already patched + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + if os.path.exists(pkg_info): + if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): + log.warn('%s already patched.', pkg_info) + return False + _rename_path(path) + os.mkdir(path) + os.mkdir(os.path.join(path, 'EGG-INFO')) + pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') + f = open(pkg_info, 'w') + try: + f.write(SETUPTOOLS_PKG_INFO) + finally: + f.close() + return True + +_patch_egg_dir = _no_sandbox(_patch_egg_dir) + + +def _before_install(): + log.warn('Before install bootstrap.') + _fake_setuptools() + + +def _under_prefix(location): + if 'install' not in sys.argv: + return True + args = sys.argv[sys.argv.index('install') + 1:] + for index, arg in enumerate(args): + for option in ('--root', '--prefix'): + if arg.startswith('%s=' % option): + top_dir = arg.split('root=')[-1] + return location.startswith(top_dir) + elif arg == option: + if len(args) > index: + top_dir = args[index + 1] + return location.startswith(top_dir) + if arg == '--user' and USER_SITE is not None: + return location.startswith(USER_SITE) + return True + + +def _fake_setuptools(): + log.warn('Scanning installed packages') + try: + import pkg_resources + except ImportError: + # we're cool + log.warn('Setuptools or Distribute does not seem to be installed.') + return + ws = pkg_resources.working_set + try: + setuptools_dist = ws.find( + pkg_resources.Requirement.parse('setuptools', replacement=False) + ) + except TypeError: + # old distribute API + setuptools_dist = ws.find( + pkg_resources.Requirement.parse('setuptools') + ) + + if setuptools_dist is None: + log.warn('No setuptools distribution found') + return + # detecting if it was already faked + setuptools_location = setuptools_dist.location + log.warn('Setuptools installation detected at %s', setuptools_location) + + # if --root or --preix was provided, and if + # setuptools is not located in them, we don't patch it + if not _under_prefix(setuptools_location): + log.warn('Not patching, --root or --prefix is installing Distribute' + ' in another location') + return + + # let's see if its an egg + if not setuptools_location.endswith('.egg'): + log.warn('Non-egg installation') + res = _remove_flat_installation(setuptools_location) + if not res: + return + else: + log.warn('Egg installation') + pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') + if (os.path.exists(pkg_info) and + _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): + log.warn('Already patched.') + return + log.warn('Patching...') + # let's create a fake egg replacing setuptools one + res = _patch_egg_dir(setuptools_location) + if not res: + return + log.warn('Patching complete.') + _relaunch() + + +def _relaunch(): + log.warn('Relaunching...') + # we have to relaunch the process + # pip marker to avoid a relaunch bug + _cmd1 = ['-c', 'install', '--single-version-externally-managed'] + _cmd2 = ['-c', 'install', '--record'] + if sys.argv[:3] == _cmd1 or sys.argv[:3] == _cmd2: + sys.argv[0] = 'setup.py' + args = [sys.executable] + sys.argv + sys.exit(subprocess.call(args)) + + +def _extractall(self, path=".", members=None): + """Extract all members from the archive to the current working + directory and set owner, modification time and permissions on + directories afterwards. `path' specifies a different directory + to extract to. `members' is optional and must be a subset of the + list returned by getmembers(). + """ + import copy + import operator + from tarfile import ExtractError + directories = [] + + if members is None: + members = self + + for tarinfo in members: + if tarinfo.isdir(): + # Extract directories with a safe mode. + directories.append(tarinfo) + tarinfo = copy.copy(tarinfo) + tarinfo.mode = 448 # decimal for oct 0700 + self.extract(tarinfo, path) + + # Reverse sort directories. + if sys.version_info < (2, 4): + def sorter(dir1, dir2): + return cmp(dir1.name, dir2.name) + directories.sort(sorter) + directories.reverse() + else: + directories.sort(key=operator.attrgetter('name'), reverse=True) + + # Set correct owner, mtime and filemode on directories. + for tarinfo in directories: + dirpath = os.path.join(path, tarinfo.name) + try: + self.chown(tarinfo, dirpath) + self.utime(tarinfo, dirpath) + self.chmod(tarinfo, dirpath) + except ExtractError: + e = sys.exc_info()[1] + if self.errorlevel > 1: + raise + else: + self._dbg(1, "tarfile: %s" % e) + + +def _build_install_args(options): + """ + Build the arguments to 'python setup.py install' on the distribute package + """ + install_args = [] + if options.user_install: + if sys.version_info < (2, 6): + log.warn("--user requires Python 2.6 or later") + raise SystemExit(1) + install_args.append('--user') + return install_args + +def _parse_args(): + """ + Parse the command line for options + """ + parser = optparse.OptionParser() + parser.add_option( + '--user', dest='user_install', action='store_true', default=False, + help='install in user site package (requires Python 2.6 or later)') + parser.add_option( + '--download-base', dest='download_base', metavar="URL", + default=DEFAULT_URL, + help='alternative URL from where to download the distribute package') + options, args = parser.parse_args() + # positional arguments are ignored + return options + +def main(version=DEFAULT_VERSION): + """Install or upgrade setuptools and EasyInstall""" + options = _parse_args() + tarball = download_setuptools(download_base=options.download_base) + return _install(tarball, _build_install_args(options)) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/fix_py_compile.py b/fix_py_compile.py new file mode 100644 index 0000000..d24fc05 --- /dev/null +++ b/fix_py_compile.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python2 +# Some Angstrom images are missing the py_compile module; get it if not +# present: +# Fix credit:https://github.com/alexanderhiam/PyBBIO/blob/master/setup.py +import random, os +python_lib_path = random.__file__.split('random')[0] +if not os.path.exists(python_lib_path + 'py_compile.py'): + print "py_compile module missing; installing to %spy_compile.py" %\ + python_lib_path + import urllib2 + url = "http://hg.python.org/cpython/raw-file/4ebe1ede981e/Lib/py_compile.py" + py_compile = urllib2.urlopen(url) + with open(python_lib_path+'py_compile.py', 'w') as f: + f.write(py_compile.read()) + print "testing py_compile..." + try: + import py_compile + print "py_compile installed successfully" + except Exception, e: + print "*py_compile install failed, could not import" + print "*Exception raised:" + raise e diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..1603471 --- /dev/null +++ b/setup.py @@ -0,0 +1,36 @@ +try: + from overlays import builder + builder.compile() + builder.copy() +except: + pass + +import distribute_setup +distribute_setup.use_setuptools() +from setuptools import setup, Extension, find_packages + +classifiers = ['Development Status :: 3 - Alpha', + 'Operating System :: POSIX :: Linux', + 'License :: OSI Approved :: MIT License', + 'Intended Audience :: Developers', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Topic :: Software Development', + 'Topic :: Home Automation', + 'Topic :: System :: Hardware'] + +setup(name = 'CHIP_IO', + version = '0.0.2', + author = 'Robert Wolterman', + author_email = 'robert.wolterman@gmail.com', + description = 'A module to control CHIP IO channels', + long_description = open('README.rst').read() + open('CHANGELOG.rst').read(), + license = 'MIT', + keywords = 'CHIP NextThingCo IO GPIO PWM ADC', + url = 'https://github.com/xtacocorex/CHIP_IO/', + classifiers = classifiers, + packages = find_packages(), + ext_modules = [Extension('CHIP_IO.GPIO', ['source/py_gpio.c', 'source/event_gpio.c', 'source/constants.c', 'source/common.c'], extra_compile_args=['-Wno-format-security'])]) #, +# Extension('CHIP_IO.PWM', ['source/py_pwm.c', 'source/c_pwm.c', 'source/constants.c', 'source/common.c'], extra_compile_args=['-Wno-format-security']), +# Extension('CHIP_IO.ADC', ['source/py_adc.c', 'source/c_adc.c', 'source/constants.c', 'source/common.c'], extra_compile_args=['-Wno-format-security']), +# Extension('CHIP_IO.SPI', ['source/spimodule.c', 'source/constants.c', 'source/common.c'], extra_compile_args=['-Wno-format-security'])]) diff --git a/source/common.c b/source/common.c new file mode 100644 index 0000000..2bbc19d --- /dev/null +++ b/source/common.c @@ -0,0 +1,309 @@ +/* +Copyright (c) 2016 Robert Wolterman + +Original BBIO Author Justin Cooper +Modified for CHIP_IO Author Robert Wolterman + +This file incorporates work covered by the following copyright and +permission notice, all modified code adopts the original license: + +Copyright (c) 2013 Adafruit + +Original RPi.GPIO Author Ben Croston +Modified for BBIO Author Justin Cooper + +This file incorporates work covered by the following copyright and +permission notice, all modified code adopts the original license: + +Copyright (c) 2013 Ben Croston + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "Python.h" +#include +#include +#include "common.h" + +int setup_error = 0; +int module_setup = 0; + +typedef struct pins_t { + const char *name; + const char *key; + int gpio; + int pwm_mux_mode; + int ain; +} pins_t; + +// I have no idea if this table is correct, we shall see - Robert Wolterman +pins_t table[] = { + { "GND", "U13_1", 0, -1, -1}, + { "CHG-IN", "U13_2", 0, -1, -1}, + { "VCC-5V", "U13_3", 0, -1, -1}, + { "GND", "U13_4", 0, -1, -1}, + { "VCC-3V3", "U13_5", 0, -1, -1}, + { "TS", "U13_6", 0, -1, -1}, + { "VCC-1V8", "U13_7", 0, -1, -1}, + { "BAT", "U13_8", 0, -1, -1}, + { "TWI1-SDA", "U13_9", 48, -1, -1}, + { "PWRON", "U13_10", 0, -1, -1}, + { "TWI1-SCK", "U13_11", 47, -1, -1}, + { "GND", "U13_12", 0, -1, -1}, + { "X1", "U13_13", 0, -1, -1}, + { "X2", "U13_14", 0, -1, -1}, + { "Y1", "U13_15", 0, -1, -1}, + { "Y2", "U13_16", 0, -1, -1}, + { "LCD-D2", "U13_17", 98, -1, -1}, + { "PWM0", "U13_18", 34, -1, -1}, + { "LCD-D4", "U13_19", 100, -1, -1}, + { "LCD-D3", "U13_20", 99, -1, -1}, + { "LCD-D6", "U13_21", 102, -1, -1}, + { "LCD-D5", "U13_22", 101, -1, -1}, + { "LCD-D10", "U13_23", 106, -1, -1}, + { "LCD-D7", "U13_24", 103, -1, -1}, + { "LCD-D12", "U13_25", 108, -1, -1}, + { "LCD-D11", "U13_26", 107, -1, -1}, + { "LCD-D14", "U13_27", 110, -1, -1}, + { "LCD-D13", "U13_28", 109, -1, -1}, + { "LCD-D18", "U13_29", 114, -1, -1}, + { "LCD-D15", "U13_30", 111, -1, -1}, + { "LCD-D20", "U13_31", 116, -1, -1}, + { "LCD-D19", "U13_32", 115, -1, -1}, + { "LCD-D22", "U13_33", 118, -1, -1}, + { "LCD-D21", "U13_34", 117, -1, -1}, + { "LCD-CLK", "U13_35", 120, -1, -1}, + { "LCD-D23", "U13_36", 119, -1, -1}, + { "LCD-VSYNC", "U13_37", 123, -1, -1}, + { "LCD-HSYNC", "U13_38", 122, -1, -1}, + { "GND", "U13_39", 0, -1, -1}, + { "LCD-DE", "U13_40", 121, -1, -1}, + { "GND", "U14_1", 0, -1, -1}, + { "VCC-5V", "U14_2", 0, -1, -1}, + { "UART1-TX", "U14_3", 195, -1, -1}, + { "HPL", "U14_4", 0, -1, -1}, + { "UART1-RX", "U14_5", 196, -1, -1}, + { "HPCOM", "U14_6", 0, -1, -1}, + { "FEL", "U14_7", 0, -1, -1}, + { "HPR", "U14_8", 0, -1, -1}, + { "VCC-3V3", "U14_9", 0, -1, -1}, + { "MICM", "U14_10", 0, -1, -1}, + { "LRADC", "U14_11", 0, -1, -1}, + { "MICIN1", "U14_12", 0, -1, -1}, + { "XIO-P0", "U14_13", 408, -1, -1}, + { "XIO-P1", "U14_14", 409, -1, -1}, + { "XIO-P2", "U14_15", 410, -1, -1}, + { "XIO-P3", "U14_16", 411, -1, -1}, + { "XIO-P4", "U14_17", 412, -1, -1}, + { "XIO-P5", "U14_18", 413, -1, -1}, + { "XIO-P6", "U14_19", 414, -1, -1}, + { "XIO-P7", "U14_20", 415, -1, -1}, + { "GND", "U14_21", 0, -1, -1}, + { "GND", "U14_22", 0, -1, -1}, + { "AP-EINT1", "U14_23", 193, -1, -1}, + { "AP-EINT3", "U14_24", 35, -1, -1}, + { "TWI2-SDA", "U14_25", 50, -1, -1}, + { "TWI2-SCK", "U14_26", 49, -1, -1}, + { "CSIPCK", "U14_27", 128, -1, -1}, + { "CSICK", "U14_28", 129, 4, -1}, + { "CSIHSYNC", "U14_29", 130, 1, -1}, + { "CSIVSYNC", "U14_30", 131, -1, -1}, + { "CSID0", "U14_31", 132, 1, -1}, + { "CSID1", "U14_32", 133, -1, -1}, + { "CSID2", "U14_33", 134, -1, 4}, + { "CSID3", "U14_34", 135, -1, -1}, + { "CSID4", "U14_35", 136, -1, 6}, + { "CSID5", "U14_36", 137, -1, 5}, + { "CSID6", "U14_37", 138, -1, 2}, + { "CSID7", "U14_38", 139, -1, 3}, + { "GND", "U14_39", 0, -1, 0}, + { "GND", "U14_40", 0, -1, 1}, + { NULL, NULL, 0 } +}; + +int lookup_gpio_by_key(const char *key) +{ + pins_t *p; + for (p = table; p->key != NULL; ++p) { + if (strcmp(p->key, key) == 0) { + return p->gpio; + } + } + return 0; +} + +int lookup_gpio_by_name(const char *name) +{ + pins_t *p; + for (p = table; p->name != NULL; ++p) { + if (strcmp(p->name, name) == 0) { + return p->gpio; + } + } + return 0; +} + +int lookup_ain_by_key(const char *key) +{ + pins_t *p; + for (p = table; p->key != NULL; ++p) { + if (strcmp(p->key, key) == 0) { + if (p->ain == -1) { + return -1; + } else { + return p->ain; + } + } + } + return -1; +} + +int lookup_ain_by_name(const char *name) +{ + pins_t *p; + for (p = table; p->name != NULL; ++p) { + if (strcmp(p->name, name) == 0) { + if (p->ain == -1) { + return -1; + } else { + return p->ain; + } + } + } + return -1; +} + +int copy_pwm_key_by_key(const char *input_key, char *key) +{ + pins_t *p; + for (p = table; p->key != NULL; ++p) { + if (strcmp(p->key, input_key) == 0) { + //validate it's a valid pwm pin + if (p->pwm_mux_mode == -1) + return 0; + + strncpy(key, p->key, 7); + key[7] = '\0'; + return 1; + } + } + return 0; +} + +int get_pwm_key_by_name(const char *name, char *key) +{ + pins_t *p; + for (p = table; p->name != NULL; ++p) { + if (strcmp(p->name, name) == 0) { + //validate it's a valid pwm pin + if (p->pwm_mux_mode == -1) + return 0; + + strncpy(key, p->key, 7); + key[7] = '\0'; + return 1; + } + } + return 0; +} + +int get_gpio_number(const char *key, unsigned int *gpio) +{ + *gpio = lookup_gpio_by_key(key); + + if (!*gpio) { + *gpio = lookup_gpio_by_name(key); + } + + return 0; +} + +int get_pwm_key(const char *input, char *key) +{ + if (!copy_pwm_key_by_key(input, key)) { + return get_pwm_key_by_name(input, key); + } + + return 1; +} + +int get_adc_ain(const char *key, unsigned int *ain) +{ + *ain = lookup_ain_by_key(key); + + if (*ain == -1) { + *ain = lookup_ain_by_name(key); + + if (*ain == -1) { + return 0; + } + } + + return 1; +} + +int build_path(const char *partial_path, const char *prefix, char *full_path, size_t full_path_len) +{ + DIR *dp; + struct dirent *ep; + + dp = opendir (partial_path); + if (dp != NULL) { + while ((ep = readdir (dp))) { + // Enforce that the prefix must be the first part of the file + char* found_string = strstr(ep->d_name, prefix); + + if (found_string != NULL && (ep->d_name - found_string) == 0) { + snprintf(full_path, full_path_len, "%s/%s", partial_path, ep->d_name); + (void) closedir (dp); + return 1; + } + } + (void) closedir (dp); + } else { + return 0; + } + + return 0; +} + +int get_spi_bus_path_number(unsigned int spi) +{ + char path[50]; + + build_path("/sys/devices", "ocp", ocp_dir, sizeof(ocp_dir)); + + if (spi == 0) { + snprintf(path, sizeof(path), "%s/48030000.spi/spi_master/spi1", ocp_dir); + } else { + snprintf(path, sizeof(path), "%s/481a0000.spi/spi_master/spi1", ocp_dir); + } + + DIR* dir = opendir(path); + if (dir) { + closedir(dir); + //device is using /dev/spidev1.x + return 1; + } else if (ENOENT == errno) { + //device is using /dev/spidev2.x + return 2; + } else { + return -1; + } +} diff --git a/source/common.h b/source/common.h new file mode 100644 index 0000000..b6f0755 --- /dev/null +++ b/source/common.h @@ -0,0 +1,59 @@ +/* +Copyright (c) 2016 Robert Wolterman + +Original BBIO Author Justin Cooper +Modified for CHIP_IO Author Robert Wolterman + +This file incorporates work covered by the following copyright and +permission notice, all modified code adopts the original license: + +Copyright (c) 2013 Adafruit + +Original RPi.GPIO Author Ben Croston +Modified for BBIO Author Justin Cooper + +This file incorporates work covered by the following copyright and +permission notice, all modified code adopts the original license: + +Copyright (c) 2013 Ben Croston + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#define MODE_UNKNOWN -1 +#define BOARD 10 +#define BCM 11 + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +#define FILENAME_BUFFER_SIZE 128 + +int gpio_mode; +int gpio_direction[120]; + +char ctrl_dir[35]; +char ocp_dir[25]; + +int get_gpio_number(const char *key, unsigned int *gpio); +int get_pwm_key(const char *input, char *key); +int get_adc_ain(const char *key, unsigned int *ain); +int build_path(const char *partial_path, const char *prefix, char *full_path, size_t full_path_len); +int get_spi_bus_path_number(unsigned int spi); +int setup_error; +int module_setup; diff --git a/source/constants.c b/source/constants.c new file mode 100644 index 0000000..0b3ba73 --- /dev/null +++ b/source/constants.c @@ -0,0 +1,81 @@ +/* +Copyright (c) 2016 Robert Wolterman + +Original BBIO Author Justin Cooper +Modified for CHIP_IO Author Robert Wolterman + +This file incorporates work covered by the following copyright and +permission notice, all modified code adopts the original license: + +Copyright (c) 2013 Adafruit + +Original RPi.GPIO Author Ben Croston +Modified for BBIO Author Justin Cooper + +This file incorporates work covered by the following copyright and +permission notice, all modified code adopts the original license: + +Copyright (c) 2013 Ben Croston + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "Python.h" +#include "constants.h" +#include "event_gpio.h" +#include "common.h" + +void define_constants(PyObject *module) +{ + high = Py_BuildValue("i", HIGH); + PyModule_AddObject(module, "HIGH", high); + + low = Py_BuildValue("i", LOW); + PyModule_AddObject(module, "LOW", low); + + output = Py_BuildValue("i", OUTPUT); + PyModule_AddObject(module, "OUT", output); + + input = Py_BuildValue("i", INPUT); + PyModule_AddObject(module, "IN", input); + + alt0 = Py_BuildValue("i", ALT0); + PyModule_AddObject(module, "ALT0", alt0); + + pud_off = Py_BuildValue("i", PUD_OFF); + PyModule_AddObject(module, "PUD_OFF", pud_off); + + pud_up = Py_BuildValue("i", PUD_UP); + PyModule_AddObject(module, "PUD_UP", pud_up); + + pud_down = Py_BuildValue("i", PUD_DOWN); + PyModule_AddObject(module, "PUD_DOWN", pud_down); + + rising_edge = Py_BuildValue("i", RISING_EDGE); + PyModule_AddObject(module, "RISING", rising_edge); + + falling_edge = Py_BuildValue("i", FALLING_EDGE); + PyModule_AddObject(module, "FALLING", falling_edge); + + both_edge = Py_BuildValue("i", BOTH_EDGE); + PyModule_AddObject(module, "BOTH", both_edge); + + version = Py_BuildValue("s", "0.0.4"); + PyModule_AddObject(module, "VERSION", version); +} diff --git a/source/constants.h b/source/constants.h new file mode 100644 index 0000000..e43921f --- /dev/null +++ b/source/constants.h @@ -0,0 +1,14 @@ +PyObject *high; +PyObject *low; +PyObject *input; +PyObject *output; +PyObject *alt0; +PyObject *pud_off; +PyObject *pud_up; +PyObject *pud_down; +PyObject *rising_edge; +PyObject *falling_edge; +PyObject *both_edge; +PyObject *version; + +void define_constants(PyObject *module); \ No newline at end of file diff --git a/source/event_gpio.c b/source/event_gpio.c new file mode 100644 index 0000000..51da8e1 --- /dev/null +++ b/source/event_gpio.c @@ -0,0 +1,660 @@ +/* +Copyright (c) 2016 Robert Wolterman + +Original BBIO Author Justin Cooper +Modified for CHIP_IO Author Robert Wolterman + +This file incorporates work covered by the following copyright and +permission notice, all modified code adopts the original license: + +Copyright (c) 2013 Adafruit + +Original RPi.GPIO Author Ben Croston +Modified for BBIO Author Justin Cooper + +This file incorporates work covered by the following copyright and +permission notice, all modified code adopts the original license: + +Copyright (c) 2013 Ben Croston + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include "event_gpio.h" +#include "common.h" + +const char *stredge[4] = {"none", "rising", "falling", "both"}; + +// file descriptors +struct fdx +{ + int fd; + unsigned int gpio; + int initial; + unsigned int is_evented; + struct fdx *next; +}; +struct fdx *fd_list = NULL; + +// event callbacks +struct callback +{ + unsigned int gpio; + void (*func)(unsigned int gpio); + struct callback *next; +}; +struct callback *callbacks = NULL; + +// gpio exports +struct gpio_exp +{ + unsigned int gpio; + struct gpio_exp *next; +}; +struct gpio_exp *exported_gpios = NULL; + +pthread_t threads; +int event_occurred[430] = { 0 }; +int thread_running = 0; +int epfd = -1; + +int gpio_export(unsigned int gpio) +{ + int fd, len; + char str_gpio[10]; + struct gpio_exp *new_gpio, *g; + + if ((fd = open("/sys/class/gpio/export", O_WRONLY)) < 0) + { + return -1; + } + len = snprintf(str_gpio, sizeof(str_gpio), "%d", gpio); + write(fd, str_gpio, len); + close(fd); + + // add to list + new_gpio = malloc(sizeof(struct gpio_exp)); + if (new_gpio == 0) + return -1; // out of memory + + new_gpio->gpio = gpio; + new_gpio->next = NULL; + + if (exported_gpios == NULL) + { + // create new list + exported_gpios = new_gpio; + } else { + // add to end of existing list + g = exported_gpios; + while (g->next != NULL) + g = g->next; + g->next = new_gpio; + } + return 0; +} + +void close_value_fd(unsigned int gpio) +{ + struct fdx *f = fd_list; + struct fdx *temp; + struct fdx *prev = NULL; + + while (f != NULL) + { + if (f->gpio == gpio) + { + close(f->fd); + if (prev == NULL) + fd_list = f->next; + else + prev->next = f->next; + temp = f; + f = f->next; + free(temp); + } else { + prev = f; + f = f->next; + } + } +} + +int fd_lookup(unsigned int gpio) +{ + struct fdx *f = fd_list; + while (f != NULL) + { + if (f->gpio == gpio) + return f->fd; + f = f->next; + } + return 0; +} + +int add_fd_list(unsigned int gpio, int fd) +{ + struct fdx *new_fd; + + new_fd = malloc(sizeof(struct fdx)); + if (new_fd == 0) + return -1; // out of memory + + new_fd->fd = fd; + new_fd->gpio = gpio; + new_fd->initial = 1; + new_fd->is_evented = 0; + if (fd_list == NULL) { + new_fd->next = NULL; + } else { + new_fd->next = fd_list; + } + fd_list = new_fd; + return 0; +} + +int open_value_file(unsigned int gpio) +{ + int fd; + char filename[MAX_FILENAME]; + + // create file descriptor of value file + snprintf(filename, sizeof(filename), "/sys/class/gpio/gpio%d/value", gpio); + + if ((fd = open(filename, O_RDONLY | O_NONBLOCK)) < 0) + return -1; + add_fd_list(gpio, fd); + return fd; +} + +int gpio_unexport(unsigned int gpio) +{ + int fd, len; + char str_gpio[10]; + struct gpio_exp *g, *temp, *prev_g = NULL; + + close_value_fd(gpio); + + if ((fd = open("/sys/class/gpio/unexport", O_WRONLY)) < 0) + return -1; + + len = snprintf(str_gpio, sizeof(str_gpio), "%d", gpio); + write(fd, str_gpio, len); + close(fd); + + // remove from list + g = exported_gpios; + while (g != NULL) + { + if (g->gpio == gpio) + { + if (prev_g == NULL) + exported_gpios = g->next; + else + prev_g->next = g->next; + temp = g; + g = g->next; + free(temp); + } else { + prev_g = g; + g = g->next; + } + } + return 0; +} + +int gpio_set_direction(unsigned int gpio, unsigned int in_flag) +{ + int fd; + char filename[40]; + char direction[10] = { 0 }; + + snprintf(filename, sizeof(filename), "/sys/class/gpio/gpio%d/direction", gpio); + if ((fd = open(filename, O_WRONLY)) < 0) + return -1; + + if (in_flag) { + strncpy(direction, "out", ARRAY_SIZE(direction) - 1); + } else { + strncpy(direction, "in", ARRAY_SIZE(direction) - 1); + } + + write(fd, direction, strlen(direction)); + close(fd); + return 0; +} + +int gpio_get_direction(unsigned int gpio, unsigned int *value) +{ + int fd; + char direction[4] = { 0 }; + char filename[40]; + + snprintf(filename, sizeof(filename), "/sys/class/gpio/gpio%d/direction", gpio); + if ((fd = open(filename, O_RDONLY | O_NONBLOCK)) < 0) + return -1; + + lseek(fd, 0, SEEK_SET); + read(fd, &direction, sizeof(direction) - 1); + + if (strcmp(direction, "out") == 0) { + *value = OUTPUT; + } else { + *value = INPUT; + } + + return 0; +} + +int gpio_set_value(unsigned int gpio, unsigned int value) +{ + int fd; + char filename[MAX_FILENAME]; + char vstr[10]; + + snprintf(filename, sizeof(filename), "/sys/class/gpio/gpio%d/value", gpio); + + if ((fd = open(filename, O_WRONLY)) < 0) + return -1; + + if (value) { + strncpy(vstr, "1", ARRAY_SIZE(vstr) - 1); + } else { + strncpy(vstr, "0", ARRAY_SIZE(vstr) - 1); + } + + write(fd, vstr, strlen(vstr)); + close(fd); + return 0; +} + +int gpio_get_value(unsigned int gpio, unsigned int *value) +{ + int fd = fd_lookup(gpio); + char ch; + + if (!fd) + { + if ((fd = open_value_file(gpio)) == -1) + return -1; + } + + lseek(fd, 0, SEEK_SET); + read(fd, &ch, sizeof(ch)); + + if (ch != '0') { + *value = 1; + } else { + *value = 0; + } + + return 0; +} + +int gpio_set_edge(unsigned int gpio, unsigned int edge) +{ + int fd; + char filename[40]; + + snprintf(filename, sizeof(filename), "/sys/class/gpio/gpio%d/edge", gpio); + + if ((fd = open(filename, O_WRONLY)) < 0) + return -1; + + write(fd, stredge[edge], strlen(stredge[edge]) + 1); + close(fd); + return 0; +} + +unsigned int gpio_lookup(int fd) +{ + struct fdx *f = fd_list; + while (f != NULL) + { + if (f->fd == fd) + return f->gpio; + f = f->next; + } + return 0; +} + +void exports_cleanup(void) +{ + // unexport everything + while (exported_gpios != NULL) + gpio_unexport(exported_gpios->gpio); +} + +int add_edge_callback(unsigned int gpio, void (*func)(unsigned int gpio)) +{ + struct callback *cb = callbacks; + struct callback *new_cb; + + new_cb = malloc(sizeof(struct callback)); + if (new_cb == 0) + return -1; // out of memory + + new_cb->gpio = gpio; + new_cb->func = func; + new_cb->next = NULL; + + if (callbacks == NULL) { + // start new list + callbacks = new_cb; + } else { + // add to end of list + while (cb->next != NULL) + cb = cb->next; + cb->next = new_cb; + } + return 0; +} + +void run_callbacks(unsigned int gpio) +{ + struct callback *cb = callbacks; + while (cb != NULL) + { + if (cb->gpio == gpio) + cb->func(cb->gpio); + cb = cb->next; + } +} + +void remove_callbacks(unsigned int gpio) +{ + struct callback *cb = callbacks; + struct callback *temp; + struct callback *prev = NULL; + + while (cb != NULL) + { + if (cb->gpio == gpio) + { + if (prev == NULL) + callbacks = cb->next; + else + prev->next = cb->next; + temp = cb; + cb = cb->next; + free(temp); + } else { + prev = cb; + cb = cb->next; + } + } +} + +void set_initial_false(unsigned int gpio) +{ + struct fdx *f = fd_list; + + while (f != NULL) + { + if (f->gpio == gpio) + f->initial = 0; + f = f->next; + } +} + +int gpio_initial(unsigned int gpio) +{ + struct fdx *f = fd_list; + + while (f != NULL) + { + if ((f->gpio == gpio) && f->initial) + return 1; + f = f->next; + } + return 0; +} + +void *poll_thread(void *threadarg) +{ + struct epoll_event events; + char buf; + unsigned int gpio; + int n; + + thread_running = 1; + while (thread_running) + { + if ((n = epoll_wait(epfd, &events, 1, -1)) == -1) + { + thread_running = 0; + pthread_exit(NULL); + } + if (n > 0) { + lseek(events.data.fd, 0, SEEK_SET); + if (read(events.data.fd, &buf, 1) != 1) + { + thread_running = 0; + pthread_exit(NULL); + } + gpio = gpio_lookup(events.data.fd); + if (gpio_initial(gpio)) { // ignore first epoll trigger + set_initial_false(gpio); + } else { + event_occurred[gpio] = 1; + run_callbacks(gpio); + } + } + } + thread_running = 0; + pthread_exit(NULL); +} + +int gpio_is_evented(unsigned int gpio) +{ + struct fdx *f = fd_list; + while (f != NULL) + { + if (f->gpio == gpio) + return 1; + f = f->next; + } + return 0; +} + +int gpio_event_add(unsigned int gpio) +{ + struct fdx *f = fd_list; + while (f != NULL) + { + if (f->gpio == gpio) + { + if (f->is_evented) + return 1; + + f->is_evented = 1; + return 0; + } + f = f->next; + } + return 0; +} + +int gpio_event_remove(unsigned int gpio) +{ + struct fdx *f = fd_list; + while (f != NULL) + { + if (f->gpio == gpio) + { + f->is_evented = 0; + return 0; + } + f = f->next; + } + return 0; +} + +int add_edge_detect(unsigned int gpio, unsigned int edge) +// return values: +// 0 - Success +// 1 - Edge detection already added +// 2 - Other error +{ + int fd = fd_lookup(gpio); + pthread_t threads; + struct epoll_event ev; + long t = 0; + + // check to see if this gpio has been added already + if (gpio_event_add(gpio) != 0) + return 1; + + // export /sys/class/gpio interface + gpio_export(gpio); + gpio_set_direction(gpio, 0); // 0=input + gpio_set_edge(gpio, edge); + + if (!fd) + { + if ((fd = open_value_file(gpio)) == -1) + return 2; + } + + // create epfd if not already open + if ((epfd == -1) && ((epfd = epoll_create(1)) == -1)) + return 2; + + // add to epoll fd + ev.events = EPOLLIN | EPOLLET | EPOLLPRI; + ev.data.fd = fd; + if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1) + return 2; + + // start poll thread if it is not already running + if (!thread_running) + { + if (pthread_create(&threads, NULL, poll_thread, (void *)t) != 0) + return 2; + } + + return 0; +} + +void remove_edge_detect(unsigned int gpio) +{ + struct epoll_event ev; + int fd = fd_lookup(gpio); + + // delete callbacks for gpio + remove_callbacks(gpio); + + // delete epoll of fd + epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev); + + // set edge to none + gpio_set_edge(gpio, NO_EDGE); + + // unexport gpio + gpio_event_remove(gpio); + + // clear detected flag + event_occurred[gpio] = 0; +} + +int event_detected(unsigned int gpio) +{ + if (event_occurred[gpio]) { + event_occurred[gpio] = 0; + return 1; + } else { + return 0; + } +} + +void event_cleanup(void) +{ + close(epfd); + thread_running = 0; + exports_cleanup(); +} + +int blocking_wait_for_edge(unsigned int gpio, unsigned int edge) +// standalone from all the event functions above +{ + int fd = fd_lookup(gpio); + int epfd, n, i; + struct epoll_event events, ev; + char buf; + + if ((epfd = epoll_create(1)) == -1) + return 1; + + // check to see if this gpio has been added already, if not, mark as added + if (gpio_event_add(gpio) != 0) + return 2; + + // export /sys/class/gpio interface + gpio_export(gpio); + gpio_set_direction(gpio, 0); // 0=input + gpio_set_edge(gpio, edge); + + if (!fd) + { + if ((fd = open_value_file(gpio)) == -1) + return 3; + } + + // add to epoll fd + ev.events = EPOLLIN | EPOLLET | EPOLLPRI; + ev.data.fd = fd; + if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1) + { + gpio_event_remove(gpio); + return 4; + } + + // epoll for event + for (i = 0; i<2; i++) // first time triggers with current state, so ignore + if ((n = epoll_wait(epfd, &events, 1, -1)) == -1) + { + gpio_event_remove(gpio); + return 5; + } + + if (n > 0) + { + lseek(events.data.fd, 0, SEEK_SET); + if (read(events.data.fd, &buf, sizeof(buf)) != 1) + { + gpio_event_remove(gpio); + return 6; + } + if (events.data.fd != fd) + { + gpio_event_remove(gpio); + return 7; + } + } + + gpio_event_remove(gpio); + close(epfd); + return 0; +} diff --git a/source/event_gpio.h b/source/event_gpio.h new file mode 100644 index 0000000..b58d102 --- /dev/null +++ b/source/event_gpio.h @@ -0,0 +1,74 @@ +/* +Copyright (c) 2016 Robert Wolterman + +Original BBIO Author Justin Cooper +Modified for CHIP_IO Author Robert Wolterman + +This file incorporates work covered by the following copyright and +permission notice, all modified code adopts the original license: + +Copyright (c) 2013 Adafruit + +Original RPi.GPIO Author Ben Croston +Modified for BBIO Author Justin Cooper + +This file incorporates work covered by the following copyright and +permission notice, all modified code adopts the original license: + +Copyright (c) 2013 Ben Croston + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#define NO_EDGE 0 +#define RISING_EDGE 1 +#define FALLING_EDGE 2 +#define BOTH_EDGE 3 + +#define INPUT 0 +#define OUTPUT 1 +#define ALT0 4 + +#define HIGH 1 +#define LOW 0 + +#define MAX_FILENAME 50 + +#define PUD_OFF 0 +#define PUD_DOWN 1 +#define PUD_UP 2 + +int gpio_export(unsigned int gpio); +int gpio_unexport(unsigned int gpio); +void exports_cleanup(void); +int gpio_set_direction(unsigned int gpio, unsigned int in_flag); +int gpio_get_direction(unsigned int gpio, unsigned int *value); +int gpio_set_value(unsigned int gpio, unsigned int value); +int gpio_get_value(unsigned int gpio, unsigned int *value); + +int add_edge_detect(unsigned int gpio, unsigned int edge); +void remove_edge_detect(unsigned int gpio); +int add_edge_callback(unsigned int gpio, void (*func)(unsigned int gpio)); +int event_detected(unsigned int gpio); +int gpio_event_add(unsigned int gpio); +int gpio_event_remove(unsigned int gpio); +int gpio_is_evented(unsigned int gpio); +int event_initialise(void); +void event_cleanup(void); +int blocking_wait_for_edge(unsigned int gpio, unsigned int edge); diff --git a/source/py_gpio.c b/source/py_gpio.c new file mode 100644 index 0000000..16c66ba --- /dev/null +++ b/source/py_gpio.c @@ -0,0 +1,554 @@ +/* +Copyright (c) 2016 Robert Wolterman + +Original BBIO Author Justin Cooper +Modified for CHIP_IO Author Robert Wolterman + +This file incorporates work covered by the following copyright and +permission notice, all modified code adopts the original license: + +Copyright (c) 2013 Adafruit + +Original RPi.GPIO Author Ben Croston +Modified for BBIO Author Justin Cooper + +This file incorporates work covered by the following copyright and +permission notice, all modified code adopts the original license: + +Copyright (c) 2013 Ben Croston + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "Python.h" +#include "constants.h" +#include "common.h" +#include "event_gpio.h" + +static int gpio_warnings = 1; + +struct py_callback +{ + char channel[32]; + unsigned int gpio; + PyObject *py_cb; + unsigned long long lastcall; + unsigned int bouncetime; + struct py_callback *next; +}; +static struct py_callback *py_callbacks = NULL; + +static int init_module(void) +{ + int i; + + for (i=0; i<430; i++) + gpio_direction[i] = -1; + + module_setup = 1; + + return 0; +} + +// python function cleanup() +static PyObject *py_cleanup(PyObject *self, PyObject *args) +{ + // clean up any exports + event_cleanup(); + + Py_RETURN_NONE; +} + +// python function setup(channel, direction, pull_up_down=PUD_OFF, initial=None) +static PyObject *py_setup_channel(PyObject *self, PyObject *args, PyObject *kwargs) +{ + unsigned int gpio; + char *channel; + int direction; + int pud = PUD_OFF; + int initial = 0; + static char *kwlist[] = {"channel", "direction", "pull_up_down", "initial", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "si|ii", kwlist, &channel, &direction, &pud, &initial)) + return NULL; + + if (!module_setup) { + init_module(); + } + + + if (direction != INPUT && direction != OUTPUT) + { + PyErr_SetString(PyExc_ValueError, "An invalid direction was passed to setup()"); + return NULL; + } + + if (direction == OUTPUT) + pud = PUD_OFF; + + if (pud != PUD_OFF && pud != PUD_DOWN && pud != PUD_UP) + { + PyErr_SetString(PyExc_ValueError, "Invalid value for pull_up_down - should be either PUD_OFF, PUD_UP or PUD_DOWN"); + return NULL; + } + + if (get_gpio_number(channel, &gpio)) + return NULL; + + gpio_export(gpio); + gpio_set_direction(gpio, direction); + if (gpio < 408) { + if (direction == OUTPUT) { + gpio_set_value(gpio, initial); + } else { + gpio_set_value(gpio, pud); + } + } + + gpio_direction[gpio] = direction; + + Py_RETURN_NONE; +} + +// python function output(channel, value) +static PyObject *py_output_gpio(PyObject *self, PyObject *args) +{ + unsigned int gpio; + int value; + char *channel; + + if (!PyArg_ParseTuple(args, "si", &channel, &value)) + return NULL; + + if (get_gpio_number(channel, &gpio)) + return NULL; + + if (!module_setup || gpio_direction[gpio] != OUTPUT) + { + PyErr_SetString(PyExc_RuntimeError, "The GPIO channel has not been setup() as an OUTPUT"); + return NULL; + } + + gpio_set_value(gpio, value); + + Py_RETURN_NONE; +} + +// python function value = input(channel) +static PyObject *py_input_gpio(PyObject *self, PyObject *args) +{ + unsigned int gpio; + char *channel; + unsigned int value; + PyObject *py_value; + + if (!PyArg_ParseTuple(args, "s", &channel)) + return NULL; + + if (get_gpio_number(channel, &gpio)) + return NULL; + + // check channel is set up as an input or output + if (!module_setup || (gpio_direction[gpio] != INPUT && gpio_direction[gpio] != OUTPUT)) + { + PyErr_SetString(PyExc_RuntimeError, "You must setup() the GPIO channel first"); + return NULL; + } + + gpio_get_value(gpio, &value); + + py_value = Py_BuildValue("i", value); + + return py_value; +} + +static void run_py_callbacks(unsigned int gpio) +{ + PyObject *result; + PyGILState_STATE gstate; + struct py_callback *cb = py_callbacks; + struct timeval tv_timenow; + unsigned long long timenow; + + while (cb != NULL) + { + if (cb->gpio == gpio) + { + gettimeofday(&tv_timenow, NULL); + timenow = tv_timenow.tv_sec*1E6 + tv_timenow.tv_usec; + if (cb->bouncetime == 0 || timenow - cb->lastcall > cb->bouncetime*1000 || cb->lastcall == 0 || cb->lastcall > timenow) { + + // save lastcall before calling func to prevent reentrant bounce + cb->lastcall = timenow; + + // run callback + gstate = PyGILState_Ensure(); + result = PyObject_CallFunction(cb->py_cb, "s", cb->channel); + + if (result == NULL && PyErr_Occurred()) + { + PyErr_Print(); + PyErr_Clear(); + } + Py_XDECREF(result); + PyGILState_Release(gstate); + } + cb->lastcall = timenow; + } + cb = cb->next; + } +} + +static int add_py_callback(char *channel, unsigned int gpio, unsigned int bouncetime, PyObject *cb_func) +{ + struct py_callback *new_py_cb; + struct py_callback *cb = py_callbacks; + + // add callback to py_callbacks list + new_py_cb = malloc(sizeof(struct py_callback)); + if (new_py_cb == 0) + { + PyErr_NoMemory(); + return -1; + } + new_py_cb->py_cb = cb_func; + Py_XINCREF(cb_func); // Add a reference to new callback + memset(new_py_cb->channel, 0, sizeof(new_py_cb->channel)); + strncpy(new_py_cb->channel, channel, sizeof(new_py_cb->channel) - 1); + new_py_cb->gpio = gpio; + new_py_cb->lastcall = 0; + new_py_cb->bouncetime = bouncetime; + new_py_cb->next = NULL; + if (py_callbacks == NULL) { + py_callbacks = new_py_cb; + } else { + // add to end of list + while (cb->next != NULL) + cb = cb->next; + cb->next = new_py_cb; + } + add_edge_callback(gpio, run_py_callbacks); + return 0; +} + +// python function add_event_callback(gpio, callback, bouncetime=0) +static PyObject *py_add_event_callback(PyObject *self, PyObject *args, PyObject *kwargs) +{ + unsigned int gpio; + char *channel; + unsigned int bouncetime = 0; + PyObject *cb_func; + char *kwlist[] = {"gpio", "callback", "bouncetime", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sO|i", kwlist, &channel, &cb_func, &bouncetime)) + return NULL; + + if (!PyCallable_Check(cb_func)) + { + PyErr_SetString(PyExc_TypeError, "Parameter must be callable"); + return NULL; + } + + if (get_gpio_number(channel, &gpio)) + return NULL; + + // check channel is set up as an input + if (!module_setup || gpio_direction[gpio] != INPUT) + { + PyErr_SetString(PyExc_RuntimeError, "You must setup() the GPIO channel as an input first"); + return NULL; + } + + if (!gpio_is_evented(gpio)) + { + PyErr_SetString(PyExc_RuntimeError, "Add event detection using add_event_detect first before adding a callback"); + return NULL; + } + + if (add_py_callback(channel, gpio, bouncetime, cb_func) != 0) + return NULL; + + Py_RETURN_NONE; +} + +// python function add_event_detect(gpio, edge, callback=None, bouncetime=0 +static PyObject *py_add_event_detect(PyObject *self, PyObject *args, PyObject *kwargs) +{ + unsigned int gpio; + char *channel; + int edge, result; + unsigned int bouncetime = 0; + PyObject *cb_func = NULL; + char *kwlist[] = {"gpio", "edge", "callback", "bouncetime", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "si|Oi", kwlist, &channel, &edge, &cb_func, &bouncetime)) + return NULL; + + if (cb_func != NULL && !PyCallable_Check(cb_func)) + { + PyErr_SetString(PyExc_TypeError, "Parameter must be callable"); + return NULL; + } + + if (get_gpio_number(channel, &gpio)) + return NULL; + + // check channel is set up as an input + if (!module_setup || gpio_direction[gpio] != INPUT) + { + PyErr_SetString(PyExc_RuntimeError, "You must setup() the GPIO channel as an input first"); + return NULL; + } + + // is edge valid value + if (edge != RISING_EDGE && edge != FALLING_EDGE && edge != BOTH_EDGE) + { + PyErr_SetString(PyExc_ValueError, "The edge must be set to RISING, FALLING or BOTH"); + return NULL; + } + + if ((result = add_edge_detect(gpio, edge)) != 0) // starts a thread + { + if (result == 1) + { + PyErr_SetString(PyExc_RuntimeError, "Edge detection already enabled for this GPIO channel"); + return NULL; + } else { + PyErr_SetString(PyExc_RuntimeError, "Failed to add edge detection"); + return NULL; + } + } + + if (cb_func != NULL) + if (add_py_callback(channel, gpio, bouncetime, cb_func) != 0) + return NULL; + + Py_RETURN_NONE; +} + +// python function remove_event_detect(gpio) +static PyObject *py_remove_event_detect(PyObject *self, PyObject *args) +{ + unsigned int gpio; + char *channel; + struct py_callback *cb = py_callbacks; + struct py_callback *temp; + struct py_callback *prev = NULL; + + if (!PyArg_ParseTuple(args, "s", &channel)) + return NULL; + + if (get_gpio_number(channel, &gpio)) + return NULL; + + // remove all python callbacks for gpio + while (cb != NULL) + { + if (cb->gpio == gpio) + { + Py_XDECREF(cb->py_cb); + if (prev == NULL) + py_callbacks = cb->next; + else + prev->next = cb->next; + temp = cb; + cb = cb->next; + free(temp); + } else { + prev = cb; + cb = cb->next; + } + } + + remove_edge_detect(gpio); + + Py_RETURN_NONE; +} + +// python function value = event_detected(channel) +static PyObject *py_event_detected(PyObject *self, PyObject *args) +{ + unsigned int gpio; + char *channel; + + if (!PyArg_ParseTuple(args, "s", &channel)) + return NULL; + + if (get_gpio_number(channel, &gpio)) + return NULL; + + if (event_detected(gpio)) + Py_RETURN_TRUE; + else + Py_RETURN_FALSE; +} + +// python function py_wait_for_edge(gpio, edge) +static PyObject *py_wait_for_edge(PyObject *self, PyObject *args) +{ + unsigned int gpio; + int edge, result; + char *channel; + char error[30]; + + if (!PyArg_ParseTuple(args, "si", &channel, &edge)) + return NULL; + + if (get_gpio_number(channel, &gpio)) + return NULL; + + // check channel is setup as an input + if (!module_setup || gpio_direction[gpio] != INPUT) + { + PyErr_SetString(PyExc_RuntimeError, "You must setup() the GPIO channel as an input first"); + return NULL; + } + + // is edge a valid value? + if (edge != RISING_EDGE && edge != FALLING_EDGE && edge != BOTH_EDGE) + { + PyErr_SetString(PyExc_ValueError, "The edge must be set to RISING, FALLING or BOTH"); + return NULL; + } + + Py_BEGIN_ALLOW_THREADS // disable GIL + result = blocking_wait_for_edge(gpio, edge); + Py_END_ALLOW_THREADS // enable GIL + + if (result == 0) { + Py_INCREF(Py_None); + return Py_None; + } else if (result == 2) { + PyErr_SetString(PyExc_RuntimeError, "Edge detection events already enabled for this GPIO channel"); + return NULL; + } else { + sprintf(error, "Error #%d waiting for edge", result); + PyErr_SetString(PyExc_RuntimeError, error); + return NULL; + } + + Py_RETURN_NONE; +} + +// python function value = gpio_function(gpio) +static PyObject *py_gpio_function(PyObject *self, PyObject *args) +{ + unsigned int gpio; + unsigned int value; + PyObject *func; + char *channel; + + + if (!PyArg_ParseTuple(args, "s", &channel)) + return NULL; + + if (get_gpio_number(channel, &gpio)) + return NULL; + + if (setup_error) + { + PyErr_SetString(PyExc_RuntimeError, "Module not imported correctly!"); + return NULL; + } + + gpio_get_direction(gpio, &value); + func = Py_BuildValue("i", value); + return func; +} + +// python function setwarnings(state) +static PyObject *py_setwarnings(PyObject *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, "i", &gpio_warnings)) + return NULL; + + if (setup_error) + { + PyErr_SetString(PyExc_RuntimeError, "Module not imported correctly!"); + return NULL; + } + + Py_RETURN_NONE; +} + +static const char moduledocstring[] = "GPIO functionality of a CHIP using Python"; + +PyMethodDef gpio_methods[] = { + {"setup", (PyCFunction)py_setup_channel, METH_VARARGS | METH_KEYWORDS, "Set up the GPIO channel, direction and (optional) pull/up down control\nchannel - Either: RPi board pin number (not BCM GPIO 00..nn number). Pins start from 1\n or : BCM GPIO number\ndirection - INPUT or OUTPUT\n[pull_up_down] - PUD_OFF (default), PUD_UP or PUD_DOWN\n[initial] - Initial value for an output channel"}, + {"cleanup", py_cleanup, METH_VARARGS, "Clean up by resetting all GPIO channels that have been used by this program to INPUT with no pullup/pulldown and no event detection"}, + {"output", py_output_gpio, METH_VARARGS, "Output to a GPIO channel\ngpio - gpio channel\nvalue - 0/1 or False/True or LOW/HIGH"}, + {"input", py_input_gpio, METH_VARARGS, "Input from a GPIO channel. Returns HIGH=1=True or LOW=0=False\ngpio - gpio channel"}, + {"add_event_detect", (PyCFunction)py_add_event_detect, METH_VARARGS | METH_KEYWORDS, "Enable edge detection events for a particular GPIO channel.\nchannel - either board pin number or BCM number depending on which mode is set.\nedge - RISING, FALLING or BOTH\n[callback] - A callback function for the event (optional)\n[bouncetime] - Switch bounce timeout in ms for callback"}, + {"remove_event_detect", py_remove_event_detect, METH_VARARGS, "Remove edge detection for a particular GPIO channel\ngpio - gpio channel"}, + {"event_detected", py_event_detected, METH_VARARGS, "Returns True if an edge has occured on a given GPIO. You need to enable edge detection using add_event_detect() first.\ngpio - gpio channel"}, + {"add_event_callback", (PyCFunction)py_add_event_callback, METH_VARARGS | METH_KEYWORDS, "Add a callback for an event already defined using add_event_detect()\ngpio - gpio channel\ncallback - a callback function\n[bouncetime] - Switch bounce timeout in ms"}, + {"wait_for_edge", py_wait_for_edge, METH_VARARGS, "Wait for an edge.\ngpio - gpio channel\nedge - RISING, FALLING or BOTH"}, + {"gpio_function", py_gpio_function, METH_VARARGS, "Return the current GPIO function (IN, OUT, ALT0)\ngpio - gpio channel"}, + {"setwarnings", py_setwarnings, METH_VARARGS, "Enable or disable warning messages"}, + {NULL, NULL, 0, NULL} +}; + +#if PY_MAJOR_VERSION > 2 +static struct PyModuleDef rpigpiomodule = { + PyModuleDef_HEAD_INIT, + "GPIO", // name of module + moduledocstring, // module documentation, may be NULL + -1, // size of per-interpreter state of the module, or -1 if the module keeps state in global variables. + gpio_methods +}; +#endif + +#if PY_MAJOR_VERSION > 2 +PyMODINIT_FUNC PyInit_GPIO(void) +#else +PyMODINIT_FUNC initGPIO(void) +#endif +{ + PyObject *module = NULL; + +#if PY_MAJOR_VERSION > 2 + if ((module = PyModule_Create(&rpigpiomodule)) == NULL) + return NULL; +#else + if ((module = Py_InitModule3("GPIO", gpio_methods, moduledocstring)) == NULL) + return; +#endif + + define_constants(module); + + if (!PyEval_ThreadsInitialized()) + PyEval_InitThreads(); + + if (Py_AtExit(event_cleanup) != 0) + { + setup_error = 1; + event_cleanup(); +#if PY_MAJOR_VERSION > 2 + return NULL; +#else + return; +#endif + } + +#if PY_MAJOR_VERSION > 2 + return module; +#else + return; +#endif +} diff --git a/source/setup.py b/source/setup.py new file mode 100644 index 0000000..f95ef40 --- /dev/null +++ b/source/setup.py @@ -0,0 +1,36 @@ +try: + from overlays import builder + builder.compile() + builder.copy() +except: + pass + +import distribute_setup +distribute_setup.use_setuptools() +from setuptools import setup, Extension, find_packages + +classifiers = ['Development Status :: 3 - Alpha', + 'Operating System :: POSIX :: Linux', + 'License :: OSI Approved :: MIT License', + 'Intended Audience :: Developers', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Topic :: Software Development', + 'Topic :: Home Automation', + 'Topic :: System :: Hardware'] + +setup(name = 'CHIP_IO', + version = '0.0.4', + author = 'Robert Wolterman', + author_email = 'robert.wolterman@gmail.com', + description = 'A module to control CHIP IO channels', + long_description = open('README.md').read() + open('CHANGELOG.rst').read(), + license = 'MIT', + keywords = 'CHIP NextThingCo IO GPIO PWM ADC', + url = 'https://github.com/xtacocorex/CHIP_IO/', + classifiers = classifiers, + packages = find_packages(), + ext_modules = [Extension('CHIP_IO.GPIO', ['source/py_gpio.c', 'source/event_gpio.c', 'source/constants.c', 'source/common.c'], extra_compile_args=['-Wno-format-security'])]) #, +# Extension('CHIP_IO.PWM', ['source/py_pwm.c', 'source/c_pwm.c', 'source/constants.c', 'source/common.c'], extra_compile_args=['-Wno-format-security']), +# Extension('CHIP_IO.ADC', ['source/py_adc.c', 'source/c_adc.c', 'source/constants.c', 'source/common.c'], extra_compile_args=['-Wno-format-security']), +# Extension('CHIP_IO.SPI', ['source/spimodule.c', 'source/constants.c', 'source/common.c'], extra_compile_args=['-Wno-format-security'])]) diff --git a/test/__pycache__/test_gpio_input.cpython-27-PYTEST.pyc b/test/__pycache__/test_gpio_input.cpython-27-PYTEST.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d9c133af7d24130186b51ff9d9580324884f715 GIT binary patch literal 2748 zcmb_e%W@l45bcp<*^=e>5pcpgR1r#MVZ|>c3rqz91=={NF;ImzQ)A6I9(g=7%)KHQ ztfC;SS@8$?2mXK+#Rrhz-~-^Co{CaB|IqA<$^o9(Z(r-#okssl0 zUV^Fw@K9s(p~x5g7FXJD*Rg8!7e0Ff2kPX1hY?+Zup&c;77k+DZ*zRFjrhgZAU?j= zyK85m4C2sFMwyOV`aDU4Q5?3Ri3+m?t#g!~gq^`rly$N^9fkvII(I(l-Sc{PJ2o^{ zg?B%TQZGufkzLPD?IP@K=;uNDAn^*XT+lEEaqI%*r!ZwCJ=zS|&ksZW0Dto`L^w$) zvr*yJsZ_6W4E-oJr)v3Ja+Qjv!htOpI5L`EDLW1z+iLm$pCN8^fM^Jm&K zuF4sHjvQBHT$8h!n9m&KYKSvhV)GD3yj;#Xgh@>zc$*QcX9sV`bum8+P3GwqtK^Tg z*(#e!ai(?Y2D9hQek;w2Q1Zv6S<$2!ie^r_^xa|z@EtVZrg`4YiUvH-U2yd`wFv`l z(hUl1Ub?GeRMv__j+{Zc#E!b)Wa$2qoN4H=|G|+&OT32MASHH5hRYHx(zhmd1g z#yRZS8J@;|@58Qn4O+*Xn$95hjp?vNRBUYB>U`zLqfqN%ch}s&#Ov+sbZgBtzb%z( zyVt1QSTk)tH`dHrb9`;L=-pW}whE&tvHHY3U01!9Jsah7%g*ael$rPR_zudV#0#Pe zbF1s!EIntZH>1}vuHY!+Ec9myR#AC=l{T~GG6ap>gm#o!;-gojP_d+VO|vjjB@g|; zWwF~P#BJaV81nqwE~DuU{5baVFv|z&N#^IFZ55NegHaUQC@~80IK@0;uhPOCrK31d z8}ELc9{91B_$MK1nS=geNN<*e^1dc0N|TTBJk2db_YTwi#J67fbr$=HZ!xGr?xjhn zijClk#bDwv%v>V7O|iyHRh6)D=h(B7W5i#59!+P9tqV=<$ePR%>x zG0c%Tmn(~v6{jUF(9C05maEcq7MuaM!=spT&z1kiJxWTT9KaF4Ce0}2n1+MAfRF-( zHfd&|oKw;afD821WjIT~k!HSQD#1)8(0|fq<0+skGj#*N`f0OYN;8x{`Qg{ntZ0%S za8J#GX5bsnlom-ls{lV}f_wp`BoGuL=YecMrAjd8`2wWP3y9ajo~yOM3J2of!J~xt z1k>9S@IxuVzqw}Cdwb2XR%8(6xO-4$E~}_O4mlt@-7Jy5?eb!|h_&t%(cOQ7`Txc7 zFRt5MQiE~1OLqxE?>D^=4@XH6@(Ub$kz$qN3I*%SeTjmXo%;&Ks}Op39K-oeS-o&jB0^SHUqo1CWLv9QcD{?#Zk`04dI*(@>l|Qk=B;vSEpD zPkF`jl()R{g~0RNw=m|eLp+I5wP$_raJeFpY^c@u`JhBB5PJ3}Nij*TR&r|uduvOwtjmsNPdAEE)TIU;1Q5`H*jqf?_RTKMe W@ox1V*+`4CB`eZ$@C9o%s(%B#oj$+- literal 0 HcmV?d00001 diff --git a/test/__pycache__/test_gpio_output.cpython-27-PYTEST.pyc b/test/__pycache__/test_gpio_output.cpython-27-PYTEST.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bacee6ef22e681f15dfcd38499a98fb9241535ec GIT binary patch literal 4347 zcmcInO>Z1U5Utr=?|S_aI|(G=Qzi&$4aghE#^HdYfI*27vcn`0@MSdF9ow^6&#Y#8 z2^O{xBsUQJ2u?`ckPs3#B#!(NBrb?^c(1y5?M-5mfMa{g{V~;3UHx8Fb@8w9kmR42Ps?sdT zZLBaN&7w4+Rbuy+$j57=T>VO|j4#D^@UznJ(Cj@+wBiVa5ovkU@Lqz>ez|FG(uUHS2=pl~uoJ7A4=}1L<>nHUPx6MaARKO>!hp-+S1-Vs#QD|Xh zJ5;hmSQnF?w9546F6Jt-1J8Q0TacZCfsM`iQ~@#>AT6tdge z9dq5p9&K3`niUyP`@yN%?_|F?HQS{c@G>k5TziZ*VC`mA7EbII#f(-vM6l&M70@%xX{@Su6N_MiIZG)+i4?g z=Wv?N5w}5@nN#Qn`DWT}H-jwdWWh!|y&krMB;1NjRVTysh%*Y=H+A1}n!J-`X=aY| zh4nPs3QZ7w+G&SLXfUbftfWcg6U#N?PKWZSz3y{SGYUZqtsl?!`*yAQ!yw=!zr$Tx zrv;nw#wItPAA=C@IDU_LV^Wr{<&>1Y22CXv`YahgJeG_Bnm}a!_rH*3$D!VlmS3IPgUM3bM;4HMkSryzg>jw?y}kbF@L`@_&w ztoS@MpMcq;hfjq2;}jb0$)opHulYQ4Ur9tB_&jN!81^G-oyHrE&svxdoZ7gQvI^z6^T`YG*V%UwwC6(9k$HOe>AtxlLl`v`InH?-2Sv^dRm1_SrpUHw^qYX3`?Cg%== zCS0W1dX%RFwfAEA|@#wHkLnX-E(jp*D#GQ z@58Oe@&6USdBk_cZ~s}09R~Wi&i-0r1K2ZB7MRU22~g*7z7N64d+Q1&7-pm;zIP77 zLeS_LetQ~t(+d(Ii3T7?2%@D$w|5Pp?EQ}*uILqJuVg(}({hiQ05=ZQIzX667REW+ znj*W2iMJvx*0ip)Zhjtb*`!Cq)M<~yOX+&hiIV_{KhRS4MSLu$h2l-3`ciTZc;;P9 z-H)8=9F)W$lR69hMXIp;MN)EC7p31l&eGst)0ECHYovtVMV43m84lDbSf@Nz->|UQ zk05mj%HJSPaABnP^I-^PeEO!w4lYFq+FFVSffnw8yGaIt&&!Jc0tFL^PjdRR6mt+< zZ=d+jw^*N~qVIB|sq9rN_$}cXvR~k+rhuS#*MMcLx(w^8z=CqdW-X-8nJl`Q#l~JN z`lE<}0Ii*|g&NOA&nUGpc9-oLUY0G4i+DqgJFK0muivou^z#Iz?s!a&$(T1*m?%_> F{{W-|TkHS; literal 0 HcmV?d00001 diff --git a/test/__pycache__/test_gpio_setup.cpython-27-PYTEST.pyc b/test/__pycache__/test_gpio_setup.cpython-27-PYTEST.pyc new file mode 100644 index 0000000000000000000000000000000000000000..664b0730562d34d6153906a7edce09d3d53b21c6 GIT binary patch literal 10541 zcmeHN-E!N;6+WOSQlw~0mVc78Y2*H+rP@Z+pJls^nxs~y)s3V=aqQ{_1Cx*inj#P^ zSVvQFrc<}qeUM&tCU1~jCNGeS-t-ar0L}NE1t?ikoHU_3jY>*9++6^>XU{IaJ^P&< z``3l)FBU&}_wN;vrevQn{Jx2=2_Rbdccde-;!(d7wh$ub?!&4UMHv{B`VAkQjNW6KWH@f!nhG9Q9Ed+ zrg7&t>s#LXW+M$us=|91hmmK3v=`6C2k8`^O9MY?MUT6lt(El~>L5-E^T|7Sbes1# zS;i05{1v|D0R)U`O-M@=*li|Y4{MtuZNrk>JhbSzmcX1PO-x9utO;0jLKCoPMH7?a zZr7`5M(xK0)l>U@2sNzz&?XZFc~XGRI);5m4jpNaOCKr~q(3Hy1vwm(Cyw-sa#$4e zsSa9EUI?MtxWKIKGDqT3Nz9(4I>e|848ka$?n;OCPfKhk*TNqdtV=^@HW(+Jk%_DrdjrP zibIw64`xE8)6^d$cU3~Pi^L2ID#toh;eGgjszXqiV|D3h(qF@XZs@GOyRhsn-83(t zpkWS7quKF|X>h`-SXjD+N3&^%D?yT8f3+ST+?qAjKV*sPt6H?27a@@qE#^f?WJU9{ zW-j*Ay=s3hct13$srKuvXJyvRR{JU6fyiLcMdE|`?DtmoyO{khXTK}vd${j&)NZuG z1g~Qlbq(L^sFzmt4O?9`Gsv?|=yHA4D!(*q^u-%Ilr^!Wka4WzH*llX?uJ4`R;Nv$ zUGW)J)On|iucYG;3JqHA=G}K(qK{$`?WBWbEtS@A;m!EKd(aCzY1lP-trIo^ON*NWIeMN^*Svt3F5?i*ohwa9k1)}2ij53_`3nGV2X>pr6UVly`3acl3wQ1 zyHT?5r(W=W-0{0U4XH|PMBP9WQ5@KkB=B45B|ZX8I@(+_+K>GtaEV$9CHjO3I=guH zY$vQu7f$4tPULT#$ln~~t$MfJ3#e~UXe(VDQUg|vx z4mc3zhY;dib}CW!c0K;3C-Px!HYQ)DH8 zxmPIop1NG_TrP1ganXH=;#(Borg)j+I~3og7+{wB3bWTKxX!rWr}zN{UvJyJSDE?| z#g8F|fGbq>G_DY~*~JmCj9fQNgAG@`hG#f!-#AR6PZf5*z(xCSLW;Vg>l^0+REUJf zKqc&Ez6#NB1Sum(QAnBPWEerpV2g{L-mtxtpTflF0#Huuek`ESNukU1H4x=vIA5eH zz;)#O5||{GfWRp@4z`ixmn_j#Qlbew&1s_PpX3F>Fe%Yg5)gXriJWM9Pp|<7hlIMY z?MVKpSzv%>NxXpMA+dy;CUc^R#FEWhqKU+k&0C@g#8Q^GMAKwOG}W~73txh00^3v5 zZP0X1G+j80X!?NsAebETOO|N5dWvY8e!9QDK9Vy_Nvv7)Y(O-DwmRysC7Lc}MANmL zXu6ycO@j_Si)flTBASRN)>~2bBJP18=*1qX&k@!f(P_iL8r}22++ue`#2O`tw(hQZ zcel<3?9g91<;P@mikq+ud0zesyvqoAKwm$x6!$qp9(RsYW8~Qk)w4MZZAdE-ZPqsL zosWGJIij=ih6~th|1sjwKnzCQ;gkP3?dzQHSQ@;zi|QF5(t(ig7lsRK2)DsYF&{ZC zcx97kMh)F8iiRrb%)!^u_@K=WGirzs+F+lkJ`$RHD#(^c4T}gHGET_YGC9?66j!qg zg)#4fvb$Ia@x;iYzI*w+r!&~c$$q8EzA#rz9sv73^B4p1j{Hj zbWqNU2~`K)N;!)YHQ6D483|OZUC+@2{`qL3iwvutUm$1MzG;c@LBCxA%1h zj^V+~|0ZR4@9lMWO;1RuYiQ$znoRuAAe^O#4-Vo$wjr1%gFMbiD4p?3YS z6STY(O?v@jkUz(DQ-Tm@8h_8lBq6P%)^Kkk<=%qOKvdf6a1cSTN}KA z8&29_w1oU0E+IMz?yqmYwzcrNe(du6?f)Ut|6DI5?h?_0m^=g^Jh7+6aSOlO0u=HH z&Kd#W%dZ>iZ@>YZ_i4bBm~pYm2!8QF1EfV}GT}cxBu+M2AV2X)izI0;Ty#%9yQZ*Q+_Ze&ayOToOD^ z!qlE<8I@zbz*_QwS literal 0 HcmV?d00001 diff --git a/test/gptest.py b/test/gptest.py new file mode 100755 index 0000000..23a8157 --- /dev/null +++ b/test/gptest.py @@ -0,0 +1,35 @@ +#!/usr/bin/python + +import CHIP_IO.GPIO as GPIO +import time, os + +print "SETUP CSIDO" +GPIO.setup("CSID0", GPIO.OUT) + +#print os.path.exists('/sys/class/gpio/gpio132') + +print "SETUP XIO-P1" +GPIO.setup("XIO-P1", GPIO.IN) +#GPIO.setup("U14_13", GPIO.IN) + +print "READING XIO-P1" +GPIO.output("CSID0", GPIO.HIGH) +print "HIGH", GPIO.input("XIO-P1") + +GPIO.output("CSID0", GPIO.LOW) +GPIO.output("CSID0", GPIO.LOW) +time.sleep(1) +print "LOW", GPIO.input("XIO-P1") + +GPIO.output("CSID0", GPIO.HIGH) +GPIO.output("CSID0", GPIO.HIGH) +print "HIGH", GPIO.input("XIO-P1") +time.sleep(1) + +GPIO.output("CSID0", GPIO.LOW) +GPIO.output("CSID0", GPIO.LOW) +print "LOW", GPIO.input("XIO-P1") + +print "CLEANUP" +GPIO.cleanup() + diff --git a/test/test_gpio_input.py b/test/test_gpio_input.py new file mode 100755 index 0000000..9ffd688 --- /dev/null +++ b/test/test_gpio_input.py @@ -0,0 +1,24 @@ +import pytest +import os +import time + +import CHIP_IO.GPIO as GPIO + +def teardown_module(module): + GPIO.cleanup() + +class TestGPIOInput: + def test_input(self): + GPIO.setup("CSID6", GPIO.IN) + #returned as an int type + input_value = GPIO.input("CSID6") + #value read from the file will have a \n new line + value = open('/sys/class/gpio/gpio138/value').read() + assert int(value) == input_value + time.sleep(30) + GPIO.cleanup() + + def test_direction_readback(self): + GPIO.setup("CSID6", GPIO.IN) + direction = GPIO.gpio_function("CSID6") + assert direction == GPIO.IN diff --git a/test/test_gpio_output.py b/test/test_gpio_output.py new file mode 100644 index 0000000..06de607 --- /dev/null +++ b/test/test_gpio_output.py @@ -0,0 +1,44 @@ +import pytest +import os + +import CHIP_IO.GPIO as GPIO + +def teardown_module(module): + GPIO.cleanup() + +class TestGPIOOutput: + def test_output_high(self): + GPIO.setup("CSID6", GPIO.OUT) + GPIO.output("CSID6", GPIO.HIGH) + value = open('/sys/class/gpio/gpio138/value').read() + assert int(value) + GPIO.cleanup() + + def test_output_low(self): + GPIO.setup("CSID6", GPIO.OUT) + GPIO.output("CSID6", GPIO.LOW) + value = open('/sys/class/gpio/gpio138/value').read() + assert not int(value) + GPIO.cleanup() + + def test_direction_readback(self): + GPIO.setup("CSID6", GPIO.OUT) + direction = GPIO.gpio_function("CSID6") + assert direction == GPIO.OUT + def test_output_greater_than_one(self): + GPIO.setup("CSID6", GPIO.OUT) + GPIO.output("CSID6", 2) + value = open('/sys/class/gpio/gpio138/value').read() + assert int(value) + GPIO.cleanup() + + def test_output_of_pin_not_setup(self): + with pytest.raises(RuntimeError): + GPIO.output("CSID7", GPIO.LOW) + GPIO.cleanup() + + def test_output_setup_as_input(self): + GPIO.setup("CSID6", GPIO.IN) + with pytest.raises(RuntimeError): + GPIO.output("CSID6", GPIO.LOW) + GPIO.cleanup() diff --git a/test/test_gpio_setup.py b/test/test_gpio_setup.py new file mode 100644 index 0000000..a6bd848 --- /dev/null +++ b/test/test_gpio_setup.py @@ -0,0 +1,72 @@ +import pytest +import os + +import CHIP_IO.GPIO as GPIO + +def teardown_module(module): + GPIO.cleanup() + +class TestSetup: + def test_setup_output_key(self): + GPIO.setup("U14_37", GPIO.OUT) + assert os.path.exists('/sys/class/gpio/gpio138') + direction = open('/sys/class/gpio/gpio138/direction').read() + assert direction == 'out\n' + GPIO.cleanup() + + def test_setup_output_name(self): + GPIO.setup("CSID6", GPIO.OUT) + assert os.path.exists('/sys/class/gpio/gpio138') + direction = open('/sys/class/gpio/gpio138/direction').read() + assert direction == 'out\n' + GPIO.cleanup() + + def test_setup_input_key(self): + GPIO.setup("U14_37", GPIO.IN) + assert os.path.exists('/sys/class/gpio/gpio138') + direction = open('/sys/class/gpio/gpio138/direction').read() + assert direction == 'in\n' + GPIO.cleanup() + + def test_setup_input_name(self): + GPIO.setup("CSID6", GPIO.IN) + assert os.path.exists('/sys/class/gpio/gpio138') + direction = open('/sys/class/gpio/gpio138/direction').read() + assert direction == 'in\n' + GPIO.cleanup() + + def test_setup_input_pull_up(self): + GPIO.setup("U14_37", GPIO.IN, pull_up_down=GPIO.PUD_UP) + assert os.path.exists('/sys/class/gpio/gpio138') + direction = open('/sys/class/gpio/gpio138/direction').read() + assert direction == 'in\n' + GPIO.cleanup() + + def test_setup_input_pull_down(self): + GPIO.setup("U14_37", GPIO.IN, pull_up_down=GPIO.PUD_DOWN) + assert os.path.exists('/sys/class/gpio/gpio138') + direction = open('/sys/class/gpio/gpio138/direction').read() + assert direction == 'in\n' + GPIO.cleanup() + + def test_setup_cleanup(self): + GPIO.setup("U14_37", GPIO.OUT) + assert os.path.exists('/sys/class/gpio/gpio138') + GPIO.cleanup() + assert not os.path.exists('/sys/class/gpio/gpio138') + + def test_setup_failed_type_error(self): + with pytest.raises(TypeError): + GPIO.setup("U14_37", "WEIRD") + GPIO.cleanup() + + def test_setup_failed_value_error(self): + with pytest.raises(ValueError): + GPIO.setup("U14_37", 3) + GPIO.cleanup() + + def test_setup_expanded_gpio(self): + GPIO.setup("XIO-P1", GPIO.OUT) + assert os.path.exists('/sys/class/gpio/gpio409') + GPIO.cleanup() + assert not os.path.exists('/sys/class/gpio/gpio409') diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..51ae1a2 --- /dev/null +++ b/tox.ini @@ -0,0 +1,11 @@ +# Tox (http://tox.testrun.org/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +envlist = py27, py34 + +[testenv] +commands = +deps =