Skip to content

Commit a2a6137

Browse files
committed
update to remove enum classes (they are not used externally), add some delays (now stable), RX/TX with bytes, and some packet parsing
1 parent 50d78b0 commit a2a6137

File tree

2 files changed

+157
-105
lines changed

2 files changed

+157
-105
lines changed

adafruit_bluefruitspi.py

Lines changed: 118 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -42,92 +42,63 @@
4242
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
4343
"""
4444

45-
# imports
46-
4745
__version__ = "0.0.0-auto.0"
4846
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BluefruitSPI.git"
4947

5048
import time
49+
import struct
5150
from digitalio import Direction, Pull
5251
from adafruit_bus_device.spi_device import SPIDevice
5352
from micropython import const
54-
import struct
55-
5653

57-
class MsgType: #pylint: disable=too-few-public-methods,bad-whitespace
58-
"""An enum-like class representing the possible message types.
59-
Possible values are:
60-
- ``MsgType.COMMAND``
61-
- ``MsgType.RESPONSE``
62-
- ``MsgType.ALERT``
63-
- ``MsgType.ERROR``
64-
"""
65-
COMMAND = const(0x10) # Command message
66-
RESPONSE = const(0x20) # Response message
67-
ALERT = const(0x40) # Alert message
68-
ERROR = const(0x80) # Error message
69-
70-
71-
class SDEPCommand: #pylint: disable=too-few-public-methods,bad-whitespace
72-
"""An enum-like class representing the possible SDEP commands.
73-
Possible values are:
74-
- ``SDEPCommand.INITIALIZE``
75-
- ``SDEPCommand.ATCOMMAND``
76-
- ``SDEPCommand.BLEUART_TX``
77-
- ``SDEPCommand.BLEUART_RX``
78-
"""
79-
INITIALIZE = const(0xBEEF) # Resets the Bluefruit device
80-
ATCOMMAND = const(0x0A00) # AT command wrapper
81-
BLEUART_TX = const(0x0A01) # BLE UART transmit data
82-
BLEUART_RX = const(0x0A02) # BLE UART read data
83-
84-
85-
class ArgType: #pylint: disable=too-few-public-methods,bad-whitespace
86-
"""An enum-like class representing the possible argument types.
87-
Possible values are
88-
- ``ArgType.STRING``
89-
- ``ArgType.BYTEARRAY``
90-
- ``ArgType.INT32``
91-
- ``ArgType.UINT32``
92-
- ``ArgType.INT16``
93-
- ``ArgType.UINT16``
94-
- ``ArgType.INT8``
95-
- ``ArgType.UINT8``
96-
"""
97-
STRING = const(0x0100) # String data type
98-
BYTEARRAY = const(0x0200) # Byte array data type
99-
INT32 = const(0x0300) # Signed 32-bit integer data type
100-
UINT32 = const(0x0400) # Unsigned 32-bit integer data type
101-
INT16 = const(0x0500) # Signed 16-bit integer data type
102-
UINT16 = const(0x0600) # Unsigned 16-bit integer data type
103-
INT8 = const(0x0700) # Signed 8-bit integer data type
104-
UINT8 = const(0x0800) # Unsigned 8-bit integer data type
105-
106-
107-
class ErrorCode: #pylint: disable=too-few-public-methods,bad-whitespace
108-
"""An enum-like class representing possible error codes.
109-
Possible values are
110-
- ``ErrorCode.``
111-
"""
112-
INVALIDMSGTYPE = const(0x8021) # SDEP: Unexpected SDEP MsgType
113-
INVALIDCMDID = const(0x8022) # SDEP: Unknown command ID
114-
INVALIDPAYLOAD = const(0x8023) # SDEP: Payload problem
115-
INVALIDLEN = const(0x8024) # SDEP: Indicated len too large
116-
INVALIDINPUT = const(0x8060) # AT: Invalid data
117-
UNKNOWNCMD = const(0x8061) # AT: Unknown command name
118-
INVALIDPARAM = const(0x8062) # AT: Invalid param value
119-
UNSUPPORTED = const(0x8063) # AT: Unsupported command
54+
# pylint: disable=bad-whitespace
55+
_MSG_COMMAND = const(0x10) # Command message
56+
_MSG_RESPONSE = const(0x20) # Response message
57+
_MSG_ALERT = const(0x40) # Alert message
58+
_MSG_ERROR = const(0x80) # Error message
59+
60+
_SDEP_INITIALIZE = const(0xBEEF) # Resets the Bluefruit device
61+
_SDEP_ATCOMMAND = const(0x0A00) # AT command wrapper
62+
_SDEP_BLEUART_TX = const(0x0A01) # BLE UART transmit data
63+
_SDEP_BLEUART_RX = const(0x0A02) # BLE UART read data
64+
65+
_ARG_STRING = const(0x0100) # String data type
66+
_ARG_BYTEARRAY = const(0x0200) # Byte array data type
67+
_ARG_INT32 = const(0x0300) # Signed 32-bit integer data type
68+
_ARG_UINT32 = const(0x0400) # Unsigned 32-bit integer data type
69+
_ARG_INT16 = const(0x0500) # Signed 16-bit integer data type
70+
_ARG_UINT16 = const(0x0600) # Unsigned 16-bit integer data type
71+
_ARG_INT8 = const(0x0700) # Signed 8-bit integer data type
72+
_ARG_UINT8 = const(0x0800) # Unsigned 8-bit integer data type
73+
74+
_ERROR_INVALIDMSGTYPE = const(0x8021) # SDEP: Unexpected SDEP MsgType
75+
_ERROR_INVALIDCMDID = const(0x8022) # SDEP: Unknown command ID
76+
_ERROR_INVALIDPAYLOAD = const(0x8023) # SDEP: Payload problem
77+
_ERROR_INVALIDLEN = const(0x8024) # SDEP: Indicated len too large
78+
_ERROR_INVALIDINPUT = const(0x8060) # AT: Invalid data
79+
_ERROR_UNKNOWNCMD = const(0x8061) # AT: Unknown command name
80+
_ERROR_INVALIDPARAM = const(0x8062) # AT: Invalid param value
81+
_ERROR_UNSUPPORTED = const(0x8063) # AT: Unsupported command
82+
83+
# For the Bluefruit Connect packets
84+
_PACKET_BUTTON_LEN = const(5)
85+
_PACKET_COLOR_LEN = const(6)
86+
87+
# pylint: enable=bad-whitespace
12088

12189

12290
class BluefruitSPI:
12391
"""Helper for the Bluefruit LE SPI Friend"""
12492

125-
def __init__(self, spi, cs, irq, reset, debug=False):
93+
def __init__(self, spi, cs, irq, reset, debug=False): # pylint: disable=too-many-arguments
12694
self._irq = irq
12795
self._buf_tx = bytearray(20)
12896
self._buf_rx = bytearray(20)
12997
self._debug = debug
13098

99+
# a cache of data, used for packet parsing
100+
self._buffer = []
101+
131102
# Reset
132103
reset.direction = Direction.OUTPUT
133104
reset.value = False
@@ -146,7 +117,7 @@ def __init__(self, spi, cs, irq, reset, debug=False):
146117
self._spi_device = SPIDevice(spi, cs,
147118
baudrate=4000000, phase=0, polarity=0)
148119

149-
def _cmd(self, cmd):
120+
def _cmd(self, cmd): # pylint: disable=too-many-branches
150121
"""
151122
Executes the supplied AT command, which must be terminated with
152123
a new-line character.
@@ -172,10 +143,13 @@ def _cmd(self, cmd):
172143
plen = 16
173144
# Note the 'more' value in bit 8 of the packet len
174145
struct.pack_into("<BHB16s", self._buf_tx, 0,
175-
MsgType.COMMAND, SDEPCommand.ATCOMMAND,
146+
_MSG_COMMAND, _SDEP_ATCOMMAND,
176147
plen | more, cmd[pos:pos+plen])
177148
if self._debug:
178149
print("Writing: ", [hex(b) for b in self._buf_tx])
150+
else:
151+
time.sleep(0.05)
152+
179153
# Update the position if there is data remaining
180154
pos += plen
181155

@@ -212,7 +186,8 @@ def _cmd(self, cmd):
212186
rsp += self._buf_rx[4:rsplen+4]
213187
if self._debug:
214188
print("Reading: ", [hex(b) for b in self._buf_rx])
215-
189+
else:
190+
time.sleep(0.05)
216191
# Clean up the response buffer
217192
if self._debug:
218193
print(rsp)
@@ -226,7 +201,7 @@ def init(self):
226201
"""
227202
# Construct the SDEP packet
228203
struct.pack_into("<BHB", self._buf_tx, 0,
229-
MsgType.COMMAND, SDEPCommand.INITIALIZE, 0)
204+
_MSG_COMMAND, _SDEP_INITIALIZE, 0)
230205
if self._debug:
231206
print("Writing: ", [hex(b) for b in self._buf_tx])
232207

@@ -237,35 +212,94 @@ def init(self):
237212
# Wait 1 second for the command to complete.
238213
time.sleep(1)
239214

240-
def uarttx(self, txt):
215+
@property
216+
def connected(self):
217+
"""Whether the Bluefruit module is connected to the central"""
218+
return int(self.command_check_OK(b'AT+GAPGETCONN')) == 1
219+
220+
def uart_tx(self, data):
241221
"""
242-
Sends the specific string out over BLE UART.
243-
:param txt: The new-line terminated string to send.
222+
Sends the specific bytestring out over BLE UART.
223+
:param data: The bytestring to send.
244224
"""
245-
return self._cmd("AT+BLEUARTTX="+txt+"\n")
225+
return self._cmd(b'AT+BLEUARTTX='+data+b'\r\n')
246226

247-
def uartrx(self):
227+
def uart_rx(self):
248228
"""
249-
Reads data from the BLE UART FIFO.
229+
Reads byte data from the BLE UART FIFO.
250230
"""
251-
return self._cmd("AT+BLEUARTRX\n")
231+
data = self.command_check_OK(b'AT+BLEUARTRX')
232+
if data:
233+
# remove \r\n from ending
234+
return data[:-2]
235+
return None
252236

253237
def command(self, string):
238+
"""Send a command and check response code"""
254239
try:
255240
msgtype, msgid, rsp = self._cmd(string+"\n")
256-
if msgtype == MsgType.ERROR:
241+
if msgtype == _MSG_ERROR:
257242
raise RuntimeError("Error (id:{0})".format(hex(msgid)))
258-
if msgtype == MsgType.RESPONSE:
243+
if msgtype == _MSG_RESPONSE:
259244
return rsp
245+
else:
246+
raise RuntimeError("Unknown response (id:{0})".format(hex(msgid)))
260247
except RuntimeError as error:
261248
raise RuntimeError("AT command failure: " + repr(error))
262249

263-
def command_check_OK(self, string, delay=0.0):
264-
ret = self.command(string)
250+
def command_check_OK(self, command, delay=0.0): # pylint: disable=invalid-name
251+
"""Send a fully formed bytestring AT command, and check
252+
whether we got an 'OK' back. Returns payload bytes if there is any"""
253+
ret = self.command(command)
265254
time.sleep(delay)
266255
if not ret or not ret[-4:]:
267256
raise RuntimeError("Not OK")
268257
if ret[-4:] != b'OK\r\n':
269258
raise RuntimeError("Not OK")
270259
if ret[:-4]:
271-
return str(ret[:-4], 'utf-8')
260+
return ret[:-4]
261+
return None
262+
263+
def read_packet(self): # pylint: disable=too-many-return-statements
264+
"""
265+
Will read a Bluefruit Connect packet and return it in a parsed format.
266+
Currently supports Button and Color packets only
267+
"""
268+
data = self.uart_rx()
269+
if not data:
270+
return None
271+
# convert to an array of character bytes
272+
self._buffer += [chr(b) for b in data]
273+
# Find beginning of new packet, starts with a '!'
274+
while self._buffer and self._buffer[0] != '!':
275+
self._buffer.pop(0)
276+
# we need at least 2 bytes in the buffer
277+
if len(self._buffer) < 2:
278+
return None
279+
280+
# Packet beginning found
281+
if self._buffer[1] == 'B':
282+
plen = _PACKET_BUTTON_LEN
283+
elif self._buffer[1] == 'C':
284+
plen = _PACKET_COLOR_LEN
285+
else:
286+
# unknown packet type
287+
self._buffer.pop(0)
288+
return None
289+
290+
# split packet off of buffer cache
291+
packet = self._buffer[0:plen]
292+
293+
self._buffer = self._buffer[plen:] # remove packet from buffer
294+
if sum([ord(x) for x in packet]) % 256 != 255: # check sum
295+
return None
296+
297+
# OK packet's good!
298+
if packet[1] == 'B': # buttons have 2 int args to parse
299+
# button number & True/False press
300+
return ('B', int(packet[2]), packet[3] == '1')
301+
if packet[1] == 'C': # colorpick has 3 int args to parse
302+
# red, green and blue
303+
return ('C', ord(packet[2]), ord(packet[3]), ord(packet[4]))
304+
# We don't nicely parse this yet
305+
return packet[1:-1]

examples/bluefruitspi_simpletest.py

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
# A simple echo test for the Feather M0 Bluefruit
2+
# Sets the name, then
3+
14
import busio
25
from digitalio import DigitalInOut, Direction
36
import board
47
import time
5-
from adafruit_bluefruitspi import BluefruitSPI, MsgType
8+
from adafruit_bluefruitspi import BluefruitSPI
69

710
spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
811
cs = DigitalInOut(board.D8)
@@ -13,31 +16,46 @@
1316
# Initialize the device and perform a factory reset
1417
print("Initializing the Bluefruit LE SPI Friend module")
1518
bluefruit.init()
16-
bluefruit.command_check_OK("AT+FACTORYRESET", 1.0)
17-
print(bluefruit.command_check_OK("ATI"))
18-
bluefruit.command_check_OK("AT+GAPDEVNAME=ColorLamp")
19+
bluefruit.command_check_OK(b'AT+FACTORYRESET', delay=1)
20+
21+
# Print the response to 'ATI' (info request) as a string
22+
print(str(bluefruit.command_check_OK(b'ATI'), 'utf-8'))
23+
24+
# Change advertised name
25+
bluefruit.command_check_OK(b'AT+GAPDEVNAME=BlinkaBLE')
1926

2027
while True:
21-
connected = False
22-
dotcount = 0
2328
print("Waiting for a connection to Bluefruit LE Connect ...")
2429
# Wait for a connection ...
25-
while not connected:
26-
connected = int(bluefruit.command_check_OK("AT+GAPGETCONN")) == 1
27-
dotcount += 1
30+
dotcount = 0
31+
while not bluefruit.connected:
32+
print(".", end="")
33+
dotcount = (dotcount + 1) % 80
2834
if dotcount == 79:
29-
print(".")
30-
dotcount = 0
31-
else:
32-
print(".", end="")
35+
print("")
3336
time.sleep(0.5)
37+
3438
# Once connected, check for incoming BLE UART data
35-
print("\nConnected!")
36-
while connected:
37-
#bluefruit.command_check_OK("AT+BLEUARTTX=0123456789")
38-
resp = bluefruit.command_check_OK("AT+BLEUARTRX")
39-
if resp:
40-
print(resp)
41-
# Check connection status followed by a 500ms delay
42-
connected = int(bluefruit.command_check_OK("AT+GAPGETCONN", 0.5)) == 1
39+
print("\n *Connected!*")
40+
connection_timestamp = time.monotonic()
41+
while True:
42+
# Check our connection status every 3 seconds
43+
if time.monotonic() - connection_timestamp > 3:
44+
connection_timestamp = time.monotonic()
45+
if not bluefruit.connected:
46+
break
47+
48+
# OK we're still connected, see if we have any data waiting
49+
resp = bluefruit.uart_rx()
50+
if not resp:
51+
continue # nothin'
52+
print("Read %d bytes: %s" % (len(resp), resp))
53+
# Now write it!
54+
print("Writing reverse...")
55+
send = []
56+
for i in range(len(resp), 0, -1):
57+
send.append(resp[i-1])
58+
print(bytes(send))
59+
bluefruit.uart_tx(bytes(send))
60+
4361
print("Connection lost.")

0 commit comments

Comments
 (0)