Skip to content
Merged
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
239 changes: 239 additions & 0 deletions notecard/gpio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
"""GPIO abstractions for note-python."""

from .platform import platform

if platform == 'circuitpython':
import digitalio
elif platform == 'micropython':
import machine
elif platform == 'raspbian':
import RPi.GPIO as rpi_gpio


class GPIO:
"""GPIO abstraction.

Supports GPIO on CircuitPython, MicroPython, and Raspbian (Raspberry Pi).
"""

IN = 0
OUT = 1
PULL_UP = 2
PULL_DOWN = 3
PULL_NONE = 4

def direction(self, direction):
"""Set the direction of the pin.

Does nothing in this base class. Should be implemented by subclasses.
"""
pass

def pull(self, pull):
"""Set the pull of the pin.

Does nothing in this base class. Should be implemented by subclasses.
"""
pass

def value(self, value=None):
"""Set the output or get the current level of the pin.

Does nothing in this base class. Should be implemented by subclasses.
"""
pass

@staticmethod
def setup(pin, direction, pull=None, value=None):
"""Set up a GPIO.

The platform is detected internally so that the user doesn't need to
write platform-specific code themselves.
"""
if platform == 'circuitpython':
return CircuitPythonGPIO(pin, direction, pull, value)
elif platform == 'micropython':
return MicroPythonGPIO(pin, direction, pull, value)
elif platform == 'raspbian':
return RpiGPIO(pin, direction, pull, value)

def __init__(self, pin, direction, pull=None, value=None):
"""Initialize the GPIO.

Pin and direction are required arguments. Pull and value will be set
only if given.
"""
self.direction(direction)

if pull is not None:
self.pull(pull)

if value is not None:
self.value(value)


class CircuitPythonGPIO(GPIO):
"""GPIO for CircuitPython."""

def direction(self, direction):
"""Set the direction of the pin.

Allowed direction values are GPIO.IN and GPIO.OUT. Other values cause a
ValueError.
"""
if direction == GPIO.IN:
self.pin.direction = digitalio.Direction.INPUT
elif direction == GPIO.OUT:
self.pin.direction = digitalio.Direction.OUTPUT
else:
raise ValueError(f"Invalid pin direction: {direction}.")

def pull(self, pull):
"""Set the pull of the pin.

Allowed pull values are GPIO.PULL_UP, GPIO.PULL_DOWN, and
GPIO.PULL_NONE. Other values cause a ValueError.
"""
if pull == GPIO.PULL_UP:
self.pin.pull = digitalio.Pull.UP
elif pull == GPIO.PULL_DOWN:
self.pin.pull = digitalio.Pull.DOWN
elif pull == GPIO.PULL_NONE:
self.pin.pull = None
else:
raise ValueError(f"Invalid pull value: {pull}.")

def value(self, value=None):
"""Set the output or get the current level of the pin.

If value is not given, returns the level of the pin (i.e. the pin is an
input). If value is given, sets the level of the pin (i.e. the pin is an
output).
"""
if value is None:
return self.pin.value
else:
self.pin.value = value

def __init__(self, pin, direction, pull=None, value=None):
"""Initialize the GPIO.

Pin and direction are required arguments. Pull and value will be set
only if given.
"""
self.pin = digitalio.DigitalInOut(pin)
super().__init__(pin, direction, pull, value)


class MicroPythonGPIO(GPIO):
"""GPIO for MicroPython."""

def direction(self, direction):
"""Set the direction of the pin.

Allowed direction values are GPIO.IN and GPIO.OUT. Other values cause a
ValueError.
"""
if direction == GPIO.IN:
self.pin.init(mode=machine.Pin.IN)
elif direction == GPIO.OUT:
self.pin.init(mode=machine.Pin.OUT)
else:
raise ValueError(f"Invalid pin direction: {direction}.")

def pull(self, pull):
"""Set the pull of the pin.

Allowed pull values are GPIO.PULL_UP, GPIO.PULL_DOWN, and
GPIO.PULL_NONE. Other values cause a ValueError.
"""
if pull == GPIO.PULL_UP:
self.pin.init(pull=machine.Pin.PULL_UP)
elif pull == GPIO.PULL_DOWN:
self.pin.init(pull=machine.Pin.PULL_DOWN)
elif pull == GPIO.PULL_NONE:
self.pin.init(pull=None)
else:
raise ValueError(f"Invalid pull value: {pull}.")

def value(self, value=None):
"""Set the output or get the current level of the pin.

If value is not given, returns the level of the pin (i.e. the pin is an
input). If value is given, sets the level of the pin (i.e. the pin is an
output).
"""
if value is None:
return self.pin.value()
else:
self.pin.init(value=value)

def __init__(self, pin, direction, pull=None, value=None):
"""Initialize the GPIO.

Pin and direction are required arguments. Pull and value will be set
only if given.
"""
self.pin = machine.Pin(pin)
super().__init__(pin, direction, pull, value)


class RpiGPIO(GPIO):
"""GPIO for Raspbian (Raspberry Pi)."""

def direction(self, direction):
"""Set the direction of the pin.

Allowed direction values are GPIO.IN and GPIO.OUT. Other values cause a
ValueError.
"""
if direction == GPIO.IN:
self.rpi_direction = rpi_gpio.IN
rpi_gpio.setup(self.pin, direction=rpi_gpio.IN)
elif direction == GPIO.OUT:
self.rpi_direction = rpi_gpio.OUT
rpi_gpio.setup(self.pin, direction=rpi_gpio.OUT)
else:
raise ValueError(f"Invalid pin direction: {direction}.")

def pull(self, pull):
"""Set the pull of the pin.

Allowed pull values are GPIO.PULL_UP, GPIO.PULL_DOWN, and
GPIO.PULL_NONE. Other values cause a ValueError.
"""
if pull == GPIO.PULL_UP:
rpi_gpio.setup(self.pin,
direction=self.rpi_direction,
pull_up_down=rpi_gpio.PUD_UP)
elif pull == GPIO.PULL_DOWN:
rpi_gpio.setup(self.pin,
direction=self.rpi_direction,
pull_up_down=GPIO.PUD_DOWN)
elif pull == GPIO.PULL_NONE:
rpi_gpio.setup(self.pin,
direction=self.rpi_direction,
pull_up_down=rpi_gpio.PUD_OFF)
else:
raise ValueError(f"Invalid pull value: {pull}.")

def value(self, value=None):
"""Set the output or get the current level of the pin.

If value is not given, returns the level of the pin (i.e. the pin is an
input). If value is given, sets the level of the pin (i.e. the pin is an
output).
"""
if value is None:
return rpi_gpio.input(self.pin)
else:
rpi_gpio.output(self.pin, value)

def __init__(self, pin, direction, pull=None, value=None):
"""Initialize the GPIO.

Pin and direction are required arguments. Pull and value will be set
only if given.
"""
self.pin = pin
super().__init__(pin, direction, pull, value)
60 changes: 23 additions & 37 deletions notecard/notecard.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
import os
import json
import time
from .timeout import start_timeout, has_timed_out
from .transaction_manager import TransactionManager

use_periphery = False
use_micropython = False
Expand All @@ -52,7 +54,6 @@

use_i2c_lock = not use_periphery and not use_micropython


NOTECARD_I2C_ADDRESS = 0x17

# The notecard is a real-time device that has a fixed size interrupt buffer.
Expand All @@ -61,37 +62,6 @@
CARD_REQUEST_SEGMENT_MAX_LEN = 250
CARD_REQUEST_SEGMENT_DELAY_MS = 250

if not use_rtc:
if use_circuitpython:
import supervisor
from supervisor import ticks_ms

_TICKS_PERIOD = 1 << 29
_TICKS_MAX = _TICKS_PERIOD - 1
_TICKS_HALFPERIOD = _TICKS_PERIOD // 2

def ticks_diff(ticks1, ticks2):
"""Compute the signed difference between two ticks values."""
diff = (ticks1 - ticks2) & _TICKS_MAX # noqa: F821
diff = ((diff + _TICKS_HALFPERIOD) # noqa: F821
& _TICKS_MAX) - _TICKS_HALFPERIOD # noqa: F821
return diff
if use_micropython:
from utime import ticks_diff, ticks_ms # noqa: F811


def has_timed_out(start, timeout_secs):
"""Determine whether a timeout interval has passed during communication."""
if not use_rtc:
return ticks_diff(ticks_ms(), start) > timeout_secs * 1000
else:
return time.time() > start + timeout_secs


def start_timeout():
"""Start the timeout interval for I2C communication."""
return ticks_ms() if not use_rtc else time.time()


def prepareRequest(req, debug=False):
"""Format the request string as a JSON object and add a newline."""
Expand Down Expand Up @@ -137,10 +107,14 @@ def serialReset(port):
raise Exception("Notecard not responding")


def serialTransaction(port, req, debug):
def serialTransaction(port, req, debug, txn_manager=None):
"""Perform a single write to and read from a Notecard."""
req_json = prepareRequest(req, debug)

transaction_timeout_secs = 30
if txn_manager:
txn_manager.start(transaction_timeout_secs)

seg_off = 0
seg_left = len(req_json)
while True:
Expand All @@ -156,8 +130,10 @@ def serialTransaction(port, req, debug):
break
time.sleep(CARD_REQUEST_SEGMENT_DELAY_MS / 1000)

rsp_json = port.readline()
if txn_manager:
txn_manager.stop()

rsp_json = port.readline()
if debug:
print(rsp_json.rstrip())

Expand Down Expand Up @@ -206,6 +182,7 @@ def __init__(self):
self._user_agent['os_family'] = os.name
else:
self._user_agent['os_family'] = os.uname().machine
self._transaction_manager = None

def _preprocessReq(self, req):
"""Inspect the request for hub.set and add the User Agent."""
Expand All @@ -231,6 +208,10 @@ def UserAgentSent(self):
"""Return true if the User Agent has been sent to the Notecard."""
return self._user_agent_sent

def SetTransactionPins(self, rtx_pin, ctx_pin):
"""Set the pins used for RTX and CTX."""
self._transaction_manager = TransactionManager(rtx_pin, ctx_pin)


class OpenSerial(Notecard):
"""Notecard class for Serial communication."""
Expand Down Expand Up @@ -258,13 +239,13 @@ def Transaction(self, req):
if use_serial_lock:
try:
self.lock.acquire(timeout=5)
return serialTransaction(self.uart, req, self._debug)
return serialTransaction(self.uart, req, self._debug, self._transaction_manager)
except Timeout:
raise Exception("Notecard in use")
finally:
self.lock.release()
else:
return serialTransaction(self.uart, req, self._debug)
return serialTransaction(self.uart, req, self._debug, self._transaction_manager)

def Reset(self):
"""Reset the Notecard."""
Expand Down Expand Up @@ -349,12 +330,15 @@ def Transaction(self, req):
pass

try:
transaction_timeout_secs = 30
if self._transaction_manager:
self._transaction_manager.start(transaction_timeout_secs)

self._sendPayload(req_json)

chunk_len = 0
received_newline = False
start = start_timeout()
transaction_timeout_secs = 30
while True:
time.sleep(.001)
reg = bytearray(2)
Expand Down Expand Up @@ -391,6 +375,8 @@ def Transaction(self, req):

finally:
self.unlock()
if self._transaction_manager:
self._transaction_manager.stop()

if self._debug:
print(rsp_json.rstrip())
Expand Down
Loading