From 8efe0896bec6eb0173fef5257e021177957a73f9 Mon Sep 17 00:00:00 2001 From: "adam_cummick:g9T51EHpC9gPQqG6sb9Q@gitlab.com" Date: Wed, 20 Jan 2021 12:48:14 -0500 Subject: [PATCH 1/6] Adds accept and context manager methods Adds socket accept API Adds context manager methods for socket Adds setter for socket socknum Adds status property for socket Corrects bug in remote_port method --- adafruit_wiznet5k/adafruit_wiznet5k.py | 25 ++++++-- adafruit_wiznet5k/adafruit_wiznet5k_socket.py | 61 +++++++++++++++++-- 2 files changed, 78 insertions(+), 8 deletions(-) diff --git a/adafruit_wiznet5k/adafruit_wiznet5k.py b/adafruit_wiznet5k/adafruit_wiznet5k.py index ca5ae0c..04bb8ff 100644 --- a/adafruit_wiznet5k/adafruit_wiznet5k.py +++ b/adafruit_wiznet5k/adafruit_wiznet5k.py @@ -4,6 +4,7 @@ # SPDX-FileCopyrightText: 2018 Paul Stoffregen # SPDX-FileCopyrightText: 2020 Brent Rubell for Adafruit Industries # SPDX-FileCopyrightText: 2021 Patrick Van Oosterwijck +# SPDX-FileCopyrightText: 2021 Adam Cummick # # SPDX-License-Identifier: MIT @@ -29,7 +30,6 @@ from random import randint import time from micropython import const - from adafruit_bus_device.spi_device import SPIDevice import adafruit_wiznet5k.adafruit_wiznet5k_dhcp as dhcp import adafruit_wiznet5k.adafruit_wiznet5k_dns as dns @@ -328,10 +328,13 @@ def link_status(self): return data[0] & 0x01 return 0 - @property - def remote_port(self): + def remote_port(self, socket_num): """Returns the port of the host who sent the current incoming packet.""" - return self.remote_port + if socket_num >= self.max_sockets: + return self._pbuff + for octet in range(0, 2): + self._pbuff[octet] = self._read_socket(socket_num, REG_SNDPORT + octet)[0] + return int((self._pbuff[0] << 8) | self._pbuff[0]) @property def ifconfig(self): @@ -598,6 +601,20 @@ def socket_listen(self, socket_num, port): if status[0] == SNSR_SOCK_CLOSED: raise RuntimeError("Listening socket closed.") + def socket_accept(self, socket_num): + """Gets the dest IP and port from an incoming connection. + Returns the next socket number so listening can continue + :parm int socket_num: socket number + """ + dest_ip = self.remote_ip(socket_num) + dest_port = self.remote_port(socket_num) + next_socknum = self.get_socket() + if self._debug: + print( + f"* Dest is ({dest_ip}, {dest_port}), Next listen socknum is #{next_socknum}" + ) + return next_socknum, (dest_ip, dest_port) + def socket_open(self, socket_num, conn_mode=SNMR_TCP): """Opens a TCP or UDP socket. By default, we use 'conn_mode'=SNMR_TCP but we may also use SNMR_UDP. diff --git a/adafruit_wiznet5k/adafruit_wiznet5k_socket.py b/adafruit_wiznet5k/adafruit_wiznet5k_socket.py index 4033659..b9d4ba4 100644 --- a/adafruit_wiznet5k/adafruit_wiznet5k_socket.py +++ b/adafruit_wiznet5k/adafruit_wiznet5k_socket.py @@ -9,7 +9,7 @@ A socket compatible interface with the Wiznet5k module. -* Author(s): ladyada, Brent Rubell, Patrick Van Oosterwijck +* Author(s): ladyada, Brent Rubell, Patrick Van Oosterwijck, Adam Cummick """ import gc @@ -108,11 +108,30 @@ def __init__( if self._socknum == SOCKET_INVALID: raise RuntimeError("Failed to allocate socket.") + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.disconnect() + while self.status == adafruit_wiznet5k.SNSR_SOCK_FIN_WAIT: + pass + self.close() + @property def socknum(self): """Returns the socket object's socket number.""" return self._socknum + @socknum.setter + def socknum(self, socknum): + """Sets the socket object's socket number.""" + self._socknum = socknum + + @property + def status(self): + """Returns the status of the socket""" + return _the_interface.socket_status(self.socknum)[0] + @property def connected(self): """Returns whether or not we are connected to the socket.""" @@ -147,10 +166,16 @@ def inet_aton(self, ip_string): return self._buffer def bind(self, address): - """Bind the socket to the listen port, we ignore the host. - :param tuple address: local socket as a (host, port) tuple, host is ignored. + """Bind the socket to the listen port, if host is specified the interface + will be reconfigured to that IP. + :param tuple address: local socket as a (host, port) tuple. """ - _, self._listen_port = address + if address[0] is not None: + ip_address = _the_interface.unpretty_ip(address[0]) + current_ip, subnet_mask, gw_addr, dns = _the_interface.ifconfig + if ip_address != current_ip: + _the_interface.ifconfig = (ip_address, subnet_mask, gw_addr, dns) + self._listen_port = address[1] def listen(self, backlog=None): """Listen on the port specified by bind. @@ -160,6 +185,34 @@ def listen(self, backlog=None): _the_interface.socket_listen(self.socknum, self._listen_port) self._buffer = b"" + def accept(self): + """Mimic python socket accept for compatibility. The socket where the + connection originated is returned while a new socket is allocated and begins + listening. + """ + stamp = time.monotonic() + while self.status not in ( + adafruit_wiznet5k.SNSR_SOCK_SYNRECV, + adafruit_wiznet5k.SNSR_SOCK_ESTABLISHED, + ): + if self._timeout > 0 and time.monotonic() - stamp > self._timeout: + return None + if self.status == adafruit_wiznet5k.SNSR_SOCK_CLOSED: + self.close() + self.listen() + + new_listen_socknum, addr = _the_interface.socket_accept(self.socknum) + current_socknum = self.socknum + # Create a new socket object and swap socket nums so we can continue listening + client_sock = socket() + client_sock.socknum = current_socknum + self.socknum = new_listen_socknum + self.bind((None, self._listen_port)) + self.listen() + while self.status != adafruit_wiznet5k.SNSR_SOCK_LISTEN: + print("Waiting for socket to listen") + return client_sock, addr + def connect(self, address, conntype=None): """Connect to a remote socket at address. (The format of address depends on the address family — see above.) From 973da5e8d834615ac8c671fd59638139a94e554f Mon Sep 17 00:00:00 2001 From: "adam_cummick:g9T51EHpC9gPQqG6sb9Q@gitlab.com" Date: Wed, 20 Jan 2021 12:49:42 -0500 Subject: [PATCH 2/6] Remove white space deletion on imports --- adafruit_wiznet5k/adafruit_wiznet5k.py | 1 + 1 file changed, 1 insertion(+) diff --git a/adafruit_wiznet5k/adafruit_wiznet5k.py b/adafruit_wiznet5k/adafruit_wiznet5k.py index 04bb8ff..f9dd0e0 100644 --- a/adafruit_wiznet5k/adafruit_wiznet5k.py +++ b/adafruit_wiznet5k/adafruit_wiznet5k.py @@ -30,6 +30,7 @@ from random import randint import time from micropython import const + from adafruit_bus_device.spi_device import SPIDevice import adafruit_wiznet5k.adafruit_wiznet5k_dhcp as dhcp import adafruit_wiznet5k.adafruit_wiznet5k_dns as dns From edff77729f62db46cfd1208b79218fef8d482848 Mon Sep 17 00:00:00 2001 From: "adam_cummick:g9T51EHpC9gPQqG6sb9Q@gitlab.com" Date: Wed, 20 Jan 2021 13:39:17 -0500 Subject: [PATCH 3/6] Remove f-string for mpy-cross 5.0.0 --- adafruit_wiznet5k/adafruit_wiznet5k.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_wiznet5k/adafruit_wiznet5k.py b/adafruit_wiznet5k/adafruit_wiznet5k.py index f9dd0e0..ba7f7d9 100644 --- a/adafruit_wiznet5k/adafruit_wiznet5k.py +++ b/adafruit_wiznet5k/adafruit_wiznet5k.py @@ -612,7 +612,9 @@ def socket_accept(self, socket_num): next_socknum = self.get_socket() if self._debug: print( - f"* Dest is ({dest_ip}, {dest_port}), Next listen socknum is #{next_socknum}" + "* Dest is ({}, {}), Next listen socknum is #{}".format( + dest_ip, dest_port, next_socknum + ) ) return next_socknum, (dest_ip, dest_port) From 69c5f3e65d95293247f3da5b4e8551579802cd58 Mon Sep 17 00:00:00 2001 From: "adam_cummick:g9T51EHpC9gPQqG6sb9Q@gitlab.com" Date: Wed, 20 Jan 2021 15:53:39 -0500 Subject: [PATCH 4/6] Add exit timeout and remove socknum setter Adds an __exit__ timeout for disconnect removes socknum setter in favor of direct reference Corrects documentation errors --- adafruit_wiznet5k/adafruit_wiznet5k_socket.py | 22 ++++++------ examples/wiznet5k_simpleserver.py | 36 +++++++++++++++++++ 2 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 examples/wiznet5k_simpleserver.py diff --git a/adafruit_wiznet5k/adafruit_wiznet5k_socket.py b/adafruit_wiznet5k/adafruit_wiznet5k_socket.py index b9d4ba4..7b1c373 100644 --- a/adafruit_wiznet5k/adafruit_wiznet5k_socket.py +++ b/adafruit_wiznet5k/adafruit_wiznet5k_socket.py @@ -113,8 +113,10 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): self.disconnect() + stamp = time.monotonic() while self.status == adafruit_wiznet5k.SNSR_SOCK_FIN_WAIT: - pass + if time.monotonic() - stamp > 1000: + raise RuntimeError("Failed to disconnect socket") self.close() @property @@ -122,11 +124,6 @@ def socknum(self): """Returns the socket object's socket number.""" return self._socknum - @socknum.setter - def socknum(self, socknum): - """Sets the socket object's socket number.""" - self._socknum = socknum - @property def status(self): """Returns the status of the socket""" @@ -186,9 +183,10 @@ def listen(self, backlog=None): self._buffer = b"" def accept(self): - """Mimic python socket accept for compatibility. The socket where the - connection originated is returned while a new socket is allocated and begins - listening. + """Accept a connection. The socket must be bound to an address and listening for + connections. The return value is a pair (conn, address) where conn is a new + socket object usable to send and receive data on the connection, and address is + the address bound to the socket on the other end of the connection. """ stamp = time.monotonic() while self.status not in ( @@ -205,12 +203,12 @@ def accept(self): current_socknum = self.socknum # Create a new socket object and swap socket nums so we can continue listening client_sock = socket() - client_sock.socknum = current_socknum - self.socknum = new_listen_socknum + client_sock._socknum = current_socknum # pylint: disable=protected-access + self._socknum = new_listen_socknum # pylint: disable=protected-access self.bind((None, self._listen_port)) self.listen() while self.status != adafruit_wiznet5k.SNSR_SOCK_LISTEN: - print("Waiting for socket to listen") + raise RuntimeError("Failed to open new listening socket") return client_sock, addr def connect(self, address, conntype=None): diff --git a/examples/wiznet5k_simpleserver.py b/examples/wiznet5k_simpleserver.py new file mode 100644 index 0000000..d8d0543 --- /dev/null +++ b/examples/wiznet5k_simpleserver.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries +# SPDX-FileCopyrightText: 2021 Adam Cummick +# +# SPDX-License-Identifier: MIT + +import board +import busio +import digitalio +from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K +import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket + +print("Wiznet5k SimpleServer Test") + +# For Adafruit Ethernet FeatherWing +cs = digitalio.DigitalInOut(board.D10) +# For Particle Ethernet FeatherWing +# cs = digitalio.DigitalInOut(board.D5) +spi_bus = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO) + +# Initialize ethernet interface +eth = WIZNET5K(spi_bus, cs, is_dhcp=False) + +# Initialize a socket for our server +socket.set_interface(eth) +server = socket.socket() # Allocate socket for the server +server_ip = "192.168.10.1" # IP address of server +server_port = 50007 # Port to listen on +server.bind((server_ip, server_port)) # Bind to IP and Port +server.listen() # Begin listening for incoming clients + +while True: + conn, addr = server.accept() # Wait for a connection from a client. + with conn: + data = conn.recv() + print(data) + conn.send(data) # Echo message back to client From 2e00a8738789605f32255460c267f344caf11217 Mon Sep 17 00:00:00 2001 From: "adam_cummick:g9T51EHpC9gPQqG6sb9Q@gitlab.com" Date: Wed, 20 Jan 2021 15:56:52 -0500 Subject: [PATCH 5/6] Remove trailing whitespace --- examples/wiznet5k_simpleserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/wiznet5k_simpleserver.py b/examples/wiznet5k_simpleserver.py index d8d0543..deb42fc 100644 --- a/examples/wiznet5k_simpleserver.py +++ b/examples/wiznet5k_simpleserver.py @@ -24,7 +24,7 @@ socket.set_interface(eth) server = socket.socket() # Allocate socket for the server server_ip = "192.168.10.1" # IP address of server -server_port = 50007 # Port to listen on +server_port = 50007 # Port to listen on server.bind((server_ip, server_port)) # Bind to IP and Port server.listen() # Begin listening for incoming clients From 6bbc69211e92a651cbb80edbf286c7da3dd0433b Mon Sep 17 00:00:00 2001 From: "adam_cummick:g9T51EHpC9gPQqG6sb9Q@gitlab.com" Date: Thu, 21 Jan 2021 08:40:00 -0500 Subject: [PATCH 6/6] Add timeout on close Adds additional timeout wait on socket close in __exit__ Corrects for occasional duplicate ACK packet that caused a 0 len message to be read. --- adafruit_wiznet5k/adafruit_wiznet5k_socket.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/adafruit_wiznet5k/adafruit_wiznet5k_socket.py b/adafruit_wiznet5k/adafruit_wiznet5k_socket.py index 7b1c373..eae8c12 100644 --- a/adafruit_wiznet5k/adafruit_wiznet5k_socket.py +++ b/adafruit_wiznet5k/adafruit_wiznet5k_socket.py @@ -118,6 +118,10 @@ def __exit__(self, exc_type, exc_val, exc_tb): if time.monotonic() - stamp > 1000: raise RuntimeError("Failed to disconnect socket") self.close() + stamp = time.monotonic() + while self.status != adafruit_wiznet5k.SNSR_SOCK_CLOSED: + if time.monotonic() - stamp > 1000: + raise RuntimeError("Failed to close socket") @property def socknum(self):