Skip to content

Commit 7b7fbe4

Browse files
committed
try to minimize allocations
1 parent 7f9e3e3 commit 7b7fbe4

File tree

2 files changed

+99
-47
lines changed

2 files changed

+99
-47
lines changed

adafruit_esp32spi/adafruit_esp32spi.py

Lines changed: 72 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ def __init__(self, spi, cs_pin, ready_pin, reset_pin, gpio0_pin, *, debug=False)
127127
self._debug = debug
128128
self._buffer = bytearray(10)
129129
self._pbuf = bytearray(1) # buffer for param read
130+
self._sendbuf = bytearray(256) # buffer for command sending
131+
self._socknum_ll = [[0]] # pre-made list of list of socket #
130132

131133
self._spi_device = SPIDevice(spi, cs_pin, baudrate=8000000)
132134
self._cs = cs_pin
@@ -155,41 +157,57 @@ def reset(self):
155157

156158
def _wait_for_ready(self):
157159
"""Wait until the ready pin goes low"""
158-
if self._debug:
160+
if self._debug >= 3:
159161
print("Wait for ESP32 ready", end='')
160162
times = time.monotonic()
161163
while (time.monotonic() - times) < 10: # wait up to 10 seconds
162164
if not self._ready.value: # we're ready!
163165
break
164-
if self._debug:
166+
if self._debug >= 3:
165167
print('.', end='')
168+
time.sleep(0.05)
166169
else:
167170
raise RuntimeError("ESP32 not responding")
168-
if self._debug:
171+
if self._debug >= 3:
169172
print()
170173

171174

172175
def _send_command(self, cmd, params=None, *, param_len_16=False):
173176
"""Send over a command with a list of parameters"""
177+
174178
if not params:
175-
params = []
176-
packet = []
177-
packet.append(_START_CMD)
178-
packet.append(cmd & ~_REPLY_FLAG)
179-
packet.append(len(params))
179+
params = ()
180+
181+
packet_len = 4 # header + end byte
182+
for i, param in enumerate(params):
183+
packet_len += len(param) # parameter
184+
packet_len += 1 # size byte
185+
if param_len_16:
186+
packet_len += 1 # 2 of em here!
187+
while packet_len % 4 != 0:
188+
packet_len += 1
189+
# we may need more space
190+
if packet_len > len(self._sendbuf):
191+
self._sendbuf = bytearray(packet_len)
192+
193+
self._sendbuf[0] = _START_CMD
194+
self._sendbuf[1] = cmd & ~_REPLY_FLAG
195+
self._sendbuf[2] = len(params)
180196

181197
# handle parameters here
198+
ptr = 3
182199
for i, param in enumerate(params):
183200
if self._debug >= 2:
184201
print("\tSending param #%d is %d bytes long" % (i, len(param)))
185202
if param_len_16:
186-
packet.append((len(param) >> 8) & 0xFF)
187-
packet.append(len(param) & 0xFF)
188-
packet += (param)
189-
190-
packet.append(_END_CMD)
191-
while len(packet) % 4 != 0:
192-
packet.append(0xFF)
203+
self._sendbuf[ptr] = (len(param) >> 8) & 0xFF
204+
ptr += 1
205+
self._sendbuf[ptr] = len(param) & 0xFF
206+
ptr += 1
207+
for i, p in enumerate(param):
208+
self._sendbuf[ptr+i] = p
209+
ptr += len(param)
210+
self._sendbuf[ptr] = _END_CMD
193211

194212
self._wait_for_ready()
195213
with self._spi_device as spi:
@@ -199,17 +217,25 @@ def _send_command(self, cmd, params=None, *, param_len_16=False):
199217
break
200218
else:
201219
raise RuntimeError("ESP32 timed out on SPI select")
202-
spi.write(bytearray(packet)) # pylint: disable=no-member
203-
if self._debug:
204-
print("Wrote: ", [hex(b) for b in packet])
220+
spi.write(self._sendbuf, start=0, end=packet_len) # pylint: disable=no-member
221+
if self._debug >= 3:
222+
print("Wrote: ", [hex(b) for b in self._sendbuf[0:packet_len]])
205223

206224
def _read_byte(self, spi):
207225
"""Read one byte from SPI"""
208226
spi.readinto(self._pbuf)
209-
if self._debug >= 2:
227+
if self._debug >= 3:
210228
print("\t\tRead:", hex(self._pbuf[0]))
211229
return self._pbuf[0]
212230

231+
def _read_bytes(self, spi, buffer, start=0, end=None):
232+
"""Read many bytes from SPI"""
233+
if not end:
234+
end = len(buffer)
235+
spi.readinto(buffer, start=start, end=end)
236+
if self._debug >= 3:
237+
print("\t\tRead:", [hex(i) for i in buffer])
238+
213239
def _wait_spi_char(self, spi, desired):
214240
"""Read a byte with a time-out, and if we get it, check that its what we expect"""
215241
times = time.monotonic()
@@ -247,19 +273,18 @@ def _wait_response_cmd(self, cmd, num_responses=None, *, param_len_16=False):
247273
else:
248274
num_responses = self._read_byte(spi)
249275
for num in range(num_responses):
250-
response = []
251276
param_len = self._read_byte(spi)
252277
if param_len_16:
253278
param_len <<= 8
254279
param_len |= self._read_byte(spi)
255280
if self._debug >= 2:
256281
print("\tParameter #%d length is %d" % (num, param_len))
257-
for _ in range(param_len):
258-
response.append(self._read_byte(spi))
259-
responses.append(bytes(response))
282+
response = bytearray(param_len)
283+
self._read_bytes(spi, response)
284+
responses.append(response)
260285
self._check_data(spi, _END_CMD)
261286

262-
if self._debug:
287+
if self._debug >= 2:
263288
print("Read %d: " % len(responses[0]), responses)
264289
return responses
265290

@@ -280,7 +305,7 @@ def status(self):
280305
print("Connection status")
281306
resp = self._send_command_get_response(_GET_CONN_STATUS_CMD)
282307
if self._debug:
283-
print("Status:", resp[0][0])
308+
print("Conn status:", resp[0][0])
284309
return resp[0][0] # one byte response
285310

286311
@property
@@ -317,9 +342,9 @@ def get_scan_networks(self):
317342
APs = [] # pylint: disable=invalid-name
318343
for i, name in enumerate(names):
319344
a_p = {'ssid': name}
320-
rssi = self._send_command_get_response(_GET_IDX_RSSI_CMD, [[i]])[0]
345+
rssi = self._send_command_get_response(_GET_IDX_RSSI_CMD, ((i,),))[0]
321346
a_p['rssi'] = struct.unpack('<i', rssi)[0]
322-
encr = self._send_command_get_response(_GET_IDX_ENCT_CMD, [[i]])[0]
347+
encr = self._send_command_get_response(_GET_IDX_ENCT_CMD, ((i,),))[0]
323348
a_p['encryption'] = encr[0]
324349
APs.append(a_p)
325350
return APs
@@ -428,7 +453,7 @@ def get_host_by_name(self, hostname):
428453
print("*** Get host by name")
429454
if isinstance(hostname, str):
430455
hostname = bytes(hostname, 'utf-8')
431-
resp = self._send_command_get_response(_REQ_HOST_BY_NAME_CMD, [hostname])
456+
resp = self._send_command_get_response(_REQ_HOST_BY_NAME_CMD, (hostname,))
432457
if resp[0][0] != 1:
433458
raise RuntimeError("Failed to request hostname")
434459
resp = self._send_command_get_response(_GET_HOST_BY_NAME_CMD)
@@ -441,7 +466,7 @@ def ping(self, dest, ttl=250):
441466
dest = self.get_host_by_name(dest)
442467
# ttl must be between 0 and 255
443468
ttl = max(0, min(ttl, 255))
444-
resp = self._send_command_get_response(_PING_CMD, [dest, [ttl]])
469+
resp = self._send_command_get_response(_PING_CMD, (dest, (ttl)))
445470
return struct.unpack('<H', resp[0])[0]
446471

447472
def get_socket(self):
@@ -462,17 +487,18 @@ def socket_open(self, socket_num, dest, port, conn_mode=TCP_MODE):
462487
using the ESP32's internal reference number. By default we use
463488
'conn_mode' TCP_MODE but can also use UDP_MODE or TLS_MODE
464489
(dest must be hostname for TLS_MODE!)"""
490+
self._socknum_ll[0][0] = socket_num
465491
if self._debug:
466492
print("*** Open socket")
467493
port_param = struct.pack('>H', port)
468494
if isinstance(dest, str): # use the 5 arg version
469495
dest = bytes(dest, 'utf-8')
470496
resp = self._send_command_get_response(_START_CLIENT_TCP_CMD,
471-
[dest, b'\x00\x00\x00\x00',
472-
port_param, [socket_num], [conn_mode]])
497+
(dest, b'\x00\x00\x00\x00',
498+
port_param, self._socknum_ll[0], (conn_mode,)))
473499
else: # ip address, use 4 arg vesion
474500
resp = self._send_command_get_response(_START_CLIENT_TCP_CMD,
475-
[dest, port_param, [socket_num], [conn_mode]])
501+
(dest, port_param, self._socknum_ll[0], (conn_mode,)))
476502
if resp[0][0] != 1:
477503
raise RuntimeError("Could not connect to remote server")
478504

@@ -481,7 +507,8 @@ def socket_status(self, socket_num):
481507
SOCKET_SYN_SENT, SOCKET_SYN_RCVD, SOCKET_ESTABLISHED, SOCKET_FIN_WAIT_1,
482508
SOCKET_FIN_WAIT_2, SOCKET_CLOSE_WAIT, SOCKET_CLOSING, SOCKET_LAST_ACK, or
483509
SOCKET_TIME_WAIT"""
484-
resp = self._send_command_get_response(_GET_CLIENT_STATE_TCP_CMD, [[socket_num]])
510+
self._socknum_ll[0][0] = socket_num
511+
resp = self._send_command_get_response(_GET_CLIENT_STATE_TCP_CMD, self._socknum_ll)
485512
return resp[0][0]
486513

487514
def socket_connected(self, socket_num):
@@ -492,35 +519,38 @@ def socket_write(self, socket_num, buffer):
492519
"""Write the bytearray buffer to a socket"""
493520
if self._debug:
494521
print("Writing:", buffer)
522+
self._socknum_ll[0][0] = socket_num
495523
resp = self._send_command_get_response(_SEND_DATA_TCP_CMD,
496-
[[socket_num], buffer],
524+
(self._socknum_ll[0], buffer),
497525
sent_param_len_16=True)
498526

499527
sent = resp[0][0]
500528
if sent != len(buffer):
501529
raise RuntimeError("Failed to send %d bytes (sent %d)" % (len(buffer), sent))
502530

503-
resp = self._send_command_get_response(_DATA_SENT_TCP_CMD, [[socket_num]])
531+
resp = self._send_command_get_response(_DATA_SENT_TCP_CMD, self._socknum_ll)
504532
if resp[0][0] != 1:
505533
raise RuntimeError("Failed to verify data sent")
506534

507535
def socket_available(self, socket_num):
508536
"""Determine how many bytes are waiting to be read on the socket"""
509-
resp = self._send_command_get_response(_AVAIL_DATA_TCP_CMD, [[socket_num]])
537+
self._socknum_ll[0][0] = socket_num
538+
resp = self._send_command_get_response(_AVAIL_DATA_TCP_CMD, self._socknum_ll)
510539
reply = struct.unpack('<H', resp[0])[0]
511540
if self._debug:
512-
print("%d bytes available" % reply)
541+
print("ESPSocket: %d bytes available" % reply)
513542
return reply
514543

515544
def socket_read(self, socket_num, size):
516545
"""Read up to 'size' bytes from the socket number. Returns a bytearray"""
517546
if self._debug:
518-
print("Reading %d bytes from socket with status %d" %
547+
print("Reading %d bytes from ESP socket with status %d" %
519548
(size, self.socket_status(socket_num)))
549+
self._socknum_ll[0][0] = socket_num
520550
resp = self._send_command_get_response(_GET_DATABUF_TCP_CMD,
521-
[[socket_num], [size & 0xFF, (size >> 8) & 0xFF]],
551+
(self._socknum_ll[0], (size & 0xFF, (size >> 8) & 0xFF)),
522552
sent_param_len_16=True, recv_param_len_16=True)
523-
return resp[0]
553+
return bytes(resp[0])
524554

525555
def socket_connect(self, socket_num, dest, port, conn_mode=TCP_MODE):
526556
"""Open and verify we connected a socket to a destination IP address or hostname
@@ -540,6 +570,7 @@ def socket_connect(self, socket_num, dest, port, conn_mode=TCP_MODE):
540570

541571
def socket_close(self, socket_num):
542572
"""Close a socket using the ESP32's internal reference number"""
543-
resp = self._send_command_get_response(_STOP_CLIENT_TCP_CMD, [[socket_num]])
573+
self._socknum_ll[0][0] = socket_num
574+
resp = self._send_command_get_response(_STOP_CLIENT_TCP_CMD, self._socknum_ll)
544575
if resp[0][0] != 1:
545576
raise RuntimeError("Failed to close socket")

adafruit_esp32spi/adafruit_esp32spi_socket.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232

3333
import time
34+
import gc
3435
from micropython import const
3536

3637
_the_interface = None # pylint: disable=invalid-name
@@ -77,45 +78,65 @@ def connect(self, address, conntype=None):
7778
def write(self, data): # pylint: disable=no-self-use
7879
"""Send some data to the socket"""
7980
_the_interface.socket_write(self._socknum, data)
81+
gc.collect()
8082

8183
def readline(self):
8284
"""Attempt to return as many bytes as we can up to but not including '\r\n'"""
85+
print("Socket readline")
8386
while b'\r\n' not in self._buffer:
8487
# there's no line already in there, read some more
8588
avail = min(_the_interface.socket_available(self._socknum), 1500)
8689
if avail:
8790
self._buffer += _the_interface.socket_read(self._socknum, avail)
8891
firstline, self._buffer = self._buffer.split(b'\r\n', 1)
92+
gc.collect()
8993
return firstline
9094

9195
def read(self, size=0):
9296
"""Read up to 'size' bytes from the socket, this may be buffered internally!
9397
If 'size' isnt specified, return everything in the buffer."""
98+
print("Socket read", size)
9499
if size == 0: # read as much as we can at the moment
95100
while True:
96101
avail = min(_the_interface.socket_available(self._socknum), 1500)
97102
if avail:
98103
self._buffer += _the_interface.socket_read(self._socknum, avail)
99104
else:
100105
break
106+
gc.collect()
101107
ret = self._buffer
102108
self._buffer = b''
109+
gc.collect()
103110
return ret
104111
stamp = time.monotonic()
105-
while len(self._buffer) < size:
112+
113+
to_read = size - len(self._buffer)
114+
received = []
115+
while to_read > 0:
116+
#print("Bytes to read:", to_read)
106117
avail = min(_the_interface.socket_available(self._socknum), 1500)
107118
if avail:
108119
stamp = time.monotonic()
109-
self._buffer += _the_interface.socket_read(self._socknum, min(size, avail))
120+
recv = _the_interface.socket_read(self._socknum, min(to_read, avail))
121+
received.append(recv)
122+
to_read -= len(recv)
123+
gc.collect()
110124
if time.monotonic() - stamp > self._timeout:
111125
break
112-
ret = self._buffer[:size]
113-
self._buffer = self._buffer[size:]
126+
print(received)
127+
self._buffer += b''.join(received)
128+
129+
ret = None
130+
if len(self._buffer) == size:
131+
ret = self._buffer
132+
self._buffer = b''
133+
else:
134+
ret = self._buffer[:size]
135+
self._buffer = self._buffer[size:]
136+
gc.collect()
114137
return ret
115138

116139
def settimeout(self, value):
117-
"""Set the reading timeout for sockets before we return with fewer
118-
or no data on read(). If set to 0 we are fully blocking"""
119140
self._timeout = value
120141

121142
def close(self):

0 commit comments

Comments
 (0)