Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 9 additions & 27 deletions l293d/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@
# -*- coding: utf-8 -*-


class ConfigMeta(type):
class Config(object):
__verbose = True
__test_mode = False
__pin_numbering = 'BOARD'
pins_in_use = []

@classmethod
def __getattr__(cls, attr):
"""
Try and call get_`attr` on the cls.
"""
try:
return cls.__dict__["get_" + attr].__func__(cls)
return getattr(cls, "get_" + attr)()
except KeyError:
raise AttributeError(
"object '{}' has no attribute '{}'".format(
cls.__name__, attr))

@classmethod
def __setattr__(cls, attr, value):
"""
Try and call set_`attr` on the cls.
Expand All @@ -23,35 +29,11 @@ def __setattr__(cls, attr, value):
# First try calling the set_ method. This will allow the set_
# method to perform it's checks, then recursively call this method.
try:
cls.__dict__["set_" + attr].__func__(cls, value)
getattr(cls, "set_" + attr)(value)
except KeyError:
raise AttributeError(
"object '{}' has no attribute '{}'".format(
cls.__name__, attr))
else:
# Now that the set_ method has done it's checks, we can use super()
# to actually set the value.
super(ConfigMeta, cls).__setattr__(attr, value)


def with_metaclass(mcls):
"""
Allows compatibility for metaclass in python2 and python3
"""
def decorator(cls):
body = vars(cls).copy()
body.pop("__dict__", None)
body.pop("__weakref__", None)
return mcls(cls.__name__, cls.__bases__, body)
return decorator


@with_metaclass(ConfigMeta)
class Config(object):
__verbose = True
__test_mode = False
__pin_numbering = 'BOARD'
pins_in_use = []

@classmethod
def set_verbose(cls, value):
Expand Down
110 changes: 36 additions & 74 deletions l293d/driver.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import print_function

from collections import namedtuple
from threading import Thread
try:
from collections import namedtuple
except ImportError:
from ucollections import namedtuple

try:
from threading import Thread
except ImportError:
threading = False
from time import sleep

from l293d.gpio import GPIO, pins_are_valid
from l293d.config import Config
Config = Config()


def v_print(string):
Expand All @@ -22,24 +30,12 @@ def v_print(string):
return False


# Import GPIO
try:
import RPi.GPIO as GPIO
except ImportError:
GPIO = None
Config.test_mode = True
v_print(
"Can't import RPi.GPIO; test mode has been enabled:\n"
"http://l293d.rtfd.io/en/latest/user-guide/configuration/#test-mode")

if not Config.test_mode:
GPIO.setwarnings(False)
GPIO.setwarnings(False)

# Set GPIO mode
if not Config.test_mode:
pin_num = Config.pin_numbering
v_print('Setting GPIO mode: {}'.format(pin_num))
GPIO.setmode(getattr(GPIO, pin_num))
pin_num = Config.pin_numbering
v_print('Setting GPIO mode: {}'.format(pin_num))
GPIO.setmode(getattr(GPIO, pin_num))

pins_in_use = Config.pins_in_use # Lists pins in use (all motors)

Expand All @@ -61,8 +57,6 @@ def __init__(self, pin_a=0, pin_b=0, pin_c=0):

self.pwm = None

self.pin_numbering = Config.pin_numbering

self.reversed = False

# Check pins are valid
Expand All @@ -79,8 +73,7 @@ def gpio_setup(self):
Set GPIO.OUT for each pin in use
"""
for pin in self.motor_pins:
if not Config.test_mode:
GPIO.setup(pin, GPIO.OUT)
GPIO.setup(pin, GPIO.OUT)

def drive_motor(self, direction=1, duration=None, wait=True, speed=100):
"""
Expand All @@ -98,20 +91,22 @@ def drive_motor(self, direction=1, duration=None, wait=True, speed=100):

if self.reversed:
direction *= -1
if not Config.test_mode:
if direction == 0: # Then stop motor
self.pwm.stop()
else: # Spin motor
# Create a PWM object to control the 'enable pin' for the chip
self.pwm = GPIO.PWM(self.motor_pins[0], speed.freq)
# Set first direction GPIO level
GPIO.output(self.motor_pins[direction], GPIO.HIGH)
# Set second direction GPIO level
GPIO.output(self.motor_pins[direction * -1], GPIO.LOW)
# Start PWM on the 'enable pin'
self.pwm.start(speed.cycle)
if direction == 0: # Then stop motor
self.pwm.stop()
else: # Spin motor
# Create a PWM object to control the 'enable pin' for the chip
self.pwm = GPIO.PWM(self.motor_pins[0], speed.freq)
# Set first direction GPIO level
GPIO.output(self.motor_pins[direction], GPIO.HIGH)
# Set second direction GPIO level
GPIO.output(self.motor_pins[direction * -1], GPIO.LOW)
# Start PWM on the 'enable pin'
self.pwm.start(speed.cycle)
# If duration has been specified, sleep then stop
if duration is not None and direction != 0:
if not threading:
self.stop(duration)
return
stop_thread = Thread(target=self.stop, args=(duration,))
# Sleep in thread
stop_thread.start()
Expand All @@ -135,7 +130,7 @@ def __move_motor(self, direction, duration, wait, action, speed):
'{pin_nums} pins {pin_str}'.format(
action=action,
reversed='reversed' if self.reversed else '',
pin_nums=self.pin_numbering,
pin_nums=Config.pin_numbering,
pin_str=self.pins_string_list()))

self.drive_motor(direction=direction, duration=duration,
Expand Down Expand Up @@ -199,45 +194,12 @@ def __init__(self):
'for more info')


def pins_are_valid(pins, force_selection=False):
"""
Check the pins specified are valid for pin numbering in use
"""
# Pin numbering, used below, should be
# a parameter of this function (future)
if Config.pin_numbering == 'BOARD': # Set valid pins for BOARD
valid_pins = [
7, 11, 12, 13, 15, 16, 18, 22, 29, 31, 32, 33, 36, 37
]
elif Config.pin_numbering == 'BCM': # Set valid pins for BCM
valid_pins = [
4, 5, 6, 12, 13, 16, 17, 18, 22, 23, 24, 25, 26, 27
]
else: # pin_numbering value invalid
raise ValueError("pin_numbering must be either 'BOARD' or 'BCM'.")
for pin in pins:
pin_int = int(pin)
if pin_int not in valid_pins and force_selection is False:
err_str = (
"GPIO pin number must be from list of valid pins: %s"
"\nTo use selected pins anyway, set force_selection=True "
"in function call." % str(valid_pins))
raise ValueError(err_str)
if pin in pins_in_use:
raise ValueError('GPIO pin {} already in use.'.format(pin))
return True


def cleanup():
"""
Call GPIO cleanup method
"""
if not Config.test_mode:
try:
GPIO.cleanup()
v_print('GPIO cleanup successful.')
except Exception:
v_print('GPIO cleanup failed.')
else:
# Skip GPIO cleanup if GPIO calls are not being made (test_mode)
v_print('Cleanup not needed when test_mode is enabled.')
try:
GPIO.cleanup()
v_print('GPIO cleanup successful.')
except Exception:
v_print('GPIO cleanup failed.')
17 changes: 17 additions & 0 deletions l293d/gpio/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
raspberry_pi, micropython = True, True

try:
from l293d.gpio.raspberrypi import GPIO, pins_are_valid
except ImportError:
raspberry_pi = False

try:
from l293d.gpio.micropython import GPIO, pins_are_valid
except ImportError:
micropython = False

if not raspberry_pi and not micropython:
print(
"Can't import a GPIO controller; test mode has been enabled:\n"
"http://l293d.rtfd.io/en/latest/user-guide/configuration/#test-mode")
from l293d.gpio.testgpio import GPIO, pins_are_valid
54 changes: 54 additions & 0 deletions l293d/gpio/micropython.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import machine


def pins_are_valid(pins, force_selection=False):
"""
Check the pins specified are valid for pin numbering in use
"""
# Micropython can be run on any number of boards, so don't do any
# validation against pin numbers.
return True


class GPIO(object):
__pins = {}

IN = 0
OUT = 1
LOW = 0
HIGH = 1

BOARD = "BOARD"
BCM = "BCM"

class PwmObject(object):
def __init__(self, pin, freq):
self.pin = pin
self.pwm = machine.PWM(self.pin)
self.pwm.freq(freq)

def start(self, duty):
self.pwm.duty(duty)

def stop(self):
self.pwm.deinit()

@classmethod
def PWM(cls, pin, freq):
return cls.PwmObject(cls.__pins[pin], freq)

@classmethod
def setwarnings(cls, warn):
pass

@classmethod
def setmode(cls, mode):
pass

@classmethod
def setup(cls, pin_num, mode):
cls.__pins[pin_num] = machine.Pin(pin_num, mode)

@classmethod
def output(cls, pin_num, mode):
cls.__pins[pin_num].value(mode)
32 changes: 32 additions & 0 deletions l293d/gpio/raspberrypi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from RPi.GPIO import GPIO # noqa: F401

from l293d import Config


def pins_are_valid(pins, force_selection=False):
"""
Check the pins specified are valid for pin numbering in use
"""
print(Config.pin_numbering)
if Config.pin_numbering == 'BOARD': # Set valid pins for BOARD
valid_pins = [
7, 11, 12, 13, 15, 16, 18, 22, 29, 31, 32, 33, 36, 37
]
elif Config.pin_numbering == 'BCM': # Set valid pins for BCM
valid_pins = [
4, 5, 6, 12, 13, 16, 17, 18, 22, 23, 24, 25, 26, 27
]
else: # pin_numbering value invalid
raise ValueError("pin_numbering must be either 'BOARD' or 'BCM'.")

for pin in pins:
pin_int = int(pin)
if pin_int not in valid_pins and force_selection is False:
err_str = (
"GPIO pin number must be from list of valid pins: %s"
"\nTo use selected pins anyway, set force_selection=True "
"in function call." % str(valid_pins))
raise ValueError(err_str)
if pin in Config.pins_in_use:
raise ValueError('GPIO pin {} already in use.'.format(pin))
return True
45 changes: 45 additions & 0 deletions l293d/gpio/testgpio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

def pins_are_valid(pins, force_selection=False):
"""
Check the pins specified are valid for pin numbering in use
"""
# Test mode shouldn't validate pins
return True


class GPIO(object):
__pins = {}

IN = 0
OUT = 1
LOW = 0
HIGH = 1

BOARD = "BOARD"
BCM = "BCM"

class PWM(object):
def __init__(self, pin_num, freq):
pass

def start(self, duty):
pass

def stop(self):
pass

@classmethod
def setwarnings(cls, warn):
pass

@classmethod
def setmode(cls, mode):
pass

@classmethod
def setup(cls, pin_num, mode):
pass

@classmethod
def output(cls, pin_num, mode):
pass