From 9212e6c6ec549646ebd622c802bcf0c46b20d268 Mon Sep 17 00:00:00 2001 From: David Planella Date: Fri, 24 Nov 2017 12:02:45 +0100 Subject: [PATCH 01/16] Encoder: do kernel check, PEP8 cleanup - Added a check for kernels >= 4.4 - Cleaned up formatting for the module to be PEP8-compliant --- Adafruit_BBIO/Encoder.py | 373 +++++++++++++++++++-------------------- 1 file changed, 183 insertions(+), 190 deletions(-) diff --git a/Adafruit_BBIO/Encoder.py b/Adafruit_BBIO/Encoder.py index e978c90..4c65c8a 100644 --- a/Adafruit_BBIO/Encoder.py +++ b/Adafruit_BBIO/Encoder.py @@ -4,6 +4,13 @@ import os import logging import itertools +import platform + +if not platform.release().startswith('4.4'): + raise ImportError( + 'The Encoder module requires Linux kernel version >= 4.4.x.\n' + 'Please upgrade your kernel to use this module.\n' + 'Your Linux kernel version is {}.'.format(platform.release())) eQEP0 = 0 eQEP1 = 1 @@ -60,193 +67,179 @@ def __init__(self, channel, pin_A, pin_B, sys_path): class RotaryEncoder(object): - def _run_cmd(self, cmd): - '''Runs a command. If not successful (i.e. error code different than zero), - print the stderr output as a warning. - ''' - - try: - output = check_output(cmd, stderr=STDOUT) - self._logger.info("_run_cmd(): cmd='{}' return code={} output={}".format( - " ".join(cmd), 0, output)) - except CalledProcessError as e: - self._logger.warning( - "_run_cmd(): cmd='{}' return code={} output={}".format( - " ".join(cmd), e.returncode, e.output)) - - def config_pin(self, pin): - ''' - config_pin() - Config pin for QEP - ''' - - self._run_cmd(["config-pin", pin, "qep"]) - - def cat_file(self, path): - ''' - cat_file() - Print contents of file - ''' - - self._run_cmd(["cat", path]) - - def __init__(self, eqep_num): - ''' - RotaryEncoder(eqep_num) - Creates an instance of the class RotaryEncoder. - eqep_num determines which eQEP pins are set up. - eqep_num can be: EQEP0, EQEP1, EQEP2 or EQEP2b based on which pins \ - the rotary encoder is connected to. - ''' - - self._logger = logging.getLogger(__name__) - self._logger.addHandler(logging.NullHandler()) - - # Configure eqep module - self._eqep = eQEP.fromdict(_eQEP_DEFS[eqep_num]) - self._logger.info( - "Configuring: {}, pin A: {}, pin B: {}, sys path: {}".format( - self._eqep.channel, self._eqep.pin_A, self._eqep.pin_B, - self._eqep.sys_path)) - - self.config_pin(self._eqep.pin_A) - self.config_pin(self._eqep.pin_B) - - self.base_dir = self._eqep.sys_path - self._logger.debug( - "RotaryEncoder(): self.base_dir: {0}".format(self.base_dir)) - - self.enable() - - def enable(self): - ''' - enable() - Turns the eQEP hardware ON - ''' - enable_file = "%s/enabled" % self.base_dir - self._logger.debug("enable(): enable_file: {0}".format(enable_file)) - self._logger.warning( - "enable(): TODO: not implemented, write 1 to {}".format(enable_file)) - #return sysfs.kernelFileIO(enable_file, '1') - - def disable(self): - ''' - disable() - Turns the eQEP hardware OFF - ''' - enable_file = "%s/enabled" % self.base_dir - self._logger.debug("disable(): enable_file: {0}".format(enable_file)) - self._logger.warning( - "disable(): TODO: not implemented, write 0 to {}".format(enable_file)) - #return sysfs.kernelFileIO(enable_file, '0') - - def setAbsolute(self): - ''' - setAbsolute() - Set mode as Absolute - The position starts at zero and is incremented or - decremented by the encoder's movement - ''' - mode_file = "%s/mode" % self.base_dir - self._logger.debug("setAbsolute(): mode_file: {0}".format(mode_file)) - self._logger.warning( - "setAbsolute(): TODO: not implemented, write 0 to {}".format(mode_file)) - #return sysfs.kernelFileIO(mode_file, '0') - - def setRelative(self): - ''' - setRelative() - Set mode as Relative - The position is reset when the unit timer overflows. - ''' - mode_file = "%s/mode" % self.base_dir - self._logger.debug("setRelative(): mode_file: {0}".format(mode_file)) - self._logger.warning( - "setRelative(): TODO: not implemented, write 1 to {}".format(mode_file)) - #return sysfs.kernelFileIO(mode_file, '1') - - def getMode(self): - ''' - getMode() - Returns the mode the eQEP hardware is in. - ''' - mode_file = "%s/mode" % self.base_dir - self._logger.debug("getMode(): mode_file: {0}".format(mode_file)) - self._logger.warning("getMode(): TODO: read mode_file") - #return sysfs.kernelFileIO(mode_file) - - def getPosition(self): - ''' - getPosition() - Get the current position of the encoder. - In absolute mode, this attribute represents the current position - of the encoder. - In relative mode, this attribute represents the position of the - encoder at the last unit timer overflow. - ''' - self._logger.debug("Channel: {}".format(self._eqep.channel)) - position_file = "%s/position" % self.base_dir - self._logger.debug("getPosition(): position_file: {0}".format(position_file)) - position_handle = open(position_file, 'r') - self._logger.debug("getPosition(): position_handle: {0}".format(position_handle)) - position = position_handle.read() - self._logger.debug("getPosition(): position: {0}".format(position)) - #return sysfs.kernelFileIO(position_file) - - return position - - def setFrequency(self, freq): - ''' - setFrequency(freq) - Set the frequency in Hz at which the driver reports new positions. - ''' - period_file = "%s/period" % self.base_dir - self._logger.debug("setFrequency(): period_file: {0}".format(period_file)) - self._logger.debug("setFrequency(): freq: {0}".format(freq)) - self._logger.debug("setFrequency(): 1000000000/freq: {0}".format(1000000000/freq)) - self._logger.debug("setFrequency(): str(1000000000/freq)): {0}".format(str(1000000000/freq))) - self._logger.warning( - "setFrequency(): TODO: not implemented, set {} to {}".format( - period_file, str(1000000000/freq))) - #return sysfs.kernelFileIO(period_file, str(1000000000/freq)) - - def setPosition(self, val): - ''' - setPosition(value) - Give a new value to the current position - ''' - position_file = "%s/position" % self.base_dir - self._logger.warning( - "setPosition(): TODO: not implemented, write position to {}".format( - position_file)) - #return sysfs.kernelFileIO(position_file, str(val)) - - def zero(self): - ''' - zero()s - Set the current position to 0 - ''' - return self.setPosition(0) - - -#""" -# encoder_test.py -# Rekha Seethamraju -# An example to demonstrate the use of the eQEP library -# for PyBBIO. -# This example program is in the public domain. -#""" -#from bbio import * -#from bbio.libraries.RotaryEncoder import RotaryEncoder -# -#encoder = RotaryEncoder(RotaryEncoder.EQEP2b) -# -#def setup(): -# encoder.setAbsolute() -# encoder.zero() -# -#def loop(): -# print("encoder position : "+encoder.getPosition()) -# delay(1000) -# -#run(setup, loop) + def _run_cmd(self, cmd): + '''Runs a command. If not successful (i.e. error code different than + zero), print the stderr output as a warning. + ''' + + try: + output = check_output(cmd, stderr=STDOUT) + self._logger.info( + "_run_cmd(): cmd='{}' return code={} output={}".format( + " ".join(cmd), 0, output)) + except CalledProcessError as e: + self._logger.warning( + "_run_cmd(): cmd='{}' return code={} output={}".format( + " ".join(cmd), e.returncode, e.output)) + + def config_pin(self, pin): + ''' + config_pin() + Config pin for QEP + ''' + + self._run_cmd(["config-pin", pin, "qep"]) + + def cat_file(self, path): + ''' + cat_file() + Print contents of file + ''' + + self._run_cmd(["cat", path]) + + def __init__(self, eqep_num): + ''' + RotaryEncoder(eqep_num) + Creates an instance of the class RotaryEncoder. + eqep_num determines which eQEP pins are set up. + eqep_num can be: EQEP0, EQEP1, EQEP2 or EQEP2b based on which pins \ + the rotary encoder is connected to. + ''' + + self._logger = logging.getLogger(__name__) + self._logger.addHandler(logging.NullHandler()) + + # Configure eqep module + self._eqep = eQEP.fromdict(_eQEP_DEFS[eqep_num]) + self._logger.info( + "Configuring: {}, pin A: {}, pin B: {}, sys path: {}".format( + self._eqep.channel, self._eqep.pin_A, self._eqep.pin_B, + self._eqep.sys_path)) + + self.config_pin(self._eqep.pin_A) + self.config_pin(self._eqep.pin_B) + + self.base_dir = self._eqep.sys_path + self._logger.debug( + "RotaryEncoder(): self.base_dir: {0}".format(self.base_dir)) + + self.enable() + + def enable(self): + ''' + enable() + Turns the eQEP hardware ON + ''' + enable_file = "%s/enabled" % self.base_dir + self._logger.debug("enable(): enable_file: {0}".format(enable_file)) + self._logger.warning( + "enable(): TODO: not implemented, write 1 to {}".format(enable_file)) + # return sysfs.kernelFileIO(enable_file, '1') + + def disable(self): + ''' + disable() + Turns the eQEP hardware OFF + ''' + enable_file = "%s/enabled" % self.base_dir + self._logger.debug("disable(): enable_file: {0}".format(enable_file)) + self._logger.warning( + "disable(): TODO: not implemented, write 0 to {}".format( + enable_file)) + # return sysfs.kernelFileIO(enable_file, '0') + + def setAbsolute(self): + ''' + setAbsolute() + Set mode as Absolute + The position starts at zero and is incremented or + decremented by the encoder's movement + ''' + mode_file = "%s/mode" % self.base_dir + self._logger.debug("setAbsolute(): mode_file: {0}".format(mode_file)) + self._logger.warning( + "setAbsolute(): TODO: not implemented, write 0 to {}".format( + mode_file)) + # return sysfs.kernelFileIO(mode_file, '0') + + def setRelative(self): + ''' + setRelative() + Set mode as Relative + The position is reset when the unit timer overflows. + ''' + mode_file = "%s/mode" % self.base_dir + self._logger.debug("setRelative(): mode_file: {0}".format(mode_file)) + self._logger.warning( + "setRelative(): TODO: not implemented, write 1 to {}".format( + mode_file)) + # return sysfs.kernelFileIO(mode_file, '1') + + def getMode(self): + ''' + getMode() + Returns the mode the eQEP hardware is in. + ''' + mode_file = "%s/mode" % self.base_dir + self._logger.debug("getMode(): mode_file: {0}".format(mode_file)) + self._logger.warning("getMode(): TODO: read mode_file") + # return sysfs.kernelFileIO(mode_file) + + def getPosition(self): + ''' + getPosition() + Get the current position of the encoder. + In absolute mode, this attribute represents the current position + of the encoder. + In relative mode, this attribute represents the position of the + encoder at the last unit timer overflow. + ''' + self._logger.debug("Channel: {}".format(self._eqep.channel)) + position_file = "%s/position" % self.base_dir + self._logger.debug( + "getPosition(): position_file: {0}".format(position_file)) + position_handle = open(position_file, 'r') + self._logger.debug( + "getPosition(): position_handle: {0}".format(position_handle)) + position = position_handle.read() + self._logger.debug("getPosition(): position: {0}".format(position)) + # return sysfs.kernelFileIO(position_file) + + return position + + def setFrequency(self, freq): + ''' + setFrequency(freq) + Set the frequency in Hz at which the driver reports new positions. + ''' + period_file = "%s/period" % self.base_dir + self._logger.debug( + "setFrequency(): period_file: {0}".format(period_file)) + self._logger.debug("setFrequency(): freq: {0}".format(freq)) + self._logger.debug( + "setFrequency(): 1000000000/freq: {0}".format(1000000000/freq)) + self._logger.debug("setFrequency(): str(1000000000/freq)): {0}".format( + str(1000000000/freq))) + self._logger.warning( + "setFrequency(): TODO: not implemented, set {} to {}".format( + period_file, str(1000000000/freq))) + # return sysfs.kernelFileIO(period_file, str(1000000000/freq)) + + def setPosition(self, val): + ''' + setPosition(value) + Give a new value to the current position + ''' + position_file = "%s/position" % self.base_dir + self._logger.warning( + "setPosition(): TODO: not implemented, write position to {}".format( + position_file)) + # return sysfs.kernelFileIO(position_file, str(val)) + + def zero(self): + ''' + zero()s + Set the current position to 0 + ''' + return self.setPosition(0) From dc546d82b7307ff675ede73e6d7bb535e57113df Mon Sep 17 00:00:00 2001 From: David Planella Date: Fri, 24 Nov 2017 14:30:00 +0100 Subject: [PATCH 02/16] Encoder: added sysfs module --- Adafruit_BBIO/sysfs.py | 116 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 Adafruit_BBIO/sysfs.py diff --git a/Adafruit_BBIO/sysfs.py b/Adafruit_BBIO/sysfs.py new file mode 100644 index 0000000..a79cfb9 --- /dev/null +++ b/Adafruit_BBIO/sysfs.py @@ -0,0 +1,116 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright (c) 2014 MIT OpenCourseWare +# +# 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. +# +# Code originally published at http://stackoverflow.com/questions/4648792/ and +# subsequently forked at https://github.com/ponycloud/python-sysfs +# +# Original author: Benedikt Reinartz +# Contributors: +# - Jan Dvořák +# - Jonathon Reinhart https://github.com/JonathonReinhart +# - Ondřej Koch +# - David Planella + +""" +Simplistic Python SysFS interface. + +Usage examples:: + from sysfs import sys + + # Print all block devices in /sys, with their sizes + for block_dev in sys.block: + print block_dev, str(int(block_dev.size) / 1048576) + ' M' + + >>> import sysfs + >>> # Read/write Beaglebone Black's eQEP module attributes + >>> eqep0 = sysfs.Node("/sys/devices/platform/ocp/48300000.epwmss/48300180.eqep") + >>> # Read eqep attributes + >>> eqep0.enabled + '1' + >>> eqep0.mode + '0' + >>> eqep0.period + '1000000000' + >>> eqep0.position + '0' + >>> # Write eqep attributes. They should be strings. + >>> eqep0.position = str(2) + >>> eqep0.position + '2' +""" + +from os import listdir +from os.path import isdir, isfile, join, realpath, basename + +__all__ = ['sys', 'Node'] + + +class Node(object): + __slots__ = ['_path_', '__dict__'] + + def __init__(self, path='/sys'): + self._path_ = realpath(path) + if not self._path_.startswith('/sys/') and not '/sys' == self._path_: + raise RuntimeError('Using this on non-sysfs files is dangerous!') + + self.__dict__.update(dict.fromkeys(listdir(self._path_))) + + def __repr__(self): + return '' % self._path_ + + def __str__(self): + return basename(self._path_) + + def __setattr__(self, name, val): + if name.startswith('_'): + return object.__setattr__(self, name, val) + + path = realpath(join(self._path_, name)) + if isfile(path): + with open(path, 'w') as fp: + fp.write(val) + else: + raise RuntimeError('Cannot write to non-files.') + + def __getattribute__(self, name): + if name.startswith('_'): + return object.__getattribute__(self, name) + + path = realpath(join(self._path_, name)) + if isfile(path): + with open(path, 'r') as fp: + return fp.read().strip() + elif isdir(path): + return Node(path) + + def __setitem__(self, name, val): + return setattr(self, name, val) + + def __getitem__(self, name): + return getattr(self, name) + + def __iter__(self): + return iter(getattr(self, name) for name in listdir(self._path_)) + + +sys = Node() From 2f2c107ed1cbffdd45df78ef73ac4f64aa265228 Mon Sep 17 00:00:00 2001 From: David Planella Date: Fri, 24 Nov 2017 14:32:21 +0100 Subject: [PATCH 03/16] Encoder: use sysfs to write QEP attributes --- Adafruit_BBIO/Encoder.py | 201 +++++++++++++++++++-------------------- 1 file changed, 97 insertions(+), 104 deletions(-) diff --git a/Adafruit_BBIO/Encoder.py b/Adafruit_BBIO/Encoder.py index 4c65c8a..35e3efb 100644 --- a/Adafruit_BBIO/Encoder.py +++ b/Adafruit_BBIO/Encoder.py @@ -4,6 +4,7 @@ import os import logging import itertools +import sysfs import platform if not platform.release().startswith('4.4'): @@ -57,12 +58,15 @@ def __init__(self, channel, pin_A, pin_B, sys_path): rotary encoder sys_path (str): sys filesystem path to access the attributes of this eQEP module + node (str): sys filesystem device node that contains the + readable or writable attributes to control the QEP channel ''' self.channel = channel self.pin_A = pin_A self.pin_B = pin_B self.sys_path = sys_path + self.node = sysfs.Node(sys_path) class RotaryEncoder(object): @@ -90,156 +94,145 @@ def config_pin(self, pin): self._run_cmd(["config-pin", pin, "qep"]) - def cat_file(self, path): - ''' - cat_file() - Print contents of file - ''' - - self._run_cmd(["cat", path]) - def __init__(self, eqep_num): - ''' - RotaryEncoder(eqep_num) - Creates an instance of the class RotaryEncoder. - eqep_num determines which eQEP pins are set up. - eqep_num can be: EQEP0, EQEP1, EQEP2 or EQEP2b based on which pins \ - the rotary encoder is connected to. - ''' + '''Creates an instance of the class RotaryEncoder. + Arguments: + eqep_num: determines which eQEP pins are set up. + Allowed values: EQEP0, EQEP1, EQEP2 or EQEP2b, + based on which pins the physical rotary encoder + is connected to. + + ''' + # Set up logging at the module level self._logger = logging.getLogger(__name__) self._logger.addHandler(logging.NullHandler()) - # Configure eqep module + # Initialize the eQEP channel structures self._eqep = eQEP.fromdict(_eQEP_DEFS[eqep_num]) self._logger.info( "Configuring: {}, pin A: {}, pin B: {}, sys path: {}".format( self._eqep.channel, self._eqep.pin_A, self._eqep.pin_B, self._eqep.sys_path)) + # Configure the pins for the given channel self.config_pin(self._eqep.pin_A) self.config_pin(self._eqep.pin_B) - self.base_dir = self._eqep.sys_path self._logger.debug( - "RotaryEncoder(): self.base_dir: {0}".format(self.base_dir)) + "RotaryEncoder(): sys node: {0}".format(self._eqep.sys_path)) + # Enable the channel upon initialization self.enable() - def enable(self): - ''' - enable() - Turns the eQEP hardware ON + def _setEnable(self, value): + '''Turns the eQEP hardware ON or OFF + + value (int): 1 represents enabled, 0 is disabled ''' - enable_file = "%s/enabled" % self.base_dir - self._logger.debug("enable(): enable_file: {0}".format(enable_file)) - self._logger.warning( - "enable(): TODO: not implemented, write 1 to {}".format(enable_file)) - # return sysfs.kernelFileIO(enable_file, '1') + if value < 0 or value > 1: + raise ValueError( + 'The "enabled" attribute can only be set to 0 or 1. ' + 'You attempted to set it to {}.'.format(value)) + + self._eqep.node.enabled = str(int(value)) + self._logger.info("Channel: {}, enabled: {}".format( + self._eqep.channel, self._eqep.node.enabled)) + + def enable(self): + '''Turns the eQEP hardware ON''' + + self._setEnable(1) def disable(self): + '''Turns the eQEP hardware OFF''' + + self._setEnable(0) + + def _setMode(self, value): + '''Sets the eQEP mode as absolute (0) or relative (1). + See the setAbsolute() and setRelative() methods for + more information. + ''' - disable() - Turns the eQEP hardware OFF - ''' - enable_file = "%s/enabled" % self.base_dir - self._logger.debug("disable(): enable_file: {0}".format(enable_file)) - self._logger.warning( - "disable(): TODO: not implemented, write 0 to {}".format( - enable_file)) - # return sysfs.kernelFileIO(enable_file, '0') + if value < 0 or value > 1: + raise ValueError( + 'The "mode" attribute can only be set to 0 or 1. ' + 'You attempted to set it to {}.'.format(value)) + + self._eqep.node.mode = str(int(value)) + self._logger.debug("Mode set to: {}".format( + self._eqep.node.mode)) def setAbsolute(self): - ''' - setAbsolute() - Set mode as Absolute + '''Sets the eQEP mode as Absolute: The position starts at zero and is incremented or decremented by the encoder's movement + ''' - mode_file = "%s/mode" % self.base_dir - self._logger.debug("setAbsolute(): mode_file: {0}".format(mode_file)) - self._logger.warning( - "setAbsolute(): TODO: not implemented, write 0 to {}".format( - mode_file)) - # return sysfs.kernelFileIO(mode_file, '0') + self._setMode(0) def setRelative(self): - ''' - setRelative() - Set mode as Relative + '''Sets the eQEP mode as Relative: The position is reset when the unit timer overflows. + ''' - mode_file = "%s/mode" % self.base_dir - self._logger.debug("setRelative(): mode_file: {0}".format(mode_file)) - self._logger.warning( - "setRelative(): TODO: not implemented, write 1 to {}".format( - mode_file)) - # return sysfs.kernelFileIO(mode_file, '1') + self._setMode(1) def getMode(self): + '''Returns the mode the eQEP hardware is in (absolute or relative). + ''' - getMode() - Returns the mode the eQEP hardware is in. - ''' - mode_file = "%s/mode" % self.base_dir - self._logger.debug("getMode(): mode_file: {0}".format(mode_file)) - self._logger.warning("getMode(): TODO: read mode_file") - # return sysfs.kernelFileIO(mode_file) + + mode = int(self._eqep.node.mode) + + if mode == 0: + mode_name = "absolute" + elif mode == 1: + mode_name = "relative" + else: + mode_name = "invalid" + + self._logger.debug("getMode(): Channel {}, mode: {} ({})".format( + self._eqep.channel, mode, mode_name)) + + return mode def getPosition(self): - ''' - getPosition() - Get the current position of the encoder. + '''Returns the current position of the encoder. In absolute mode, this attribute represents the current position of the encoder. In relative mode, this attribute represents the position of the encoder at the last unit timer overflow. + ''' - self._logger.debug("Channel: {}".format(self._eqep.channel)) - position_file = "%s/position" % self.base_dir - self._logger.debug( - "getPosition(): position_file: {0}".format(position_file)) - position_handle = open(position_file, 'r') - self._logger.debug( - "getPosition(): position_handle: {0}".format(position_handle)) - position = position_handle.read() - self._logger.debug("getPosition(): position: {0}".format(position)) - # return sysfs.kernelFileIO(position_file) + position = self._eqep.node.position + + self._logger.debug("getPosition(): Channel {}, position: {}".format( + self._eqep.channel, position)) - return position + return int(position) def setFrequency(self, freq): + '''Sets the frequency in Hz at which the driver reports + new positions. + ''' - setFrequency(freq) - Set the frequency in Hz at which the driver reports new positions. - ''' - period_file = "%s/period" % self.base_dir - self._logger.debug( - "setFrequency(): period_file: {0}".format(period_file)) - self._logger.debug("setFrequency(): freq: {0}".format(freq)) + ns_factor = 1000000000 + period = ns_factor/freq # Period in nanoseconds + self._eqep.node.period = str(period) self._logger.debug( - "setFrequency(): 1000000000/freq: {0}".format(1000000000/freq)) - self._logger.debug("setFrequency(): str(1000000000/freq)): {0}".format( - str(1000000000/freq))) - self._logger.warning( - "setFrequency(): TODO: not implemented, set {} to {}".format( - period_file, str(1000000000/freq))) - # return sysfs.kernelFileIO(period_file, str(1000000000/freq)) - - def setPosition(self, val): - ''' - setPosition(value) - Give a new value to the current position - ''' - position_file = "%s/position" % self.base_dir - self._logger.warning( - "setPosition(): TODO: not implemented, write position to {}".format( - position_file)) - # return sysfs.kernelFileIO(position_file, str(val)) + "setFrequency(): Channel {}, frequency: {} Hz, " + "period: {} ns".format( + self._eqep.channel, freq, period)) + + def setPosition(self, position): + '''Sets the current position to a new value''' + + position = int(position) + self._eqep.node.position = str(position) def zero(self): - ''' - zero()s - Set the current position to 0 - ''' + '''Resets the current position to 0''' + return self.setPosition(0) From 134edf68f53f0cb0f96fa8069ca2199387227e66 Mon Sep 17 00:00:00 2001 From: Drew Fustini Date: Sat, 25 Nov 2017 05:04:55 -0600 Subject: [PATCH 04/16] remove test_rotary.py as not valid for pytest --- test/test_rotary.py | 74 --------------------------------------------- 1 file changed, 74 deletions(-) delete mode 100755 test/test_rotary.py diff --git a/test/test_rotary.py b/test/test_rotary.py deleted file mode 100755 index 2292cd0..0000000 --- a/test/test_rotary.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/python -# -# BeagleBone must boot with cape-universal enabled -# and load the cape-universala overlay in order to -# use all the eQEP pins -# -# Install the latest Device Tree overlays: -# ======================================== -# sudo apt-get upgrade bb-cape-overlays -# -# File: /boot/uEnv.txt -# ==================== -# uname_r=4.4.62-ti-r99 -# cmdline=coherent_pool=1M quiet cape_universal=enable -# cape_enable=bone_capemgr.enable_partno=cape-universala -# -# File: /sys/devices/platform/bone_capemgr/slots -# ============================================== -# 0: PF---- -1 -# 1: PF---- -1 -# 2: PF---- -1 -# 3: PF---- -1 -# 4: P-O-L- 0 Override Board Name,00A0,Override Manuf,cape-universala -# -# eqep0: P9_27, P9_92 -# =================== -# config-pin P9_27 qep -# config-pin P9_92 qep # alias for P9_42.1 -# cat /sys/devices/platform/ocp/48300000.epwmss/48300180.eqep/position -# -# eqep1: P8.33, P8.35 -# =================== -# config-pin P8.33 qep -# config-pin P8.35 qep -# cat /sys/devices/platform/ocp/48302000.epwmss/48302180.eqep/position -# -# eqep2: P8.11, P8.12 -# =================== -# config-pin P8.11 qep -# config-pin P8.12 qep -# cat /sys/devices/platform/ocp/48304000.epwmss/48304180.eqep/position -# -# alternate pins for eqep2 (mutually exclusive) -# eqep2b: P8.41, P8.42 -# ==================== -# config-pin P8.41 qep -# config-pin P8.42 qep -# cat /sys/devices/platform/ocp/48304000.epwmss/48304180.eqep/position -# -# -# How To Run This Test: -# debian@beaglebone:~/ssh/adafruit-beaglebone-io-python$ sudo python ./setup.py install &> /dev/null && sudo python ./test/test_rotary.py -# -# - -import Adafruit_BBIO.Encoder as Encoder - -qep = Encoder.RotaryEncoder(0) -print("qep.getPosition(): {0}".format(qep.getPosition())) - -qep = Encoder.RotaryEncoder(1) -print("qep.getPosition(): {0}".format(qep.getPosition())) - -qep = Encoder.RotaryEncoder(2) -print("qep.getPosition(): {0}".format(qep.getPosition())) - - -#qep.getMode() -#qep.setAbsolute() -#qep.setRelative() -#qep.setFrequency(5000) -#qep.setPosition(100) -#qep.disable() -#print("qep.enable(): {0}".format(qep.enable())) From 896ea69c5fcf545dbb71b118e6b317f3e9860564 Mon Sep 17 00:00:00 2001 From: Drew Fustini Date: Sat, 25 Nov 2017 05:24:44 -0600 Subject: [PATCH 05/16] Fix seg fault of PWM in Python 3.6 (#189) PWM.set_frequency() and PWM.set_duty_cycle() were resulting in a seg fault inside the call to PyArg_ParseTupleAndKeywords(). Signed-off-by: Drew Fustini --- source/py_pwm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/py_pwm.c b/source/py_pwm.c index 49c539b..55f50f9 100644 --- a/source/py_pwm.c +++ b/source/py_pwm.c @@ -217,8 +217,8 @@ static const char moduledocstring[] = "PWM functionality of a BeagleBone using P PyMethodDef pwm_methods[] = { {"start", (PyCFunction)py_start_channel, METH_VARARGS | METH_KEYWORDS, "Set up and start the PWM channel. channel can be in the form of 'P8_10', or 'EHRPWM2A'"}, {"stop", (PyCFunction)py_stop_channel, METH_VARARGS | METH_KEYWORDS, "Stop the PWM channel. channel can be in the form of 'P8_10', or 'EHRPWM2A'"}, - { "set_duty_cycle", (PyCFunction)py_set_duty_cycle, METH_VARARGS, "Change the duty cycle\ndutycycle - between 0.0 and 100.0" }, - { "set_frequency", (PyCFunction)py_set_frequency, METH_VARARGS, "Change the frequency\nfrequency - frequency in Hz (freq > 0.0)" }, + { "set_duty_cycle", (PyCFunction)py_set_duty_cycle, METH_VARARGS | METH_KEYWORDS, "Change the duty cycle\ndutycycle - between 0.0 and 100.0" }, + { "set_frequency", (PyCFunction)py_set_frequency, METH_VARARGS | METH_KEYWORDS, "Change the frequency\nfrequency - frequency in Hz (freq > 0.0)" }, {"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"}, //{"setwarnings", py_setwarnings, METH_VARARGS, "Enable or disable warning messages"}, {NULL, NULL, 0, NULL} From 398e7fa7e888360805396070176d00b1366d4250 Mon Sep 17 00:00:00 2001 From: David Planella Date: Sat, 25 Nov 2017 19:59:41 +0100 Subject: [PATCH 06/16] Encoder: corrected kernel check logic --- Adafruit_BBIO/Encoder.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Adafruit_BBIO/Encoder.py b/Adafruit_BBIO/Encoder.py index 35e3efb..5c872d4 100644 --- a/Adafruit_BBIO/Encoder.py +++ b/Adafruit_BBIO/Encoder.py @@ -7,7 +7,8 @@ import sysfs import platform -if not platform.release().startswith('4.4'): +(major, minor, patch) = platform.release().split("-")[0].split(".") +if not (int(major) >= 4 and int(minor) >= 4): raise ImportError( 'The Encoder module requires Linux kernel version >= 4.4.x.\n' 'Please upgrade your kernel to use this module.\n' From cf89854a7da4f304a55e5f3cc5b369d5ec0d5008 Mon Sep 17 00:00:00 2001 From: Drew Fustini Date: Mon, 27 Nov 2017 22:52:23 -0600 Subject: [PATCH 07/16] Clarify there is no 0 prefix for pin lables (#180) Please note that there is no '0' prefix for the pin numbers. For example, pin 7 on header P8 is "P8_7" --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 32da985..6445bcf 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,22 @@ sudo pip install --upgrade Adafruit_BBIO Using the library is very similar to the excellent RPi.GPIO library used on the Raspberry Pi. Below are some examples. +### Pin Numbers + +Please note that there is no '0' prefix for the pin numbers. For example, pin 7 on header P8 is `P8_7`. + +**Correct:** +``` +GPIO.setup("P8_7", OUT ) +``` + +**INCORRECT:** +``` +GPIO.setup("P8_07", OUT ) +``` + +Refer to `pins_t table[]` in [common.c](https://github.com/adafruit/adafruit-beaglebone-io-python/blob/master/source/common.c#L73) all the pin labels. + ### config-pin [config-pin](https://github.com/beagleboard/bb.org-overlays/tree/master/tools/beaglebone-universal-io) is now used on the official BeagleBoard.org Debian Jessie and Stretch images to control pin mode (e.g. pin mux). From 2deabc58b7a30105b3cedf386321712447d8478e Mon Sep 17 00:00:00 2001 From: David Planella Date: Tue, 28 Nov 2017 12:10:57 +0100 Subject: [PATCH 08/16] Encoder: convert get/set methods to properties, update apidoc strings --- Adafruit_BBIO/Encoder.py | 166 ++++++++++++++++++++++++++------------- 1 file changed, 113 insertions(+), 53 deletions(-) diff --git a/Adafruit_BBIO/Encoder.py b/Adafruit_BBIO/Encoder.py index 5c872d4..83c7a9f 100644 --- a/Adafruit_BBIO/Encoder.py +++ b/Adafruit_BBIO/Encoder.py @@ -14,11 +14,16 @@ 'Please upgrade your kernel to use this module.\n' 'Your Linux kernel version is {}.'.format(platform.release())) + +# eQEP module channel identifiers +# eQEP 2 and 2b are the same channel, exposed on two different sets of pins, +# which are mutually exclusive eQEP0 = 0 eQEP1 = 1 eQEP2 = 2 eQEP2b = 3 +# Definitions to initialize the eQEP modules _OCP_PATH = "/sys/devices/platform/ocp" _eQEP_DEFS = [ {'channel': 'eQEP0', 'pin_A': 'P9_92', 'pin_B': 'P9_27', @@ -71,12 +76,31 @@ def __init__(self, channel, pin_A, pin_B, sys_path): class RotaryEncoder(object): + ''' + Rotary encoder class abstraction to control a given QEP channel. + + Constructor: + eqep_num: QEP object that determines which channel to control + + Properties: + position: current position of the encoder + frequency: frequency at which the encoder reports new positions + enabled: (read only) true if the module is enabled, false otherwise + mode: current mode of the encoder (absolute: 0, relative: 1) + + Methods: + enable: enable the QEP channel + disable: disable the QEP channel + setAbsolute: shortcut for setting the mode to absolute + setRelative: shortcut for setting the mode to relative + zero: shortcut for setting the position to 0 + ''' def _run_cmd(self, cmd): '''Runs a command. If not successful (i.e. error code different than zero), print the stderr output as a warning. - ''' + ''' try: output = check_output(cmd, stderr=STDOUT) self._logger.info( @@ -87,11 +111,8 @@ def _run_cmd(self, cmd): "_run_cmd(): cmd='{}' return code={} output={}".format( " ".join(cmd), e.returncode, e.output)) - def config_pin(self, pin): - ''' - config_pin() - Config pin for QEP - ''' + def _config_pin(self, pin): + '''Configures a pin in QEP mode using the `config-pin` binary''' self._run_cmd(["config-pin", pin, "qep"]) @@ -103,8 +124,11 @@ def __init__(self, eqep_num): Allowed values: EQEP0, EQEP1, EQEP2 or EQEP2b, based on which pins the physical rotary encoder is connected to. - + ''' + # nanoseconds factor to convert period to frequency and back + self._NS_FACTOR = 1000000000 + # Set up logging at the module level self._logger = logging.getLogger(__name__) self._logger.addHandler(logging.NullHandler()) @@ -117,8 +141,8 @@ def __init__(self, eqep_num): self._eqep.sys_path)) # Configure the pins for the given channel - self.config_pin(self._eqep.pin_A) - self.config_pin(self._eqep.pin_B) + self._config_pin(self._eqep.pin_A) + self._config_pin(self._eqep.pin_B) self._logger.debug( "RotaryEncoder(): sys node: {0}".format(self._eqep.sys_path)) @@ -126,19 +150,32 @@ def __init__(self, eqep_num): # Enable the channel upon initialization self.enable() - def _setEnable(self, value): + @property + def enabled(self): + '''Returns the enabled status of the module: + + true: module is enabled + false: module is disabled + ''' + isEnabled = bool(int(self._eqep.node.enabled)) + + return isEnabled + + def _setEnable(self, enabled): '''Turns the eQEP hardware ON or OFF value (int): 1 represents enabled, 0 is disabled + ''' - if value < 0 or value > 1: + enabled = int(enabled) + if enabled < 0 or enabled > 1: raise ValueError( 'The "enabled" attribute can only be set to 0 or 1. ' - 'You attempted to set it to {}.'.format(value)) + 'You attempted to set it to {}.'.format(enabled)) - self._eqep.node.enabled = str(int(value)) + self._eqep.node.enabled = str(enabled) self._logger.info("Channel: {}, enabled: {}".format( - self._eqep.channel, self._eqep.node.enabled)) + self._eqep.channel, self._eqep.node.enabled)) def enable(self): '''Turns the eQEP hardware ON''' @@ -150,20 +187,41 @@ def disable(self): self._setEnable(0) - def _setMode(self, value): + @property + def mode(self): + '''Returns the mode the eQEP hardware is in (absolute or relative). + + ''' + mode = int(self._eqep.node.mode) + + if mode == 0: + mode_name = "absolute" + elif mode == 1: + mode_name = "relative" + else: + mode_name = "invalid" + + self._logger.debug("getMode(): Channel {}, mode: {} ({})".format( + self._eqep.channel, mode, mode_name)) + + return mode + + @mode.setter + def mode(self, mode): '''Sets the eQEP mode as absolute (0) or relative (1). See the setAbsolute() and setRelative() methods for more information. ''' - if value < 0 or value > 1: + mode = int(mode) + if mode < 0 or mode > 1: raise ValueError( 'The "mode" attribute can only be set to 0 or 1. ' - 'You attempted to set it to {}.'.format(value)) + 'You attempted to set it to {}.'.format(mode)) - self._eqep.node.mode = str(int(value)) + self._eqep.node.mode = str(mode) self._logger.debug("Mode set to: {}".format( - self._eqep.node.mode)) + self._eqep.node.mode)) def setAbsolute(self): '''Sets the eQEP mode as Absolute: @@ -171,35 +229,17 @@ def setAbsolute(self): decremented by the encoder's movement ''' - self._setMode(0) + self.mode = 0 def setRelative(self): '''Sets the eQEP mode as Relative: The position is reset when the unit timer overflows. ''' - self._setMode(1) - - def getMode(self): - '''Returns the mode the eQEP hardware is in (absolute or relative). - - ''' + self.mode = 1 - mode = int(self._eqep.node.mode) - - if mode == 0: - mode_name = "absolute" - elif mode == 1: - mode_name = "relative" - else: - mode_name = "invalid" - - self._logger.debug("getMode(): Channel {}, mode: {} ({})".format( - self._eqep.channel, mode, mode_name)) - - return mode - - def getPosition(self): + @property + def position(self): '''Returns the current position of the encoder. In absolute mode, this attribute represents the current position of the encoder. @@ -209,31 +249,51 @@ def getPosition(self): ''' position = self._eqep.node.position - self._logger.debug("getPosition(): Channel {}, position: {}".format( + self._logger.debug("Get position: Channel {}, position: {}".format( self._eqep.channel, position)) return int(position) - def setFrequency(self, freq): + @position.setter + def position(self, position): + '''Sets the current position to a new value''' + + position = int(position) + self._eqep.node.position = str(position) + + self._logger.debug("Set position: Channel {}, position: {}".format( + self._eqep.channel, position)) + + + @property + def frequency(self): '''Sets the frequency in Hz at which the driver reports new positions. ''' - ns_factor = 1000000000 - period = ns_factor/freq # Period in nanoseconds - self._eqep.node.period = str(period) + frequency = self._eqep.node.period / self._NS_FACTOR + self._logger.debug( - "setFrequency(): Channel {}, frequency: {} Hz, " + "Set frequency(): Channel {}, frequency: {} Hz, " "period: {} ns".format( - self._eqep.channel, freq, period)) + self._eqep.channel, frequency, period)) - def setPosition(self, position): - '''Sets the current position to a new value''' + return frequency - position = int(position) - self._eqep.node.position = str(position) + @frequency.setter + def frequency(self, frequency): + '''Sets the frequency in Hz at which the driver reports + new positions. + + ''' + period = self._NS_FACTOR / frequency # Period in nanoseconds + self._eqep.node.period = str(period) + self._logger.debug( + "Set frequency(): Channel {}, frequency: {} Hz, " + "period: {} ns".format( + self._eqep.channel, frequency, period)) def zero(self): '''Resets the current position to 0''' - return self.setPosition(0) + self.position = 0 From cebf6b519521c531a6f2d10ba3508503540a120f Mon Sep 17 00:00:00 2001 From: David Planella Date: Tue, 28 Nov 2017 12:30:22 +0100 Subject: [PATCH 09/16] Encoder: updated README --- Adafruit_BBIO/README.md | 60 ++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/Adafruit_BBIO/README.md b/Adafruit_BBIO/README.md index aa3642b..000b6a8 100644 --- a/Adafruit_BBIO/README.md +++ b/Adafruit_BBIO/README.md @@ -1,52 +1,47 @@ -# Adafruit_BBIO.Encoder module +# Adafruit_BBIO.Encoder -This module enables access to the Beaglebone Black enhanced Quadrature Encoder Pulse (eQEP) modules: eQEP0, eQEP1 and eQEP2. - -Initially based on the [PyBBIO](https://github.com/graycatlabs/PyBBIO/bbio/libraries/RotaryEncoder/rotary_encoder.py) rotary encoder code. +This module enables access to the Beaglebone Black enhanced Quadrature Encoder Pulse (eQEP) modules: eQEP0, eQEP1 and eQEP2/eQEP2b. ## Prerequisites -These instructions are based on a 4.4.x Linux kernel. - -In order to use all eQEP pins the BeagleBone must boot with the [cape-universal](https://github.com/beagleboard/bb.org-overlays/tree/master/tools/beaglebone-universal-io) enabled, and load the cape-universal overlay - -``` -enable_uboot_cape_universal=1 -``` +These instructions are based on: -Notes: -- It seems that the `cape-universal` cape _does only enable access to eQEP0 and eQEP2_. TBD: check how to load [`cape-universala`](https://github.com/cdsteinkuehler/beaglebone-universal-io/pull/30) -- An alternative option to the `cape-universal` overlay would be to load one of the [dedicated eQEP overlays](https://github.com/Teknoman117/beaglebot/tree/master/encoders/dts). +- Linux kernel: 4.4.x or later +- `bb-cape-overlays` package: version 4.4.20171120.0-0rcnee1~stretch+20171120 or later +- `bb-customizations` package: version 1.20171123-0rcnee0~stretch+20171123 or later -### Install/upgrade the latest Device Tree overlays +It's recommended to run the following command to ensure you have the latest required packages: ``` -sudo apt-get upgrade bb-cape-overlays +sudo apt upgrade bb-cape-overlays bb-customizations ``` -### Load the universal cape +In order to use all eQEP pins the BeagleBone must boot with the [cape-universal](https://github.com/beagleboard/bb.org-overlays/tree/master/tools/beaglebone-universal-io) enabled, and load the `cape-universal` overlay. -If it doesn't already contain it, modify the `/boot/uEnv.txt` file to contain this line: +This is the default, thus **no further steps are initially required to use eQEP0 and eQEP2**. Simply double-check that the following line is present and not commented out on your `/boot/uEnv.txt` file: ``` enable_uboot_cape_universal=1 ``` -Notes: +Note: Some older documentation recommends using the `cmdline` and `cape_enable` options instead. They are meant to load deprecated kernel-based overlays and it's not recommended to use them. Use the new way of [loading overlays via uboot](https://elinux.org/Beagleboard:BeagleBoneBlack_Debian#U-Boot_Overlays) instead, as instructed above. + +### Enabling additional eQEP modules -- Some older documentation recommends using these two lines instead. They are meant to load deprecated kernel-based overlays and it's not recommended to use them. Use the new way of [loading overlays via uboot](https://elinux.org/Beagleboard:BeagleBoneBlack_Debian#U-Boot_Overlays) instead, as instructed above. +The `cape-universal` overlay will enable access to the eQEP0 and eQEP2 modules. As it does not expose pins that are shared with the HDMI interface, eQEP1 and eQEP2b will **not** be available. - ``` - cmdline=cape_universal=enable # Plus some other options - ``` - ``` - cape_enable=bone_capemgr.enable_partno=cape-universala - ``` -- TBD: check the overlays that are currently loaded +To disable the HDMI interface and gain access to the pins and peripherals that share its pins, comment out the following line on the `/boot/uEnv.txt` file and reboot: + +``` +disable_uboot_overlay_video=1 +``` ## eQEP configuraton -Note: if either eQEP1 or eQEP2b are used on the Beaglebone Black, video must be disabled, as their pins are shared with the LCD_DATAx lines of the HDMI interface. +Notes: + +- If either eQEP1 or eQEP2b are used on the Beaglebone Black, video must be disabled, as their pins are shared with the LCD_DATAx lines of the HDMI interface. +- eQEP2 and eQEP2b are the same module, but with the alternative of accessing it via two sets of pins. These are mutually exclusive. ### eQEP0 @@ -91,7 +86,12 @@ $ config-pin P8.41 qep $ config-pin P8.42 qep $ cat /sys/devices/platform/ocp/48304000.epwmss/48304180.eqep/position ``` + +## Credits + +Initially based on the [PyBBIO](https://github.com/graycatlabs/PyBBIO/bbio/libraries/RotaryEncoder/rotary_encoder.py) rotary encoder code. + ## Further reading -- [Beaglebone encoder inputs](https://github.com/Teknoman117/beaglebot/tree/master/encoders) -- [Beaglebone eQEP overlays](https://github.com/Teknoman117/beaglebot/tree/master/encoders/dts) +1. [Beaglebone encoder inputs](https://github.com/Teknoman117/beaglebot/tree/master/encoders) +1. [Beaglebone eQEP overlays](https://github.com/Teknoman117/beaglebot/tree/master/encoders/dts) From 01f131cc3cc362e3b82d523145527a73ef66468b Mon Sep 17 00:00:00 2001 From: David Planella Date: Tue, 28 Nov 2017 12:31:22 +0100 Subject: [PATCH 10/16] Encoder: add README apt install clarification --- Adafruit_BBIO/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Adafruit_BBIO/README.md b/Adafruit_BBIO/README.md index 000b6a8..d58a9b7 100644 --- a/Adafruit_BBIO/README.md +++ b/Adafruit_BBIO/README.md @@ -10,9 +10,10 @@ These instructions are based on: - `bb-cape-overlays` package: version 4.4.20171120.0-0rcnee1~stretch+20171120 or later - `bb-customizations` package: version 1.20171123-0rcnee0~stretch+20171123 or later -It's recommended to run the following command to ensure you have the latest required packages: +It's recommended to run the following commands to ensure you have the latest required packages: ``` +sudo apt update sudo apt upgrade bb-cape-overlays bb-customizations ``` From 29abba788ea0b8447a53f98f6ec2e71a1080af27 Mon Sep 17 00:00:00 2001 From: David Planella Date: Tue, 28 Nov 2017 12:39:49 +0100 Subject: [PATCH 11/16] Encoder: copyright assignment note, updated comments --- Adafruit_BBIO/sysfs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Adafruit_BBIO/sysfs.py b/Adafruit_BBIO/sysfs.py index a79cfb9..e6802f0 100644 --- a/Adafruit_BBIO/sysfs.py +++ b/Adafruit_BBIO/sysfs.py @@ -2,6 +2,8 @@ # -*- coding: utf-8 -*- # Copyright (c) 2014 MIT OpenCourseWare +# TODO: the copyright assignment above is boiler plate. Copyright needs to be +# properly assigned. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -32,7 +34,8 @@ # - David Planella """ -Simplistic Python SysFS interface. +Simplistic Python SysFS interface. It enables access to the sys filesystem device +nodes and to get and set their exposed attributes. Usage examples:: from sysfs import sys From f4e9623900c2c11984c9419f18ddcc12389b49a0 Mon Sep 17 00:00:00 2001 From: David Planella Date: Tue, 28 Nov 2017 12:46:51 +0100 Subject: [PATCH 12/16] Encoder: added usage notes --- Adafruit_BBIO/README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Adafruit_BBIO/README.md b/Adafruit_BBIO/README.md index d58a9b7..8bb4d7d 100644 --- a/Adafruit_BBIO/README.md +++ b/Adafruit_BBIO/README.md @@ -2,6 +2,30 @@ This module enables access to the Beaglebone Black enhanced Quadrature Encoder Pulse (eQEP) modules: eQEP0, eQEP1 and eQEP2/eQEP2b. +## Usage + +On a recent Beaglebone Debian image, access to the eQEP0 and eQEP2 channels should work out of the box: + +```python +import Adafruit_BBIO.Encoder as Encoder + +''' +Each channel can be accessed and initialized using its corresponding +channel name constants: + + Encoder.eQEP0 + Encoder.eQEP1 # Pins only available when video is disabled + Encoder.eQEP2 + Encoder.eQEP2b # Pins only available when video is disabled +''' + +# Instantiate the class to access channel eQEP2, and only initialize +# that channel +myEncoder = Encoder.RotaryEncoder(Encoder.eQEP2) +``` + +If you need to use further channels, read on the prerequisites in the following section. + ## Prerequisites These instructions are based on: From f4d1eb381bbbdce5701b915f841c8afb529bf6f8 Mon Sep 17 00:00:00 2001 From: Drew Fustini Date: Wed, 29 Nov 2017 20:47:24 -0600 Subject: [PATCH 13/16] assign copyright for new file to Adafruit Industries Signed-off-by: Drew Fustini --- Adafruit_BBIO/sysfs.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Adafruit_BBIO/sysfs.py b/Adafruit_BBIO/sysfs.py index e6802f0..43fd078 100644 --- a/Adafruit_BBIO/sysfs.py +++ b/Adafruit_BBIO/sysfs.py @@ -2,8 +2,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2014 MIT OpenCourseWare -# TODO: the copyright assignment above is boiler plate. Copyright needs to be -# properly assigned. +# Copyright (c) 2017 Adafruit Industries # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal From ede3277751af977d6ca80c0e0499f3f2339def23 Mon Sep 17 00:00:00 2001 From: Drew Fustini Date: Thu, 30 Nov 2017 02:13:28 -0600 Subject: [PATCH 14/16] Add bash scripts to help install and test Add useful bash scripts to install and run pytest for all versions of Python that I'm currently running on my BeagleBone. "python" package in Debian 9.2: Python 2.7.13 "python3" package in Debian 9.2: Python 3.5.3 compiled from source by myself on a beaglebone: Python 3.6.3 Example usage: debian@beaglebone:~/ssh/adafruit-beaglebone-io-python$ sudo ./install_all_python_versions.sh && ./pytest_all_versions.sh Signed-off-by: Drew Fustini --- install_all_python_versions.sh | 9 +++++++++ pytest_all_versions.sh | 11 +++++++++++ 2 files changed, 20 insertions(+) create mode 100755 install_all_python_versions.sh create mode 100755 pytest_all_versions.sh diff --git a/install_all_python_versions.sh b/install_all_python_versions.sh new file mode 100755 index 0000000..932c462 --- /dev/null +++ b/install_all_python_versions.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# useful for testing changes against all versions of python +make clean +echo "Install Python 2.7" +python2.7 ./setup.py install +echo "Install Python 3.5" +python3.5 ./setup.py install +echo "Install Python 3.6" +python3.6 ./setup.py install diff --git a/pytest_all_versions.sh b/pytest_all_versions.sh new file mode 100755 index 0000000..dca6919 --- /dev/null +++ b/pytest_all_versions.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# useful for testing changes against all versions of python + +cd test +echo "Testing Python 2.7" +python2.7 -mpytest +echo "Testing Python 3.5" +python3.5 -mpytest +echo "Testing Python 3.6" +python3.6 -mpytest +cd .. From e70a7fde00ae258f33508d390993f1c46b9c8611 Mon Sep 17 00:00:00 2001 From: David Planella Date: Thu, 30 Nov 2017 19:56:48 +0100 Subject: [PATCH 15/16] Encoder: switched sysfs to be a relative import compatible with Python 2 and 3 --- Adafruit_BBIO/Encoder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Adafruit_BBIO/Encoder.py b/Adafruit_BBIO/Encoder.py index 83c7a9f..5caa166 100644 --- a/Adafruit_BBIO/Encoder.py +++ b/Adafruit_BBIO/Encoder.py @@ -4,7 +4,7 @@ import os import logging import itertools -import sysfs +from .sysfs import Node import platform (major, minor, patch) = platform.release().split("-")[0].split(".") @@ -72,7 +72,7 @@ def __init__(self, channel, pin_A, pin_B, sys_path): self.pin_A = pin_A self.pin_B = pin_B self.sys_path = sys_path - self.node = sysfs.Node(sys_path) + self.node = Node(sys_path) class RotaryEncoder(object): From 37f7204f3fd9d429822b541a9f783eb0684b559e Mon Sep 17 00:00:00 2001 From: David Planella Date: Thu, 30 Nov 2017 20:08:31 +0100 Subject: [PATCH 16/16] Encoder: use items() instead of iteritems() to be Python 3 compatible --- Adafruit_BBIO/Encoder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Adafruit_BBIO/Encoder.py b/Adafruit_BBIO/Encoder.py index 5caa166..0e94fa6 100644 --- a/Adafruit_BBIO/Encoder.py +++ b/Adafruit_BBIO/Encoder.py @@ -47,7 +47,7 @@ def fromdict(cls, d): '''Creates a class instance from a dictionary''' allowed = ('channel', 'pin_A', 'pin_B', 'sys_path') - df = {k: v for k, v in d.iteritems() if k in allowed} + df = {k: v for k, v in d.items() if k in allowed} return cls(**df) def __init__(self, channel, pin_A, pin_B, sys_path): pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy