Skip to content

Commit c15e8fd

Browse files
authored
Merge pull request #59 from mscosti/server
Add Server creation and management support
2 parents 59203bc + 8dbeef0 commit c15e8fd

File tree

7 files changed

+548
-22
lines changed

7 files changed

+548
-22
lines changed

adafruit_esp32spi/adafruit_esp32spi.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
_GET_CURR_ENCT_CMD = const(0x26)
6565

6666
_SCAN_NETWORKS = const(0x27)
67+
_START_SERVER_TCP_CMD = const(0x28)
6768
_GET_SOCKET_CMD = const(0x3F)
6869
_GET_STATE_TCP_CMD = const(0x29)
6970
_DATA_SENT_TCP_CMD = const(0x2A)
@@ -622,6 +623,25 @@ def socket_close(self, socket_num):
622623
if resp[0][0] != 1:
623624
raise RuntimeError("Failed to close socket")
624625

626+
def start_server(self, port, socket_num, conn_mode=TCP_MODE, ip=None): # pylint: disable=invalid-name
627+
"""Opens a server on the specified port, using the ESP32's internal reference number"""
628+
if self._debug:
629+
print("*** starting server")
630+
self._socknum_ll[0][0] = socket_num
631+
params = [struct.pack('>H', port), self._socknum_ll[0], (conn_mode,)]
632+
if ip:
633+
params.insert(0, ip)
634+
resp = self._send_command_get_response(_START_SERVER_TCP_CMD, params)
635+
636+
if resp[0][0] != 1:
637+
raise RuntimeError("Could not start server")
638+
639+
def server_state(self, socket_num):
640+
"""Get the state of the ESP32's internal reference server socket number"""
641+
self._socknum_ll[0][0] = socket_num
642+
resp = self._send_command_get_response(_GET_STATE_TCP_CMD, self._socknum_ll)
643+
return resp[0][0]
644+
625645
def set_esp_debug(self, enabled):
626646
"""Enable/disable debug mode on the ESP32. Debug messages will be
627647
written to the ESP32's UART."""

adafruit_esp32spi/adafruit_esp32spi_requests.py

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -204,23 +204,11 @@ def request(method, url, data=None, json=None, headers=None, stream=False, timeo
204204
reason = ""
205205
if len(line) > 2:
206206
reason = line[2].rstrip()
207-
while True:
208-
line = sock.readline()
209-
if not line or line == b"\r\n":
210-
break
211-
212-
#print("**line: ", line)
213-
title, content = line.split(b': ', 1)
214-
if title and content:
215-
title = str(title.lower(), 'utf-8')
216-
content = str(content, 'utf-8')
217-
resp.headers[title] = content
218-
219-
if line.startswith(b"Transfer-Encoding:"):
220-
if b"chunked" in line:
221-
raise ValueError("Unsupported " + line)
222-
elif line.startswith(b"Location:") and not 200 <= status <= 299:
223-
raise NotImplementedError("Redirects not yet supported")
207+
resp.headers = parse_headers(sock)
208+
if "chunked" in resp.headers.get("transfer-encoding"):
209+
raise ValueError("Unsupported " + line)
210+
elif resp.headers.get("location") and not 200 <= status <= 299:
211+
raise NotImplementedError("Redirects not yet supported")
224212

225213
except:
226214
sock.close()
@@ -232,6 +220,27 @@ def request(method, url, data=None, json=None, headers=None, stream=False, timeo
232220
# pylint: enable=too-many-branches, too-many-statements, unused-argument
233221
# pylint: enable=too-many-arguments, too-many-locals
234222

223+
def parse_headers(sock):
224+
"""
225+
Parses the header portion of an HTTP request/response from the socket.
226+
Expects first line of HTTP request/response to have been read already
227+
return: header dictionary
228+
rtype: Dict
229+
"""
230+
headers = {}
231+
while True:
232+
line = sock.readline()
233+
if not line or line == b"\r\n":
234+
break
235+
236+
#print("**line: ", line)
237+
title, content = line.split(b': ', 1)
238+
if title and content:
239+
title = str(title.lower(), 'utf-8')
240+
content = str(content, 'utf-8')
241+
headers[title] = content
242+
return headers
243+
235244
def head(url, **kw):
236245
"""Send HTTP HEAD request"""
237246
return request("HEAD", url, **kw)

adafruit_esp32spi/adafruit_esp32spi_socket.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@
2929
* Author(s): ladyada
3030
"""
3131

32+
# pylint: disable=no-name-in-module
3233

3334
import time
3435
import gc
3536
from micropython import const
37+
from adafruit_esp32spi import adafruit_esp32spi
3638

3739
_the_interface = None # pylint: disable=invalid-name
3840
def set_interface(iface):
@@ -42,6 +44,7 @@ def set_interface(iface):
4244

4345
SOCK_STREAM = const(1)
4446
AF_INET = const(2)
47+
NO_SOCKET_AVAIL = const(255)
4548

4649
MAX_PACKET = const(4000)
4750

@@ -59,14 +62,16 @@ def getaddrinfo(host, port, family=0, socktype=0, proto=0, flags=0):
5962
class socket:
6063
"""A simplified implementation of the Python 'socket' class, for connecting
6164
through an interface to a remote device"""
62-
def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None):
65+
# pylint: disable=too-many-arguments
66+
def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None, socknum=None):
6367
if family != AF_INET:
6468
raise RuntimeError("Only AF_INET family supported")
6569
if type != SOCK_STREAM:
6670
raise RuntimeError("Only SOCK_STREAM type supported")
6771
self._buffer = b''
68-
self._socknum = _the_interface.get_socket()
72+
self._socknum = socknum if socknum else _the_interface.get_socket()
6973
self.settimeout(0)
74+
# pylint: enable=too-many-arguments
7075

7176
def connect(self, address, conntype=None):
7277
"""Connect the socket to the 'address' (which can be 32bit packed IP or
@@ -90,7 +95,7 @@ def readline(self):
9095
stamp = time.monotonic()
9196
while b'\r\n' not in self._buffer:
9297
# there's no line already in there, read some more
93-
avail = min(_the_interface.socket_available(self._socknum), MAX_PACKET)
98+
avail = self.available()
9499
if avail:
95100
self._buffer += _the_interface.socket_read(self._socknum, avail)
96101
elif self._timeout > 0 and time.monotonic() - stamp > self._timeout:
@@ -106,7 +111,7 @@ def read(self, size=0):
106111
#print("Socket read", size)
107112
if size == 0: # read as much as we can at the moment
108113
while True:
109-
avail = min(_the_interface.socket_available(self._socknum), MAX_PACKET)
114+
avail = self.available()
110115
if avail:
111116
self._buffer += _the_interface.socket_read(self._socknum, avail)
112117
else:
@@ -122,7 +127,7 @@ def read(self, size=0):
122127
received = []
123128
while to_read > 0:
124129
#print("Bytes to read:", to_read)
125-
avail = min(_the_interface.socket_available(self._socknum), MAX_PACKET)
130+
avail = self.available()
126131
if avail:
127132
stamp = time.monotonic()
128133
recv = _the_interface.socket_read(self._socknum, min(to_read, avail))
@@ -148,6 +153,38 @@ def settimeout(self, value):
148153
"""Set the read timeout for sockets, if value is 0 it will block"""
149154
self._timeout = value
150155

156+
def available(self):
157+
"""Returns how many bytes of data are available to be read (up to the MAX_PACKET length)"""
158+
if self.socknum != NO_SOCKET_AVAIL:
159+
return min(_the_interface.socket_available(self._socknum), MAX_PACKET)
160+
return 0
161+
162+
def connected(self):
163+
"""Whether or not we are connected to the socket"""
164+
if self.socknum == NO_SOCKET_AVAIL:
165+
return False
166+
elif self.available():
167+
return True
168+
else:
169+
status = _the_interface.socket_status(self.socknum)
170+
result = status not in (adafruit_esp32spi.SOCKET_LISTEN,
171+
adafruit_esp32spi.SOCKET_CLOSED,
172+
adafruit_esp32spi.SOCKET_FIN_WAIT_1,
173+
adafruit_esp32spi.SOCKET_FIN_WAIT_2,
174+
adafruit_esp32spi.SOCKET_TIME_WAIT,
175+
adafruit_esp32spi.SOCKET_SYN_SENT,
176+
adafruit_esp32spi.SOCKET_SYN_RCVD,
177+
adafruit_esp32spi.SOCKET_CLOSE_WAIT)
178+
if not result:
179+
self.close()
180+
self._socknum = NO_SOCKET_AVAIL
181+
return result
182+
183+
@property
184+
def socknum(self):
185+
"""The socket number"""
186+
return self._socknum
187+
151188
def close(self):
152189
"""Close the socket, after reading whatever remains"""
153190
_the_interface.socket_close(self._socknum)

0 commit comments

Comments
 (0)