Skip to content

Commit 5d9f5a9

Browse files
committed
Add WSGI web server, socket listen, fix bugs
- Added WSGI web server with up to 7 simultaneous connections, which are serviced sequentially - adafruit_wiznet5k_wsgiserver based on and has interface identical to adafruit_esp32spi_wsgiserver for maximum compatibility - Added socket 'listen' function - Fixed socket 'connected' logic and made compatible with 'listen' - Fixed source port > 65535 bug - Removed SOCKETS list, it was buggy and caused get_socket allocation to only work if socket connections ended in order, which is not the case for web servers (and wrong in many other cases) - Fixed TX buffer pointer limit to 16-bit - Allow 5s for DHCP response
1 parent 7450af5 commit 5d9f5a9

File tree

6 files changed

+319
-41
lines changed

6 files changed

+319
-41
lines changed

README.rst

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,69 @@ wifitest.adafruit.com.
102102
103103
print("Done!")
104104
105+
This example demonstrates a simple web server that allows setting the Neopixel color.
106+
107+
.. code-block:: python
108+
109+
import board
110+
import busio
111+
import digitalio
112+
import neopixel
113+
114+
from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K
115+
import adafruit_wiznet5k.adafruit_wiznet5k_wsgiserver as server
116+
from adafruit_wsgi.wsgi_app import WSGIApp
117+
118+
print("Wiznet5k Web Server Test")
119+
120+
# Status LED
121+
led = neopixel.NeoPixel(board.NEOPIXEL, 1)
122+
led.brightness = 0.3
123+
led[0] = (0, 0, 255)
124+
125+
# W5500 connections
126+
cs = digitalio.DigitalInOut(board.D10)
127+
spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
128+
129+
# Initialize ethernet interface with DHCP and the MAC we have from the 24AA02E48
130+
eth = WIZNET5K(spi_bus, cs)
131+
132+
# Here we create our application, registering the
133+
# following functions to be called on specific HTTP GET requests routes
134+
135+
web_app = WSGIApp()
136+
137+
138+
@web_app.route("/led/<r>/<g>/<b>")
139+
def led_on(request, r, g, b):
140+
print("led handler")
141+
led.fill((int(r), int(g), int(b)))
142+
return ("200 OK", [], ["led set!"])
143+
144+
@web_app.route("/")
145+
def root(request):
146+
print("root handler")
147+
return ("200 OK", [], ["root document"])
148+
149+
@web_app.route("/large")
150+
def large(request):
151+
print("large handler")
152+
return ("200 OK", [], ["*-.-" * 2000])
153+
154+
155+
# Here we setup our server, passing in our web_app as the application
156+
server.set_interface(eth)
157+
wsgiServer = server.WSGIServer(80, application=web_app)
158+
159+
print("Open this IP in your browser: ", eth.pretty_ip(eth.ip_address))
160+
161+
# Start the server
162+
wsgiServer.start()
163+
while True:
164+
# Our main loop where we have the server poll for incoming requests
165+
wsgiServer.update_poll()
166+
# Could do any other background tasks here, like reading sensors
167+
105168
Contributing
106169
============
107170

@@ -122,4 +185,4 @@ with `CircuitPython <https://circuitpython.org/>`_ and made changes so it works
122185
5500 Ethernet interface <https://circuitpython.readthedocs.io/en/latest/shared-bindings/wiznet/wiznet5k.html>`_ and CPython's `Socket low-level
123186
networking interface module <https://docs.python.org/3.8/library/socket.html>`_.
124187

125-
This open source code is licensed under the LGPL license (see LICENSE for details).
188+
This open source code is licensed under the LGPL license (see LICENSE for details).

adafruit_wiznet5k/adafruit_wiznet5k.py

100755100644
Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
# Copyright (c) 2008 Bjoern Hartmann
66
# Copyright 2018 Paul Stoffregen
77
# Modified by Brent Rubell for Adafruit Industries, 2020
8+
# Copyright (c) 2021 Patrick Van Oosterwijck
89
#
910
# Permission is hereby granted, free of charge, to any person obtaining a copy
1011
# of this software and associated documentation files (the "Software"), to deal
@@ -29,7 +30,8 @@
2930
3031
Pure-Python interface for WIZNET 5k ethernet modules.
3132
32-
* Author(s): WIZnet, Arduino LLC, Bjoern Hartmann, Paul Stoffregen, Brent Rubell
33+
* Author(s): WIZnet, Arduino LLC, Bjoern Hartmann, Paul Stoffregen, Brent Rubell,
34+
Patrick Van Oosterwijck
3335
3436
Implementation Notes
3537
--------------------
@@ -257,7 +259,7 @@ def get_host_by_name(self, hostname):
257259
print("* Get host by name")
258260
if isinstance(hostname, str):
259261
hostname = bytes(hostname, "utf-8")
260-
self._src_port = int(time.monotonic())
262+
self._src_port = int(time.monotonic()) & 0xFFFF
261263
# Return IP assigned by DHCP
262264
_dns_client = dns.DNS(self, self._dns, debug=self._debug)
263265
ret = _dns_client.gethostbyname(hostname)
@@ -563,16 +565,15 @@ def _send_socket_cmd(self, socket, cmd):
563565
if self._debug:
564566
print("waiting for sncr to clear...")
565567

566-
def get_socket(self, sockets):
568+
def get_socket(self):
567569
"""Requests, allocates and returns a socket from the W5k
568570
chip. Returned socket number may not exceed max_sockets.
569-
:parm int socket_num: Desired socket number
570571
"""
571572
if self._debug:
572573
print("*** Get socket")
573574

574575
sock = 0
575-
for _sock in range(len(sockets), self.max_sockets):
576+
for _sock in range(self.max_sockets):
576577
status = self.socket_status(_sock)
577578
if (
578579
status[0] == SNSR_SOCK_CLOSED
@@ -586,14 +587,42 @@ def get_socket(self, sockets):
586587
print("Allocated socket #{}".format(sock))
587588
return sock
588589

590+
def socket_listen(self, socket_num, port):
591+
"""Start listening on a socket (TCP mode only).
592+
:parm int socket_num: socket number
593+
:parm int port: port to listen on
594+
"""
595+
assert self.link_status, "Ethernet cable disconnected!"
596+
if self._debug:
597+
print(
598+
"* Listening on port={}, ip={}".format(
599+
port, self.pretty_ip(self.ip_address)
600+
)
601+
)
602+
# Initialize a socket and set the mode
603+
self._src_port = port
604+
res = self.socket_open(socket_num, conn_mode=SNMR_TCP)
605+
if res == 1:
606+
raise RuntimeError("Failed to initalize the socket.")
607+
# Send listen command
608+
self._send_socket_cmd(socket_num, CMD_SOCK_LISTEN)
609+
# Wait until ready
610+
status = [SNSR_SOCK_CLOSED]
611+
while status[0] != SNSR_SOCK_LISTEN:
612+
status = self._read_snsr(socket_num)
613+
if status[0] == SNSR_SOCK_CLOSED:
614+
raise RuntimeError("Listening socket closed.")
615+
616+
589617
def socket_open(self, socket_num, conn_mode=SNMR_TCP):
590618
"""Opens a TCP or UDP socket. By default, we use
591619
'conn_mode'=SNMR_TCP but we may also use SNMR_UDP.
592620
"""
593621
assert self.link_status, "Ethernet cable disconnected!"
594622
if self._debug:
595623
print("*** Opening socket %d" % socket_num)
596-
if self._read_snsr(socket_num)[0] == SNSR_SOCK_CLOSED:
624+
status = self._read_snsr(socket_num)[0]
625+
if status in (SNSR_SOCK_CLOSED, SNSR_SOCK_FIN_WAIT, SNSR_SOCK_CLOSE_WAIT):
597626
if self._debug:
598627
print("* Opening W5k Socket, protocol={}".format(conn_mode))
599628
time.sleep(0.00025)
@@ -719,7 +748,7 @@ def socket_write(self, socket_num, buffer):
719748
dst_addr = offset + (socket_num * 2048 + 0x8000)
720749

721750
# update sn_tx_wr to the value + data size
722-
ptr += len(buffer)
751+
ptr = (ptr + len(buffer)) & 0xFFFF
723752
self._write_sntx_wr(socket_num, ptr)
724753

725754
cntl_byte = 0x14 + (socket_num << 5)
@@ -826,12 +855,10 @@ def _read_sncr(self, sock):
826855
def _read_snmr(self, sock):
827856
return self._read_socket(sock, REG_SNMR)
828857

829-
def _write_socket(self, sock, address, data, length=None):
858+
def _write_socket(self, sock, address, data):
830859
"""Write to a W5k socket register."""
831860
base = self._ch_base_msb << 8
832861
cntl_byte = (sock << 5) + 0x0C
833-
if length is None:
834-
return self.write(base + sock * CH_SIZE + address, cntl_byte, data)
835862
return self.write(base + sock * CH_SIZE + address, cntl_byte, data)
836863

837864
def _read_socket(self, sock, address):

adafruit_wiznet5k/adafruit_wiznet5k_dhcp.py

100755100644
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ class DHCP:
101101

102102
# pylint: disable=too-many-arguments, too-many-instance-attributes, invalid-name
103103
def __init__(
104-
self, eth, mac_address, hostname=None, response_timeout=3, debug=False
104+
self, eth, mac_address, hostname=None, response_timeout=5, debug=False
105105
):
106106
self._debug = debug
107107
self._response_timeout = response_timeout

adafruit_wiznet5k/adafruit_wiznet5k_dns.py

100755100644
File mode changed.

adafruit_wiznet5k/adafruit_wiznet5k_socket.py

100755100644
Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#
33
# Copyright (c) 2019 ladyada for Adafruit Industries
44
# Modified by Brent Rubell for Adafruit Industries, 2020
5+
# Copyright (c) 2020 Patrick Van Oosterwijck
56
#
67
# Permission is hereby granted, free of charge, to any person obtaining a copy
78
# of this software and associated documentation files (the "Software"), to deal
@@ -26,7 +27,7 @@
2627
2728
A socket compatible interface with the Wiznet5k module.
2829
29-
* Author(s): ladyada, Brent Rubell
30+
* Author(s): ladyada, Brent Rubell, Patrick Van Oosterwijck
3031
3132
"""
3233
import gc
@@ -64,8 +65,6 @@ def htons(x):
6465
AF_INET = const(3)
6566
NO_SOCKET_AVAIL = const(255)
6667

67-
# keep track of sockets we allocate
68-
SOCKETS = []
6968

7069
# pylint: disable=too-many-arguments, unused-argument
7170
def getaddrinfo(host, port, family=0, socktype=0, proto=0, flags=0):
@@ -122,9 +121,7 @@ def __init__(
122121
self._buffer = b""
123122
self._timeout = 0
124123

125-
self._socknum = _the_interface.get_socket(SOCKETS)
126-
SOCKETS.append(self._socknum)
127-
self.settimeout(self._timeout)
124+
self._socknum = _the_interface.get_socket()
128125

129126
@property
130127
def socknum(self):
@@ -135,22 +132,20 @@ def socknum(self):
135132
def connected(self):
136133
"""Returns whether or not we are connected to the socket."""
137134
if self.socknum >= _the_interface.max_sockets:
138-
return 0
135+
return False
139136
status = _the_interface.socket_status(self.socknum)[0]
140-
if (
141-
status == adafruit_wiznet5k.SNSR_SOCK_CLOSE_WAIT
142-
and self.available()[0] == 0
143-
):
137+
if (status == adafruit_wiznet5k.SNSR_SOCK_CLOSE_WAIT
138+
and self.available() == 0):
144139
result = False
145-
result = status not in (
146-
adafruit_wiznet5k.SNSR_SOCK_CLOSED,
147-
adafruit_wiznet5k.SNSR_SOCK_LISTEN,
148-
adafruit_wiznet5k.SNSR_SOCK_CLOSE_WAIT,
149-
adafruit_wiznet5k.SNSR_SOCK_FIN_WAIT,
150-
)
151-
if not result:
140+
else:
141+
result = status not in (
142+
adafruit_wiznet5k.SNSR_SOCK_CLOSED,
143+
adafruit_wiznet5k.SNSR_SOCK_LISTEN,
144+
adafruit_wiznet5k.SNSR_SOCK_TIME_WAIT,
145+
adafruit_wiznet5k.SNSR_SOCK_FIN_WAIT,
146+
)
147+
if not result and status != adafruit_wiznet5k.SNSR_SOCK_LISTEN:
152148
self.close()
153-
return result
154149
return result
155150

156151
def getpeername(self):
@@ -167,6 +162,13 @@ def inet_aton(self, ip_string):
167162
self._buffer = bytearray(self._buffer)
168163
return self._buffer
169164

165+
def listen(self, port):
166+
"""Listen on the specified port.
167+
:param int port: The port to listen on.
168+
"""
169+
_the_interface.socket_listen(self.socknum, port)
170+
self._buffer = b""
171+
170172
def connect(self, address, conntype=None):
171173
"""Connect to a remote socket at address. (The format of address depends
172174
on the address family — see above.)
@@ -209,11 +211,11 @@ def recv(self, bufsize=0): # pylint: disable=too-many-branches
209211
avail = _the_interface.udp_remaining()
210212
if avail:
211213
if self._sock_type == SOCK_STREAM:
212-
self._buffer += _the_interface.socket_read(self.socknum, avail)[
213-
1
214-
]
214+
self._buffer += _the_interface.socket_read(
215+
self.socknum, avail)[1]
215216
elif self._sock_type == SOCK_DGRAM:
216-
self._buffer += _the_interface.read_udp(self.socknum, avail)[1]
217+
self._buffer += _the_interface.read_udp(
218+
self.socknum, avail)[1]
217219
else:
218220
break
219221
gc.collect()
@@ -235,10 +237,10 @@ def recv(self, bufsize=0): # pylint: disable=too-many-branches
235237
stamp = time.monotonic()
236238
if self._sock_type == SOCK_STREAM:
237239
recv = _the_interface.socket_read(
238-
self.socknum, min(to_read, avail)
239-
)[1]
240+
self.socknum, min(to_read, avail))[1]
240241
elif self._sock_type == SOCK_DGRAM:
241-
recv = _the_interface.read_udp(self.socknum, min(to_read, avail))[1]
242+
recv = _the_interface.read_udp(
243+
self.socknum, min(to_read, avail))[1]
242244
recv = bytes(recv)
243245
received.append(recv)
244246
to_read -= len(recv)
@@ -267,12 +269,14 @@ def readline(self):
267269
if self._sock_type == SOCK_STREAM:
268270
avail = self.available()
269271
if avail:
270-
self._buffer += _the_interface.socket_read(self.socknum, avail)[1]
272+
self._buffer += _the_interface.socket_read(
273+
self.socknum, avail)[1]
271274
elif self._sock_type == SOCK_DGRAM:
272275
avail = _the_interface.udp_remaining()
273276
if avail:
274277
self._buffer += _the_interface.read_udp(self.socknum, avail)
275-
elif self._timeout > 0 and time.monotonic() - stamp > self._timeout:
278+
if not avail and self._timeout > 0 and \
279+
time.monotonic() - stamp > self._timeout:
276280
self.close()
277281
raise RuntimeError("Didn't receive response, failing out...")
278282
firstline, self._buffer = self._buffer.split(b"\r\n", 1)
@@ -287,7 +291,6 @@ def disconnect(self):
287291
def close(self):
288292
"""Closes the socket."""
289293
_the_interface.socket_close(self.socknum)
290-
SOCKETS.remove(self.socknum)
291294

292295
def available(self):
293296
"""Returns how many bytes of data are available to be read from the socket."""

0 commit comments

Comments
 (0)