42
42
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
43
43
"""
44
44
45
- # imports
46
-
47
45
__version__ = "0.0.0-auto.0"
48
46
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_BluefruitSPI.git"
49
47
50
48
import time
49
+ import struct
51
50
from digitalio import Direction , Pull
52
51
from adafruit_bus_device .spi_device import SPIDevice
53
52
from micropython import const
54
- import struct
55
-
56
53
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
120
88
121
89
122
90
class BluefruitSPI :
123
91
"""Helper for the Bluefruit LE SPI Friend"""
124
92
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
126
94
self ._irq = irq
127
95
self ._buf_tx = bytearray (20 )
128
96
self ._buf_rx = bytearray (20 )
129
97
self ._debug = debug
130
98
99
+ # a cache of data, used for packet parsing
100
+ self ._buffer = []
101
+
131
102
# Reset
132
103
reset .direction = Direction .OUTPUT
133
104
reset .value = False
@@ -146,7 +117,7 @@ def __init__(self, spi, cs, irq, reset, debug=False):
146
117
self ._spi_device = SPIDevice (spi , cs ,
147
118
baudrate = 4000000 , phase = 0 , polarity = 0 )
148
119
149
- def _cmd (self , cmd ):
120
+ def _cmd (self , cmd ): # pylint: disable=too-many-branches
150
121
"""
151
122
Executes the supplied AT command, which must be terminated with
152
123
a new-line character.
@@ -172,16 +143,19 @@ def _cmd(self, cmd):
172
143
plen = 16
173
144
# Note the 'more' value in bit 8 of the packet len
174
145
struct .pack_into ("<BHB16s" , self ._buf_tx , 0 ,
175
- MsgType . COMMAND , SDEPCommand . ATCOMMAND ,
146
+ _MSG_COMMAND , _SDEP_ATCOMMAND ,
176
147
plen | more , cmd [pos :pos + plen ])
177
148
if self ._debug :
178
149
print ("Writing: " , [hex (b ) for b in self ._buf_tx ])
150
+ else :
151
+ time .sleep (0.05 )
152
+
179
153
# Update the position if there is data remaining
180
154
pos += plen
181
155
182
156
# Send out the SPI bus
183
157
with self ._spi_device as spi :
184
- spi .write (self ._buf_tx , end = len (cmd ) + 4 )
158
+ spi .write (self ._buf_tx , end = len (cmd ) + 4 ) # pylint: disable=no-member
185
159
186
160
# Wait up to 200ms for a response
187
161
timeout = 0.2
@@ -212,7 +186,8 @@ def _cmd(self, cmd):
212
186
rsp += self ._buf_rx [4 :rsplen + 4 ]
213
187
if self ._debug :
214
188
print ("Reading: " , [hex (b ) for b in self ._buf_rx ])
215
-
189
+ else :
190
+ time .sleep (0.05 )
216
191
# Clean up the response buffer
217
192
if self ._debug :
218
193
print (rsp )
@@ -226,46 +201,105 @@ def init(self):
226
201
"""
227
202
# Construct the SDEP packet
228
203
struct .pack_into ("<BHB" , self ._buf_tx , 0 ,
229
- MsgType . COMMAND , SDEPCommand . INITIALIZE , 0 )
204
+ _MSG_COMMAND , _SDEP_INITIALIZE , 0 )
230
205
if self ._debug :
231
206
print ("Writing: " , [hex (b ) for b in self ._buf_tx ])
232
207
233
208
# Send out the SPI bus
234
209
with self ._spi_device as spi :
235
- spi .write (self ._buf_tx , end = 4 )
210
+ spi .write (self ._buf_tx , end = 4 ) # pylint: disable=no-member
236
211
237
212
# Wait 1 second for the command to complete.
238
213
time .sleep (1 )
239
214
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 ):
241
221
"""
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.
244
224
"""
245
- return self ._cmd (" AT+BLEUARTTX=" + txt + " \n " )
225
+ return self ._cmd (b' AT+BLEUARTTX=' + data + b' \r \n ' )
246
226
247
- def uartrx (self ):
227
+ def uart_rx (self ):
248
228
"""
249
- Reads data from the BLE UART FIFO.
229
+ Reads byte data from the BLE UART FIFO.
250
230
"""
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
252
236
253
237
def command (self , string ):
238
+ """Send a command and check response code"""
254
239
try :
255
240
msgtype , msgid , rsp = self ._cmd (string + "\n " )
256
- if msgtype == MsgType . ERROR :
241
+ if msgtype == _MSG_ERROR :
257
242
raise RuntimeError ("Error (id:{0})" .format (hex (msgid )))
258
- if msgtype == MsgType . RESPONSE :
243
+ if msgtype == _MSG_RESPONSE :
259
244
return rsp
245
+ else :
246
+ raise RuntimeError ("Unknown response (id:{0})" .format (hex (msgid )))
260
247
except RuntimeError as error :
261
248
raise RuntimeError ("AT command failure: " + repr (error ))
262
249
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 )
265
254
time .sleep (delay )
266
255
if not ret or not ret [- 4 :]:
267
256
raise RuntimeError ("Not OK" )
268
257
if ret [- 4 :] != b'OK\r \n ' :
269
258
raise RuntimeError ("Not OK" )
270
259
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 ]
0 commit comments