Voting resources, early voting, and poll worker information - VOTE. ... Adafruit is open and shipping.
0

Bluefruit SPI Friend/Feather M4 Express AT Commands failing
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Bluefruit SPI Friend/Feather M4 Express AT Commands failing

by Floor1984 on Thu Oct 24, 2019 12:10 pm

Hello,
CircuitPython 4.1
I have been trying to get get my feather M4 Express to communicate with my Bluefruit BLE SPI Friend.
When running any AT commands in console or through the bluefruitspi_simpletest I get the following errors. I have checked my wiring twice and will do so again.

"AT command failure: RuntimeError('buffer size must match format',)"

I have updated the Bluefruit module using Adafruit BLE Connect android AP.

Any help would be appreciated.

Thank you,
B


import time
import busio
import board
from digitalio import DigitalInOut
from adafruit_bluefruitspi import BluefruitSPI

spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
cs = DigitalInOut(board.D9)
irq = DigitalInOut(board.D6)
rst = DigitalInOut(board.D5)
bluefruit = BluefruitSPI(spi_bus, cs, irq, rst, debug=False)

# Initialize the device and perform a factory reset
print("Initializing the Bluefruit LE SPI Friend module")
bluefruit.init()

>>> print(str(bluefruit.command_check_OK(b'ATI'), 'utf-8'))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "adafruit_bluefruitspi.py", line 253, in command_check_OK
File "adafruit_bluefruitspi.py", line 248, in command
File "adafruit_bluefruitspi.py", line 248, in command
RuntimeError: AT command failure: RuntimeError('buffer size must match format',)

Floor1984
 
Posts: 2
Joined: Thu Oct 24, 2019 11:51 am

Re: Bluefruit SPI Friend/Feather M4 Express AT Commands fail

by Floor1984 on Fri Oct 25, 2019 8:33 am

Additional information -
Tried a second Feather M4 Express with CircuitPython 4.1 and the appropriate 4.x libraries. The result was the same error as above.
Tried CircuitPython 5.0.0.A4 with the 5.0.0 libraries, this too had the same error.

Floor1984
 
Posts: 2
Joined: Thu Oct 24, 2019 11:51 am

Re: Bluefruit SPI Friend/Feather M4 Express AT Commands fail

by mwhuffnagle on Sun Feb 23, 2020 7:32 pm

I think I've come up with a fix for the "buffer size must match format" error.

I'm very new to Python but have a good bit of experience programming in other languages.

I was able to trace that error back into the adafruit_bluefruitspi (.py) file. The "buffer size..." error was being thrown by the struct.unpack method in the _cmd method of bluefruitspi. The .unpack appeared to be asking for 4 bytes but the _buf_rx buffer was defined as 20 bytes. I added a few lines to copy the first 4 bytes of _buf_rx to a temporary bytearray called _hdr and then told .unpack to use that instead of the whole _buf_rx bytearray. That got past the "buffer size..." error.

Once that worked, it turned out that the .unpack was not returning the length of the response from the BLE in resplen so the later code said that the AT command had failed. I explicitly pulled the 1 byte response length value and stored that in resplen. From that point on, it appears to have been working.

Listed below should be a copy of the whole adafruit_bluefruitspi (.py) file that is working for me.

Mike

Code: Select all | TOGGLE FULL SIZE
# The MIT License (MIT)
#
# Copyright (c) 2018 Kevin Townsend for 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
# 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,
# F.ITNESS 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.
"""
`adafruit_bluefruitspi`
====================================================

Helper class to work with the Adafruit Bluefruit LE SPI friend breakout.

* Author(s): Kevin Townsend

Implementation Notes
--------------------

**Hardware:**

"* `Adafruit Bluefruit LE SPI Friend <https://www.adafruit.com/product/2633>`_"

**Software and Dependencies:**

* Adafruit CircuitPython firmware for the supported boards:
  https://github.com/adafruit/circuitpython/releases

* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
"""

__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

# 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): # pylint: disable=too-many-arguments
        self._irq = irq
        self._buf_tx = bytearray(20)
        self._buf_rx = bytearray(20)
        self._debug = debug
        self._hdr = bytearray(4)

        # a cache of data, used for packet parsing
        self._buffer = []

        # Reset
        reset.direction = Direction.OUTPUT
        reset.value = False
        time.sleep(0.01)
        reset.value = True
        time.sleep(0.5)

        # CS is an active low output
        cs.direction = Direction.OUTPUT
        cs.value = True

        # irq line is active high input, so set a pulldown as a precaution
        self._irq.direction = Direction.INPUT
        self._irq.pull = Pull.DOWN

        self._spi_device = SPIDevice(spi, cs,
                                     baudrate=4000000, phase=0, polarity=0)

    def _cmd(self, cmd):  # pylint: disable=too-many-branches
        """
        Executes the supplied AT command, which must be terminated with
        a new-line character.
        Returns msgtype, rspid, rsp, which are 8-bit int, 16-bit int and a
        bytearray.
        :param cmd: The new-line terminated AT command to execute.
        """
       
        # Make sure we stay within the 255 byte limit
        if len(cmd) > 127:
            if self._debug:
                print("ERROR: Command too long.")
            raise ValueError('Command too long.')

        more = 0x80 # More bit is in pos 8, 1 = more data available
        pos = 0
        while len(cmd) - pos:
            # Construct the SDEP packet
            if len(cmd) - pos <= 16:
                # Last or sole packet
                more = 0
            plen = len(cmd) - pos
            if plen > 16:
                plen = 16
            # Note the 'more' value in bit 8 of the packet len
            struct.pack_into("<BHB16s", self._buf_tx, 0,
                             _MSG_COMMAND, _SDEP_ATCOMMAND,
                             plen | more, cmd[pos:pos+plen])
            if self._debug:
                print("")
#                print("Writing: ", [hex(b) for b in self._buf_tx])
                print("Writing: ", [chr(b) for b in self._buf_tx])
                print("")
            else:
                time.sleep(0.05)

            # Update the position if there is data remaining
            pos += plen

            # Send out the SPI bus
            with self._spi_device as spi:
                spi.write(self._buf_tx, end=len(cmd) + 4) # pylint: disable=no-member

# Wait up to 200ms for a response
        timeout = 0.2
        while timeout > 0 and not self._irq.value:
            time.sleep(0.01)
            timeout -= 0.01
        if timeout <= 0:
            if self._debug:
                print("ERROR: Timed out waiting for a response.")
            raise RuntimeError('Timed out waiting for a response.')

        # Retrieve the response message
        msgtype = 0
        rspid = 0
        rsplen = 0
        rsp = b""
        while self._irq.value is True:
            # Read the current response packet
            time.sleep(0.01)
            with self._spi_device as spi:
                spi.readinto(self._buf_rx)

            # Read the message envelope and contents

            for p in range (0,3):
                self._hdr[p] = self._buf_rx[p]
               
            msgtype, rspid, rsplen = struct.unpack('>BHB', self._hdr)
            rsplen = self._buf_rx[3]

            if rsplen >= 16:
                rsp += self._buf_rx[4:20]
            else:
                rsp += self._buf_rx[4:rsplen+4]
            if self._debug:
                print("Reading: ", [hex(b) for b in self._buf_rx])
            else:
                time.sleep(0.05)
        # Clean up the response buffer
        if self._debug:
            print(rsp)

        return msgtype, rspid, rsp

    def init(self):
        """
        Sends the SDEP initialize command, which causes the board to reset.
        This command should complete in under 1s.
        """
        # Construct the SDEP packet
        struct.pack_into("<BHB", self._buf_tx, 0,
                         _MSG_COMMAND, _SDEP_INITIALIZE, 0)
        if self._debug:
            print("Writing: ", [hex(b) for b in self._buf_tx])

        # Send out the SPI bus
        with self._spi_device as spi:
            spi.write(self._buf_tx, end=4) # pylint: disable=no-member

        # Wait 1 second for the command to complete.
        time.sleep(1)

    @property
    def connected(self):
        """Whether the Bluefruit module is connected to the central"""
        return int(self.command_check_OK(b'AT+GAPGETCONN')) == 1

    def uart_tx(self, data):
        """
        Sends the specific bytestring out over BLE UART.
        :param data: The bytestring to send.
        """
        return self._cmd(b'AT+BLEUARTTX='+data+b'\r\n')

    def uart_rx(self):
        """
        Reads byte data from the BLE UART FIFO.
        """
        data = self.command_check_OK(b'AT+BLEUARTRX')
        if data:
            # remove \r\n from ending
            return data[:-2]
        return None

    def command(self, string):
        """Send a command and check response code"""
        try:
            msgtype, msgid, rsp = self._cmd(string+"\n")
            if msgtype == _MSG_ERROR:
                raise RuntimeError("Error (id:{0})".format(hex(msgid)))
            if msgtype == _MSG_RESPONSE:
                return rsp
            else:
                raise RuntimeError("Unknown response (id:{0})".format(hex(msgid)))
        except RuntimeError as error:
            raise RuntimeError("AT command failure: " + repr(error))

    def command_check_OK(self, command, delay=0.0):   # pylint: disable=invalid-name
        """Send a fully formed bytestring AT command, and check
        whether we got an 'OK' back. Returns payload bytes if there is any"""
        ret = self.command(command)
        time.sleep(delay)
        if not ret or not ret[-4:]:
            raise RuntimeError("Not OK")
        if ret[-4:] != b'OK\r\n':
            raise RuntimeError("Not OK")
        if ret[:-4]:
            return ret[:-4]
        return None

    def read_packet(self):   # pylint: disable=too-many-return-statements
        """
        Will read a Bluefruit Connect packet and return it in a parsed format.
        Currently supports Button and Color packets only
        """
        data = self.uart_rx()
        if not data:
            return None
        # convert to an array of character bytes
        self._buffer += [chr(b) for b in data]
        # Find beginning of new packet, starts with a '!'
        while self._buffer and self._buffer[0] != '!':
            self._buffer.pop(0)
        # we need at least 2 bytes in the buffer
        if len(self._buffer) < 2:
            return None

        # Packet beginning found
        if self._buffer[1] == 'B':
            plen = _PACKET_BUTTON_LEN
        elif self._buffer[1] == 'C':
            plen = _PACKET_COLOR_LEN
        else:
            # unknown packet type
            self._buffer.pop(0)
            return None

        # split packet off of buffer cache
        packet = self._buffer[0:plen]

        self._buffer = self._buffer[plen:]    # remove packet from buffer
        if sum([ord(x) for x in packet]) % 256 != 255: # check sum
            return None

        # OK packet's good!
        if packet[1] == 'B':  # buttons have 2 int args to parse
            # button number & True/False press
            return ('B', int(packet[2]), packet[3] == '1')
        if packet[1] == 'C':  # colorpick has 3 int args to parse
            # red, green and blue
            return ('C', ord(packet[2]), ord(packet[3]), ord(packet[4]))
        # We don't nicely parse this yet
        return packet[1:-1]

mwhuffnagle
 
Posts: 5
Joined: Sat May 27, 2017 9:59 pm

Re: Bluefruit SPI Friend/Feather M4 Express AT Commands fail

by danhalbert on Sun Feb 23, 2020 10:03 pm

mwhuffnagle - Thank you very much! Would you be willing to file an issue (or even a pull request) for the fix at https://github.com/adafruit/Adafruit_Ci ... uefruitSPI ?

danhalbert
 
Posts: 2138
Joined: Tue Aug 08, 2017 12:37 pm

Please be positive and constructive with your questions and comments.