Skip to content

Commit fa1ec91

Browse files
committed
move into a subfolder, working ssl
1 parent 8d2c1e8 commit fa1ec91

File tree

3 files changed

+206
-10
lines changed

3 files changed

+206
-10
lines changed

adafruit_esp32spi.py renamed to adafruit_esp32spi/adafruit_esp32spi.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ class ESP_SPIcontrol:
6464
SOCKET_TIME_WAIT = const(10)
6565

6666
TCP_MODE = const(0)
67+
UDP_MODE = const(1)
68+
TLS_MODE = const(2)
6769

6870
def __init__(self, spi, cs_pin, ready_pin, reset_pin, gpio0_pin, *, debug=False):
6971
self._debug = debug
@@ -344,13 +346,17 @@ def get_socket(self):
344346
print("Allocated socket #%d" % resp)
345347
return resp
346348

347-
def socket_open(self, socket_num, ip, port, conn_mode=TCP_MODE):
349+
def socket_open(self, socket_num, dest, port, conn_mode=TCP_MODE):
348350
if self._debug:
349351
print("*** Open socket")
350352
port_param = struct.pack('>H', port)
351-
resp = self.send_command_get_response(START_CLIENT_TCP_CMD,
352-
[ip, port_param,
353-
[socket_num], [conn_mode]])
353+
if isinstance(dest, str): # use the 5 arg version
354+
dest = bytes(dest, 'utf-8')
355+
resp = self.send_command_get_response(START_CLIENT_TCP_CMD,
356+
[dest, b'\x00\x00\x00\x00', port_param, [socket_num], [conn_mode]])
357+
else: # ip address, use 4 arg vesion
358+
resp = self.send_command_get_response(START_CLIENT_TCP_CMD,
359+
[dest, port_param, [socket_num], [conn_mode]])
354360
if resp[0][0] != 1:
355361
raise RuntimeError("Could not connect to remote server")
356362

@@ -362,13 +368,19 @@ def socket_connected(self, socket_num):
362368
return self.socket_status(socket_num) == SOCKET_ESTABLISHED
363369

364370
def socket_write(self, socket_num, buffer):
371+
if self._debug:
372+
print("Writing:", buffer)
365373
resp = self.send_command_get_response(SEND_DATA_TCP_CMD,
366374
[[socket_num], buffer],
367375
sent_param_len_16=True)
368376

369377
sent = resp[0][0]
370378
if sent != len(buffer):
371379
raise RuntimeError("Failed to send %d bytes (sent %d)" % (len(buffer), sent))
380+
381+
resp = self.send_command_get_response(DATA_SENT_TCP_CMD, [[socket_num]])
382+
if self._debug:
383+
print("** DATA SENT: ",resp)
372384
return sent
373385

374386
def socket_available(self, socket_num):
@@ -380,13 +392,11 @@ def socket_read(self, socket_num, size):
380392
sent_param_len_16=True, recv_param_len_16=True)
381393
return resp[0]
382394

383-
def socket_connect(self, socket_num, dest, port):
395+
def socket_connect(self, socket_num, dest, port, conn_mode=TCP_MODE):
384396
if self._debug:
385-
print("*** Socket connect")
386-
if isinstance(dest, str): # convert to IP address
387-
dest = self.get_host_by_name(dest)
397+
print("*** Socket connect mode", conn_mode)
388398

389-
self.socket_open(socket_num, dest, port)
399+
self.socket_open(socket_num, dest, port, conn_mode=conn_mode)
390400
times = time.monotonic()
391401
while (time.monotonic() - times) < 3: # wait 3 seconds
392402
if self.socket_connected(socket_num):
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
"""
2+
adapted from https://github.com/micropython/micropython-lib/tree/master/urequests
3+
4+
micropython-lib consists of multiple modules from different sources and
5+
authors. Each module comes under its own licensing terms. Short name of
6+
a license can be found in a file within a module directory (usually
7+
metadata.txt or setup.py). Complete text of each license used is provided
8+
at https://github.com/micropython/micropython-lib/blob/master/LICENSE
9+
10+
author='Paul Sokolovsky'
11+
license='MIT'
12+
"""
13+
14+
# pylint: disable=no-name-in-module
15+
16+
import gc
17+
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
18+
19+
_the_interface = None # pylint: disable=invalid-name
20+
def set_interface(iface):
21+
"""Helper to set the global internet interface"""
22+
global _the_interface # pylint: disable=invalid-name, global-statement
23+
_the_interface = iface
24+
socket.set_interface(iface)
25+
26+
class Response:
27+
"""The response from a request, contains all the headers/content"""
28+
headers = {}
29+
encoding = None
30+
31+
def __init__(self, f):
32+
self.raw = f
33+
self.encoding = "utf-8"
34+
self._cached = None
35+
self.status_code = None
36+
self.reason = None
37+
38+
def close(self):
39+
"""Close, delete and collect the response data"""
40+
if self.raw:
41+
self.raw.close()
42+
del self.raw
43+
del self._cached
44+
gc.collect()
45+
46+
@property
47+
def content(self):
48+
"""The HTTP content direct from the socket, as bytes"""
49+
if self._cached is None:
50+
try:
51+
self._cached = self.raw.read()
52+
finally:
53+
self.raw.close()
54+
self.raw = None
55+
return self._cached
56+
57+
@property
58+
def text(self):
59+
"""The HTTP content, encoded into a string according to the HTTP
60+
header encoding"""
61+
return str(self.content, self.encoding)
62+
63+
def json(self):
64+
"""The HTTP content, parsed into a json dictionary"""
65+
import ujson
66+
return ujson.loads(self.content)
67+
68+
69+
# pylint: disable=too-many-branches, too-many-statements, unused-argument, too-many-arguments, too-many-locals
70+
def request(method, url, data=None, json=None, headers=None, stream=None):
71+
"""Perform an HTTP request to the given url which we will parse to determine
72+
whether to use SSL ('https://') or not. We can also send some provided 'data'
73+
or a json dictionary which we will stringify. 'headers' is optional HTTP headers
74+
sent along. 'stream' is unused in this implementation"""
75+
global _the_interface # pylint: disable=global-statement, invalid-name
76+
77+
if not headers:
78+
headers = {}
79+
80+
try:
81+
proto, dummy, host, path = url.split("/", 3)
82+
except ValueError:
83+
proto, dummy, host = url.split("/", 2)
84+
path = ""
85+
if proto == "http:":
86+
port = 80
87+
elif proto == "https:":
88+
port = 443
89+
else:
90+
raise ValueError("Unsupported protocol: " + proto)
91+
92+
if ":" in host:
93+
host, port = host.split(":", 1)
94+
port = int(port)
95+
96+
addr_info = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM)[0]
97+
sock = socket.socket(addr_info[0], addr_info[1], addr_info[2])
98+
resp = Response(sock) # our response
99+
100+
try:
101+
if proto == "https:":
102+
conntype = _the_interface.TLS_MODE
103+
sock.connect((host, port), conntype) # for SSL we need to know the host name
104+
else:
105+
conntype = _the_interface.TCP_MODE
106+
sock.connect(addr_info[-1], conntype)
107+
sock.write(b"%s /%s HTTP/1.0\r\n" % (method, path))
108+
if "Host" not in headers:
109+
sock.write(b"Host: %s\r\n" % host)
110+
if "User-Agent" not in headers:
111+
sock.write(b"User-Agent: Adafruit CircuitPython\r\n")
112+
# Iterate over keys to avoid tuple alloc
113+
for k in headers:
114+
sock.write(k)
115+
sock.write(b": ")
116+
sock.write(headers[k])
117+
sock.write(b"\r\n")
118+
if json is not None:
119+
assert data is None
120+
import ujson
121+
data = ujson.dumps(json)
122+
sock.write(b"Content-Type: application/json\r\n")
123+
if data:
124+
sock.write(b"Content-Length: %d\r\n" % len(data))
125+
sock.write(b"\r\n")
126+
if data:
127+
sock.write(bytes(data, 'utf-8'))
128+
129+
line = sock.readline()
130+
#print(line)
131+
line = line.split(None, 2)
132+
status = int(line[1])
133+
reason = ""
134+
if len(line) > 2:
135+
reason = line[2].rstrip()
136+
while True:
137+
line = sock.readline()
138+
if not line or line == b"\r\n":
139+
break
140+
141+
#print(line)
142+
title, content = line.split(b': ', 1)
143+
if title and content:
144+
title = str(title.lower(), 'utf-8')
145+
content = str(content, 'utf-8')
146+
resp.headers[title] = content
147+
148+
if line.startswith(b"Transfer-Encoding:"):
149+
if b"chunked" in line:
150+
raise ValueError("Unsupported " + line)
151+
elif line.startswith(b"Location:") and not 200 <= status <= 299:
152+
raise NotImplementedError("Redirects not yet supported")
153+
154+
except OSError:
155+
sock.close()
156+
raise
157+
158+
resp.status_code = status
159+
resp.reason = reason
160+
return resp
161+
# pylint: enable=too-many-branches, too-many-statements, unused-argument
162+
# pylint: enable=too-many-arguments, too-many-locals
163+
164+
def head(url, **kw):
165+
"""Send HTTP HEAD request"""
166+
return request("HEAD", url, **kw)
167+
168+
def get(url, **kw):
169+
"""Send HTTP GET request"""
170+
return request("GET", url, **kw)
171+
172+
def post(url, **kw):
173+
"""Send HTTP POST request"""
174+
return request("POST", url, **kw)
175+
176+
def put(url, **kw):
177+
"""Send HTTP PUT request"""
178+
return request("PUT", url, **kw)
179+
180+
def patch(url, **kw):
181+
"""Send HTTP PATCH request"""
182+
return request("PATCH", url, **kw)
183+
184+
def delete(url, **kw):
185+
"""Send HTTP DELETE request"""
186+
return request("DELETE", url, **kw)

adafruit_esp32spi_socket.py renamed to adafruit_esp32spi/adafruit_esp32spi_socket.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def connect(self, address, conntype=None):
3737
a hostname string). 'conntype' is an extra that may indicate SSL or not,
3838
depending on the underlying interface"""
3939
host, port = address
40-
if not _the_interface.socket_connect(self._socknum, host, port):
40+
if not _the_interface.socket_connect(self._socknum, host, port, conn_mode=conntype):
4141
raise RuntimeError("Failed to connect to host", host)
4242
self._buffer = b''
4343

0 commit comments

Comments
 (0)