Skip to content

Add Server creation and management support #59

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Jul 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5b28118
separate server code from AP code
mscosti Jul 13, 2019
5e09b7b
Add server class to handle server start and get connected clients
mscosti Jul 13, 2019
faf031a
add simple request method handling
mscosti Jul 14, 2019
8c4035d
py linting
mscosti Jul 14, 2019
b8fd02e
more linting
mscosti Jul 14, 2019
5bd6c86
add in support for automatically serving files from a specified direc…
mscosti Jul 14, 2019
83d1525
add in example static asset serving with ajax call LED color changing
mscosti Jul 14, 2019
f6d9415
fix spacing
mscosti Jul 14, 2019
d469ab9
untested first draft of simple WSGI server
mscosti Jul 20, 2019
94fce44
fix a couple wsgi server bugs
mscosti Jul 21, 2019
c429455
send response headers separately before body to support chunking
mscosti Jul 21, 2019
92d0c66
new example script for using WsgiServer
mscosti Jul 21, 2019
6af7675
Merge branch 'master' of https://github.com/adafruit/Adafruit_Circuit…
mscosti Jul 21, 2019
d1fe777
fix led_on to pass rgb tuple
mscosti Jul 21, 2019
842ac46
Remove server.py in favor of new wsgiserver.py
mscosti Jul 21, 2019
c2b7cb0
Address PR comments for adafruit_esp32spi.py
mscosti Jul 21, 2019
4f3d07f
fix file name. adafruite -> adafruit
mscosti Jul 21, 2019
fc97dac
fix linting of example file
mscosti Jul 21, 2019
41eab1c
PATH_INFO shouldn't contain query params
mscosti Jul 21, 2019
e6bfd36
remove wifimanager debug arg
mscosti Jul 22, 2019
473f850
fix PATH_INFO when there are query params
mscosti Jul 22, 2019
a93c099
Address PR comments
mscosti Jul 23, 2019
fd4e191
Use constants instead of magic numbers
mscosti Jul 23, 2019
6951f8c
fix import?
mscosti Jul 23, 2019
a5d59f8
fix linting
mscosti Jul 23, 2019
8dbeef0
move all server example assets into examples/server
mscosti Jul 23, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions adafruit_esp32spi/adafruit_esp32spi.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
_GET_CURR_ENCT_CMD = const(0x26)

_SCAN_NETWORKS = const(0x27)
_START_SERVER_TCP_CMD = const(0x28)
_GET_SOCKET_CMD = const(0x3F)
_GET_STATE_TCP_CMD = const(0x29)
_DATA_SENT_TCP_CMD = const(0x2A)
Expand Down Expand Up @@ -622,6 +623,25 @@ def socket_close(self, socket_num):
if resp[0][0] != 1:
raise RuntimeError("Failed to close socket")

def start_server(self, port, socket_num, conn_mode=TCP_MODE, ip=None): # pylint: disable=invalid-name
"""Opens a server on the specified port, using the ESP32's internal reference number"""
if self._debug:
print("*** starting server")
self._socknum_ll[0][0] = socket_num
params = [struct.pack('>H', port), self._socknum_ll[0], (conn_mode,)]
if ip:
params.insert(0, ip)
resp = self._send_command_get_response(_START_SERVER_TCP_CMD, params)

if resp[0][0] != 1:
raise RuntimeError("Could not start server")

def server_state(self, socket_num):
"""Get the state of the ESP32's internal reference server socket number"""
self._socknum_ll[0][0] = socket_num
resp = self._send_command_get_response(_GET_STATE_TCP_CMD, self._socknum_ll)
return resp[0][0]

def set_esp_debug(self, enabled):
"""Enable/disable debug mode on the ESP32. Debug messages will be
written to the ESP32's UART."""
Expand Down
43 changes: 26 additions & 17 deletions adafruit_esp32spi/adafruit_esp32spi_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,23 +204,11 @@ def request(method, url, data=None, json=None, headers=None, stream=False, timeo
reason = ""
if len(line) > 2:
reason = line[2].rstrip()
while True:
line = sock.readline()
if not line or line == b"\r\n":
break

#print("**line: ", line)
title, content = line.split(b': ', 1)
if title and content:
title = str(title.lower(), 'utf-8')
content = str(content, 'utf-8')
resp.headers[title] = content

if line.startswith(b"Transfer-Encoding:"):
if b"chunked" in line:
raise ValueError("Unsupported " + line)
elif line.startswith(b"Location:") and not 200 <= status <= 299:
raise NotImplementedError("Redirects not yet supported")
resp.headers = parse_headers(sock)
if "chunked" in resp.headers.get("transfer-encoding"):
raise ValueError("Unsupported " + line)
elif resp.headers.get("location") and not 200 <= status <= 299:
raise NotImplementedError("Redirects not yet supported")

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

def parse_headers(sock):
"""
Parses the header portion of an HTTP request/response from the socket.
Expects first line of HTTP request/response to have been read already
return: header dictionary
rtype: Dict
"""
headers = {}
while True:
line = sock.readline()
if not line or line == b"\r\n":
break

#print("**line: ", line)
title, content = line.split(b': ', 1)
if title and content:
title = str(title.lower(), 'utf-8')
content = str(content, 'utf-8')
headers[title] = content
return headers

def head(url, **kw):
"""Send HTTP HEAD request"""
return request("HEAD", url, **kw)
Expand Down
47 changes: 42 additions & 5 deletions adafruit_esp32spi/adafruit_esp32spi_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@
* Author(s): ladyada
"""

# pylint: disable=no-name-in-module

import time
import gc
from micropython import const
from adafruit_esp32spi import adafruit_esp32spi

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

SOCK_STREAM = const(1)
AF_INET = const(2)
NO_SOCKET_AVAIL = const(255)

MAX_PACKET = const(4000)

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

def connect(self, address, conntype=None):
"""Connect the socket to the 'address' (which can be 32bit packed IP or
Expand All @@ -90,7 +95,7 @@ def readline(self):
stamp = time.monotonic()
while b'\r\n' not in self._buffer:
# there's no line already in there, read some more
avail = min(_the_interface.socket_available(self._socknum), MAX_PACKET)
avail = self.available()
if avail:
self._buffer += _the_interface.socket_read(self._socknum, avail)
elif self._timeout > 0 and time.monotonic() - stamp > self._timeout:
Expand All @@ -106,7 +111,7 @@ def read(self, size=0):
#print("Socket read", size)
if size == 0: # read as much as we can at the moment
while True:
avail = min(_the_interface.socket_available(self._socknum), MAX_PACKET)
avail = self.available()
if avail:
self._buffer += _the_interface.socket_read(self._socknum, avail)
else:
Expand All @@ -122,7 +127,7 @@ def read(self, size=0):
received = []
while to_read > 0:
#print("Bytes to read:", to_read)
avail = min(_the_interface.socket_available(self._socknum), MAX_PACKET)
avail = self.available()
if avail:
stamp = time.monotonic()
recv = _the_interface.socket_read(self._socknum, min(to_read, avail))
Expand All @@ -148,6 +153,38 @@ def settimeout(self, value):
"""Set the read timeout for sockets, if value is 0 it will block"""
self._timeout = value

def available(self):
"""Returns how many bytes of data are available to be read (up to the MAX_PACKET length)"""
if self.socknum != NO_SOCKET_AVAIL:
return min(_the_interface.socket_available(self._socknum), MAX_PACKET)
return 0

def connected(self):
"""Whether or not we are connected to the socket"""
if self.socknum == NO_SOCKET_AVAIL:
return False
elif self.available():
return True
else:
status = _the_interface.socket_status(self.socknum)
result = status not in (adafruit_esp32spi.SOCKET_LISTEN,
adafruit_esp32spi.SOCKET_CLOSED,
adafruit_esp32spi.SOCKET_FIN_WAIT_1,
adafruit_esp32spi.SOCKET_FIN_WAIT_2,
adafruit_esp32spi.SOCKET_TIME_WAIT,
adafruit_esp32spi.SOCKET_SYN_SENT,
adafruit_esp32spi.SOCKET_SYN_RCVD,
adafruit_esp32spi.SOCKET_CLOSE_WAIT)
if not result:
self.close()
self._socknum = NO_SOCKET_AVAIL
return result

@property
def socknum(self):
"""The socket number"""
return self._socknum

def close(self):
"""Close the socket, after reading whatever remains"""
_the_interface.socket_close(self._socknum)
Expand Down
Loading