diff --git a/adafruit_bluefruitspi.py b/adafruit_bluefruitspi.py index 20b00d3..7e7a071 100644 --- a/adafruit_bluefruitspi.py +++ b/adafruit_bluefruitspi.py @@ -42,92 +42,63 @@ * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice """ -# imports - __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BluefruitSPI.git" import time +import struct from digitalio import Direction, Pull from adafruit_bus_device.spi_device import SPIDevice from micropython import const -import struct - -class MsgType: #pylint: disable=too-few-public-methods,bad-whitespace - """An enum-like class representing the possible message types. - Possible values are: - - ``MsgType.COMMAND`` - - ``MsgType.RESPONSE`` - - ``MsgType.ALERT`` - - ``MsgType.ERROR`` - """ - COMMAND = const(0x10) # Command message - RESPONSE = const(0x20) # Response message - ALERT = const(0x40) # Alert message - ERROR = const(0x80) # Error message - - -class SDEPCommand: #pylint: disable=too-few-public-methods,bad-whitespace - """An enum-like class representing the possible SDEP commands. - Possible values are: - - ``SDEPCommand.INITIALIZE`` - - ``SDEPCommand.ATCOMMAND`` - - ``SDEPCommand.BLEUART_TX`` - - ``SDEPCommand.BLEUART_RX`` - """ - INITIALIZE = const(0xBEEF) # Resets the Bluefruit device - ATCOMMAND = const(0x0A00) # AT command wrapper - BLEUART_TX = const(0x0A01) # BLE UART transmit data - BLEUART_RX = const(0x0A02) # BLE UART read data - - -class ArgType: #pylint: disable=too-few-public-methods,bad-whitespace - """An enum-like class representing the possible argument types. - Possible values are - - ``ArgType.STRING`` - - ``ArgType.BYTEARRAY`` - - ``ArgType.INT32`` - - ``ArgType.UINT32`` - - ``ArgType.INT16`` - - ``ArgType.UINT16`` - - ``ArgType.INT8`` - - ``ArgType.UINT8`` - """ - STRING = const(0x0100) # String data type - BYTEARRAY = const(0x0200) # Byte array data type - INT32 = const(0x0300) # Signed 32-bit integer data type - UINT32 = const(0x0400) # Unsigned 32-bit integer data type - INT16 = const(0x0500) # Signed 16-bit integer data type - UINT16 = const(0x0600) # Unsigned 16-bit integer data type - INT8 = const(0x0700) # Signed 8-bit integer data type - UINT8 = const(0x0800) # Unsigned 8-bit integer data type - - -class ErrorCode: #pylint: disable=too-few-public-methods,bad-whitespace - """An enum-like class representing possible error codes. - Possible values are - - ``ErrorCode.`` - """ - INVALIDMSGTYPE = const(0x8021) # SDEP: Unexpected SDEP MsgType - INVALIDCMDID = const(0x8022) # SDEP: Unknown command ID - INVALIDPAYLOAD = const(0x8023) # SDEP: Payload problem - INVALIDLEN = const(0x8024) # SDEP: Indicated len too large - INVALIDINPUT = const(0x8060) # AT: Invalid data - UNKNOWNCMD = const(0x8061) # AT: Unknown command name - INVALIDPARAM = const(0x8062) # AT: Invalid param value - UNSUPPORTED = const(0x8063) # AT: Unsupported command +# pylint: disable=bad-whitespace +_MSG_COMMAND = const(0x10) # Command message +_MSG_RESPONSE = const(0x20) # Response message +_MSG_ALERT = const(0x40) # Alert message +_MSG_ERROR = const(0x80) # Error message + +_SDEP_INITIALIZE = const(0xBEEF) # Resets the Bluefruit device +_SDEP_ATCOMMAND = const(0x0A00) # AT command wrapper +_SDEP_BLEUART_TX = const(0x0A01) # BLE UART transmit data +_SDEP_BLEUART_RX = const(0x0A02) # BLE UART read data + +_ARG_STRING = const(0x0100) # String data type +_ARG_BYTEARRAY = const(0x0200) # Byte array data type +_ARG_INT32 = const(0x0300) # Signed 32-bit integer data type +_ARG_UINT32 = const(0x0400) # Unsigned 32-bit integer data type +_ARG_INT16 = const(0x0500) # Signed 16-bit integer data type +_ARG_UINT16 = const(0x0600) # Unsigned 16-bit integer data type +_ARG_INT8 = const(0x0700) # Signed 8-bit integer data type +_ARG_UINT8 = const(0x0800) # Unsigned 8-bit integer data type + +_ERROR_INVALIDMSGTYPE = const(0x8021) # SDEP: Unexpected SDEP MsgType +_ERROR_INVALIDCMDID = const(0x8022) # SDEP: Unknown command ID +_ERROR_INVALIDPAYLOAD = const(0x8023) # SDEP: Payload problem +_ERROR_INVALIDLEN = const(0x8024) # SDEP: Indicated len too large +_ERROR_INVALIDINPUT = const(0x8060) # AT: Invalid data +_ERROR_UNKNOWNCMD = const(0x8061) # AT: Unknown command name +_ERROR_INVALIDPARAM = const(0x8062) # AT: Invalid param value +_ERROR_UNSUPPORTED = const(0x8063) # AT: Unsupported command + +# For the Bluefruit Connect packets +_PACKET_BUTTON_LEN = const(5) +_PACKET_COLOR_LEN = const(6) + +# pylint: enable=bad-whitespace class BluefruitSPI: """Helper for the Bluefruit LE SPI Friend""" - def __init__(self, spi, cs, irq, reset, debug=False): + def __init__(self, spi, cs, irq, reset, debug=False): # pylint: disable=too-many-arguments self._irq = irq self._buf_tx = bytearray(20) self._buf_rx = bytearray(20) self._debug = debug + # a cache of data, used for packet parsing + self._buffer = [] + # Reset reset.direction = Direction.OUTPUT reset.value = False @@ -146,7 +117,7 @@ def __init__(self, spi, cs, irq, reset, debug=False): self._spi_device = SPIDevice(spi, cs, baudrate=4000000, phase=0, polarity=0) - def _cmd(self, cmd): + def _cmd(self, cmd): # pylint: disable=too-many-branches """ Executes the supplied AT command, which must be terminated with a new-line character. @@ -172,16 +143,19 @@ def _cmd(self, cmd): plen = 16 # Note the 'more' value in bit 8 of the packet len struct.pack_into(" n_sec): + connection_timestamp = time.monotonic() + is_connected = bluefruit.connected + return is_connected + +# Unlike most circuitpython code, this runs in two loops +# one outer loop manages reconnecting bluetooth if we lose connection +# then one inner loop for doing what we want when connected! +while True: + # Initialize the module + try: # Wireless connections can have corrupt data or other runtime failures + # This try block will reset the module if that happens + init_bluefruit() + wait_for_connection() + print("\n *Connected!*") + + # Once connected, check for incoming BLE UART data + while check_connection(3): # Check our connection status every 3 seconds + # OK we're still connected, see if we have any data waiting + resp = bluefruit.read_packet() + if not resp: + continue # nothin' + print("Read packet", resp) + # Look for a 'C'olor packet + if resp[0] != 'C': + continue + # Set the neopixels to the three bytes in the packet + pixels.fill(resp[1:4]) + print("Connection lost.") + + except RuntimeError as e: + print(e) # Print what happened + continue # retry! diff --git a/examples/bluefruitspi_ruggedechotest.py b/examples/bluefruitspi_ruggedechotest.py new file mode 100644 index 0000000..27394cd --- /dev/null +++ b/examples/bluefruitspi_ruggedechotest.py @@ -0,0 +1,83 @@ +# A more 'rugged' echo test for the Feather M0 Bluefruit +# Sets the name, then echo's all RX'd data with a reversed packet +# we wrap the loop in a try/except block and have multiple loops +# and functions to keep our connection up and running no matter what + +import time +import busio +import board +from digitalio import DigitalInOut +from adafruit_bluefruitspi import BluefruitSPI + +ADVERT_NAME = b'BlinkaBLE' + +spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) +cs = DigitalInOut(board.D8) +irq = DigitalInOut(board.D7) +rst = DigitalInOut(board.D4) +bluefruit = BluefruitSPI(spi_bus, cs, irq, rst, debug=False) + +def init_bluefruit(): + # Initialize the device and perform a factory reset + print("Initializing the Bluefruit LE SPI Friend module") + bluefruit.init() + bluefruit.command_check_OK(b'AT+FACTORYRESET', delay=1) + # Print the response to 'ATI' (info request) as a string + print(str(bluefruit.command_check_OK(b'ATI'), 'utf-8')) + # Change advertised name + bluefruit.command_check_OK(b'AT+GAPDEVNAME='+ADVERT_NAME) + +def wait_for_connection(): + print("Waiting for a connection to Bluefruit LE Connect ...") + # Wait for a connection ... + dotcount = 0 + while not bluefruit.connected: + print(".", end="") + dotcount = (dotcount + 1) % 80 + if dotcount == 79: + print("") + time.sleep(0.5) + +# This code will check the connection but only query the module if it has been +# at least 'n_sec' seconds. Otherwise it 'caches' the response, to keep from +# hogging the Bluefruit connection with constant queries +connection_timestamp = None +is_connected = None +def check_connection(n_sec): + # pylint: disable=global-statement + global connection_timestamp, is_connected + if (not connection_timestamp) or (time.monotonic() - connection_timestamp > n_sec): + connection_timestamp = time.monotonic() + is_connected = bluefruit.connected + return is_connected + +# Unlike most circuitpython code, this runs in two loops +# one outer loop manages reconnecting bluetooth if we lose connection +# then one inner loop for doing what we want when connected! +while True: + # Initialize the module + try: # Wireless connections can have corrupt data or other runtime failures + # This try block will reset the module if that happens + init_bluefruit() + wait_for_connection() + print("\n *Connected!*") + + # Once connected, check for incoming BLE UART data + while check_connection(3): # Check our connection status every 3 seconds + # OK we're still connected, see if we have any data waiting + resp = bluefruit.uart_rx() + if not resp: + continue # nothin' + print("Read %d bytes: %s" % (len(resp), resp)) + # Now write it! + print("Writing reverse...") + send = [] + for i in range(len(resp), 0, -1): + send.append(resp[i-1]) + print(bytes(send)) + bluefruit.uart_tx(bytes(send)) + print("Connection lost.") + + except RuntimeError as e: + print(e) # Print what happened + continue # retry! diff --git a/examples/bluefruitspi_simpletest.py b/examples/bluefruitspi_simpletest.py index 4ef9017..58e2a17 100644 --- a/examples/bluefruitspi_simpletest.py +++ b/examples/bluefruitspi_simpletest.py @@ -1,8 +1,11 @@ +# A simple echo test for the Feather M0 Bluefruit +# Sets the name, then echo's all RX'd data with a reversed packet + +import time import busio -from digitalio import DigitalInOut, Direction import board -import time -from adafruit_bluefruitspi import BluefruitSPI, MsgType +from digitalio import DigitalInOut +from adafruit_bluefruitspi import BluefruitSPI spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) cs = DigitalInOut(board.D8) @@ -13,31 +16,46 @@ # Initialize the device and perform a factory reset print("Initializing the Bluefruit LE SPI Friend module") bluefruit.init() -bluefruit.command_check_OK("AT+FACTORYRESET", 1.0) -print(bluefruit.command_check_OK("ATI")) -bluefruit.command_check_OK("AT+GAPDEVNAME=ColorLamp") +bluefruit.command_check_OK(b'AT+FACTORYRESET', delay=1) + +# Print the response to 'ATI' (info request) as a string +print(str(bluefruit.command_check_OK(b'ATI'), 'utf-8')) + +# Change advertised name +bluefruit.command_check_OK(b'AT+GAPDEVNAME=BlinkaBLE') while True: - connected = False - dotcount = 0 print("Waiting for a connection to Bluefruit LE Connect ...") # Wait for a connection ... - while not connected: - connected = int(bluefruit.command_check_OK("AT+GAPGETCONN")) == 1 - dotcount += 1 + dotcount = 0 + while not bluefruit.connected: + print(".", end="") + dotcount = (dotcount + 1) % 80 if dotcount == 79: - print(".") - dotcount = 0 - else: - print(".", end="") + print("") time.sleep(0.5) + # Once connected, check for incoming BLE UART data - print("\nConnected!") - while connected: - #bluefruit.command_check_OK("AT+BLEUARTTX=0123456789") - resp = bluefruit.command_check_OK("AT+BLEUARTRX") - if resp: - print(resp) - # Check connection status followed by a 500ms delay - connected = int(bluefruit.command_check_OK("AT+GAPGETCONN", 0.5)) == 1 + print("\n *Connected!*") + connection_timestamp = time.monotonic() + while True: + # Check our connection status every 3 seconds + if time.monotonic() - connection_timestamp > 3: + connection_timestamp = time.monotonic() + if not bluefruit.connected: + break + + # OK we're still connected, see if we have any data waiting + resp = bluefruit.uart_rx() + if not resp: + continue # nothin' + print("Read %d bytes: %s" % (len(resp), resp)) + # Now write it! + print("Writing reverse...") + send = [] + for i in range(len(resp), 0, -1): + send.append(resp[i-1]) + print(bytes(send)) + bluefruit.uart_tx(bytes(send)) + print("Connection lost.")