adafruit / Adafruit_CircuitPython_Wiznet5k

Pure-Python interface for WIZNET 5k Ethernet modules
Other
16 stars 36 forks source link

WIZnet 5500 and 5100S TCP (and UDP) servers are not working #50

Closed anecdata closed 1 year ago

anecdata commented 2 years ago

This presumably used to work, or works with some variation that's not apparent to me, as there are examples in the repo.

TCP and UDP clients are fine with both WIZnet devices, and are interoperable with CPython.

I've tried a number of variations (including the example in this repo, with only the necessary locale changes), but the client and server structure below works for CPython and ESP32-S2, but not on RP2040+Ethernet FeatherWing: Adafruit CircuitPython 7.2.0-alpha.1-224-gac7a80753 on 2022-01-26; Adafruit Feather RP2040 with rp2040 or on WIZnet W5100S-EVB-Pico: Adafruit CircuitPython 7.2.0-alpha.1-224-gac7a80753 on 2022-01-26; Raspberry Pi Pico with rp2040

Info below refers to a configuration with a CPython TCP client, and the TCP Server on Adafruit Feather RP2040 with an Adafruit Ethernet FeatherWing...

Client code:

import socket
import time

# edit host and port to match server
HOST = "192.168.6.247"
PORT = 5000
TIMEOUT = 60
INTERVAL = 5
MAXBUF = 256

while True:
    print("Create TCP Client Socket")
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(TIMEOUT)

    print(f"Connecting to {HOST}, {PORT}")
    s.connect((HOST, PORT))

    size = s.send(b'Hello, world')
    print("Sent", size, "bytes")

    buf = s.recv(MAXBUF)
    print('Received', buf)

    s.close()

    time.sleep(INTERVAL)

Server code:

import board
import busio
import digitalio
import time
from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K
import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket

# Adafruit featherWing (custom CS) on Feather RP2040
eth_cs = digitalio.DigitalInOut(board.D25)
eth = WIZNET5K(board.SPI(),
               eth_cs, mac=(0xDE, 0xAD, 0xBE, 0xEF, 0x06, 0xF7),
               is_dhcp=True,
               debug=False)

HOST = eth.pretty_ip(eth.ip_address)
PORT = 5000
TIMEOUT = 0
MAXBUF = 256

print("Create TCP Server Socket")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(TIMEOUT)

s.bind((HOST, PORT))
s.listen()
print(f"Listening on {HOST}, {PORT}")

while True:
    print("Accepting connections")
    conn, addr = s.accept()
    conn.settimeout(TIMEOUT)
    print("Accepted from", addr)

    buf = conn.recv(MAXBUF)
    print("Received", buf, "from", addr)

    size = conn.sendall(buf)
    print("Sent", buf[:size], size, "bytes to", addr)

    conn.close()

Client output:

Create TCP Client Socket
Connecting to 192.168.6.247, 5000
Sent 12 bytes
Traceback (most recent call last):
  File "tcp_client_CPython.py", line 22, in <module>
    buf = s.recv(MAXBUF)
socket.timeout: timed out

Server output:

code.py output:
Create TCP Server Socket
Listening on 192.168.6.247, 5000
Accepting connections
Accepted from ('192.168.5.32', 58853)
# it hangs there until control-C

Server Debug output:

*** Get socket
Allocated socket #0
server_ip 192.168.6.247
server_port 5000
* Listening on port=5000, ip=192.168.6.247
*** Opening socket 0
* Opening W5k Socket, protocol=33
*** Get socket
Allocated socket #1
* Dest is (192.168.5.32, 58082), Next listen socknum is #1
*** Get socket
Allocated socket #1
* Listening on port=5000, ip=192.168.6.247
*** Opening socket 1
* Opening W5k Socket, protocol=33
Connection accepted from <socket object at 0x20021e40> ('192.168.5.32', 58082)
* socket_available called on socket 0, protocol 33
Bytes avail. on sock:  12
     * Processing 12 bytes of data
* socket_available called on socket 0, protocol 33
* socket_available called on socket 0, protocol 33
* socket_available called on socket 0, protocol 33
* socket_available called on socket 0, protocol 33
* # ad infinitum...

Client Exception if control-C during receive:

Sent 12 bytes
^CTraceback (most recent call last):
  File "tcp_client_CPython.py", line 22, in <module>
    buf = s.recv(MAXBUF)
KeyboardInterrupt

Server Exception if control-C after accept (before client times out):

Accepted from ('192.168.5.32', 59624)
Traceback (most recent call last):
  File "code.py", line 34, in <module>
  File "adafruit_wiznet5k/adafruit_wiznet5k_socket.py", line 301, in recv
  File "adafruit_wiznet5k/adafruit_wiznet5k_socket.py", line 406, in available
  File "adafruit_wiznet5k/adafruit_wiznet5k.py", line 518, in socket_available
  File "adafruit_wiznet5k/adafruit_wiznet5k.py", line 868, in _get_rx_rcv_size
  File "adafruit_wiznet5k/adafruit_wiznet5k.py", line 908, in _read_snrx_rsr
  File "adafruit_wiznet5k/adafruit_wiznet5k.py", line 963, in _read_socket
  File "adafruit_wiznet5k/adafruit_wiznet5k.py", line 456, in read
KeyboardInterrupt: 

Server Exception if control-C after accept and after client times out:

Accepted from ('192.168.5.32', 59881)
Traceback (most recent call last):
  File "code.py", line 34, in <module>
  File "adafruit_wiznet5k/adafruit_wiznet5k_socket.py", line 301, in recv
  File "adafruit_wiznet5k/adafruit_wiznet5k_socket.py", line 406, in available
  File "adafruit_wiznet5k/adafruit_wiznet5k.py", line 518, in socket_available
  File "adafruit_wiznet5k/adafruit_wiznet5k.py", line 870, in _get_rx_rcv_size
  File "adafruit_wiznet5k/adafruit_wiznet5k.py", line 908, in _read_snrx_rsr
  File "adafruit_wiznet5k/adafruit_wiznet5k.py", line 963, in _read_socket
  File "adafruit_wiznet5k/adafruit_wiznet5k.py", line 456, in read
KeyboardInterrupt: 

(note different line number in _get_rx_rcv_size)

anecdata commented 2 years ago

Turns out WIZnet TCP Server works if the server sets a timeout for the accepted connection. This is different than in CPython and CircuitPython Espressif native:

The following works in CPYthon (and CircuitPython Espressif native), with blocking on the accepted connection:

Server:

#!/usr/bin/env python3
import socket

HOST = ""
PORT = 5010
TIMEOUT = None
# CONN_TIMEOUT = 5
MAXBUF = 256

print("Create TCP Server Socket")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(TIMEOUT)

s.bind((HOST, PORT))
s.listen()
print("Listening")

while True:
    print("Accepting connections")
    conn, addr = s.accept()
    conn.settimeout(TIMEOUT)  # CONN_TIMEOUT for WIZnet
    print("Accepted from", addr)

    buf = conn.recv(MAXBUF)
    print("Received", buf, "from", addr)

    size = conn.sendall(buf)
    print("Sent", buf[:size], size, "bytes to", addr)

    conn.close()

Client:

#!/usr/bin/env python3
import socket
import time

# edit host and port to match server
HOST = "localhost"
PORT = 5010
TIMEOUT = 10
INTERVAL = 5
MAXBUF = 256

while True:
    print("Create TCP Client Socket")
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(TIMEOUT)

    print(f"Connecting to {HOST}, {PORT}")
    s.connect((HOST, PORT))

    size = s.send(b'Hello, world')
    print("Sent", size, "bytes")

    buf = s.recv(MAXBUF)
    print('Received', buf)

    s.close()

    time.sleep(INTERVAL)

I think what may be happening is that in the other two contexts, the server recognizes that the client has closed its end of the connection and the server stops reading, but the WIZnet socket does not.

anecdata commented 2 years ago

Another quirk:

CPython:

>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> 
>>> _ = s.gettimeout()
>>> _, repr(_), type(_)
(None, 'None', <class 'NoneType'>)
>>> # CPython default: blocking; ∞ timeout
>>> 
>>> s.settimeout(None)
>>>_, repr(_), type(_)
(None, 'None', <class 'NoneType'>)

WIZnet5k library:

>>> s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>>> 
>>> _ = s.gettimeout()
>>> _, repr(_), type(_)
(0, '0', <class 'int'>)
>>> # WIZnet default: non-blocking; 0 timeout
>>> 
>>> s.settimeout(None)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "adafruit_wiznet5k/adafruit_wiznet5k_socket.py", line 413, in settimeout
TypeError: unsupported types for __lt__: 'NoneType', 'int'
>>> # can't set sockets to blocking

Addendum: as of 2.0.0, this is now consistent with CPython:

>>> _ = s.gettimeout()
>>> _, repr(_), type(_)
(None, 'None', <class 'NoneType'>)
>>> s.settimeout(None)
>>> 
anecdata commented 1 year ago

With recent changes to the library, WIZnet server now works.

WIZnet Server test code:

import board
import busio
import digitalio
import time
from adafruit_wiznet5k.adafruit_wiznet5k import WIZNET5K
import adafruit_wiznet5k.adafruit_wiznet5k_socket as socket

# WIZnet Ethernet Hat on Raspberry Pi Pico [W]
cs = digitalio.DigitalInOut(board.GP17)
spi = busio.SPI(board.GP18, MOSI=board.GP19, MISO=board.GP16)
eth = WIZNET5K(spi, cs, mac=(0xDE, 0xAD, 0xBE, 0xEF, 0x06, 247), is_dhcp=True)

HOST = eth.pretty_ip(eth.ip_address)
PORT = 5000
TIMEOUT = None
MAXBUF = 256

print(f"WIZnet Client IP Address: {HOST}")

print("Create TCP Server Socket")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(TIMEOUT)

s.bind((HOST, PORT))
s.listen()
print(f"Listening on {HOST}, {PORT}")

buf = bytearray(MAXBUF)
while True:
    print("Accepting connections")
    conn, addr = s.accept()
    conn.settimeout(TIMEOUT)
    print("Accepted from", addr)

    size = conn.recv_into(buf, MAXBUF)
    print("Received", buf[:size], size, "bytes")

    conn.send(buf[:size])
    print("Sent", buf[:size], size, "bytes")

    conn.close()

CPython client test code:

#!/usr/bin/env python3
import socket
import time

# edit host and port to match server
HOST = "192.168.6.247"
PORT = 5000
TIMEOUT = 60
INTERVAL = 5
MAXBUF = 256

while True:
    print("Create TCP Client Socket")
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(TIMEOUT)

    print("Connecting")
    s.connect((HOST, PORT))

    size = s.send(b'Hello, world')
    print("Sent", size, "bytes")

    buf = s.recv(MAXBUF)
    print('Received', buf)

    s.close()

    time.sleep(INTERVAL)