Skip to content

UDP send() does not clear esp32 write buffer #135

Open
@faithanalog

Description

@faithanalog

My Platform:

  • metro m4 airlift lite
  • adafruit nina-fw 1.7.3
  • circuitpython 6.2.0
  • April 20, 2021 library bundle

The problem:

udp.send() appends to the write buffer instead of replacing it. This results in the following behavior:

sock.send("line 1+\n")
sock.send("line 2++\n")
sock.send("line 3+++\n")

will actually send

line 1+             <--- datagram 1
line 1+             <--- datagram 2
line 2++               |
line 1+             <--- datagram 3
line 2++               |
line 3+++              |

and this continues until the esp32's internal write buffer is full. at which point calls to send() will only send the buffer and not append anything new.

The expected behavior is that a call to write() will send the provided data as a datagram packet and nothing else.

Why this happens

In the nina-fw WiFiUdp.c implementation we can see the following:

  • _sndSize tracks the write buffer size for the current packet
  • beginPacket() sets _sndSize to 0. This is the only place where _sndSize is set to 0.
  • endPacket() does not set _sndSize to 0.

The only nina-fw command that calls beginPacket() is the startTcpClient() command which, despite the outdated name, is also used to initialize UDP sockets.

sendUDPData() only calls endPacket(). It does not call beginPacket().

socket_write() has code roughly equivalent (with simplification and no handling of large buffers) to this:

if self.UDP_MODE:
    self._send_command_get_response(_INSERT_DATABUF_TCP_CMD, self._socknum_ll[0], memoryview(buffer))
    self._send_command_get_response(_SEND_UDP_DATA_CMD, self._socknum_ll)

_INSERT_DATABUF_TCP_CMD writes data to the UDP socket buffer, and then _SEND_UDP_DATA_CMD calls endPacket() which sends the current buffer as a datagram packet. beginPacket() is never called during a socket_write().

Workaround

Currently you can work around this problem by closing and re-opening the socket before every write. So every write() now becomes

sock.close()
sock.connect(socketaddr, conntype=esp.UDP_MODE)
sock.write()

The close() is necessary because connect() also opens a "server" port for receiving responses, and so connect() will fail if that server port is already open.

Potential fixes

It's not entirely clear to me whether this is a bug in esp32spi or in nina-fw. Changing esp32spi's socket_write to send a _START_SERVER_TCP_CMD message after sending _SEND_UDP_DATA_CMD would fix this issue. Changing nina-fw's sendUDPData() command to call beginPacket() again after endPacket() would also solve this issue. So would changing both to add a new command specifically to call beginPacket() into nina-fw and using it in esp32wifi.

So I'm happy to write a patch to fix this, but I don't know which project i should be writing the patch for.

Example code to cause this problem

import board
import time
from digitalio import DigitalInOut
import adafruit_esp32spi.adafruit_esp32spi_socket as socket
from adafruit_esp32spi import adafruit_esp32spi
from secrets import secrets

esp32_cs = DigitalInOut(board.ESP_CS)
esp32_ready = DigitalInOut(board.ESP_BUSY)
esp32_reset = DigitalInOut(board.ESP_RESET)
spi = board.SPI()
esp = adafruit_esp32spi.ESP_SPIcontrol(spi, esp32_cs, esp32_ready, esp32_reset)


HOST = "192.168.1.100"
PORT = 3000


def connect():
    esp.reset()
    if esp.status == adafruit_esp32spi.WL_IDLE_STATUS:
        print("ESP32 found and in idle mode")
    print("Firmware vers.", esp.firmware_version)
    print("MAC addr:", [hex(i) for i in esp.MAC_address])
    print("Connecting to AP...")
    while not esp.is_connected:
        try:
            esp.connect_AP(secrets["ssid"], secrets["password"])
        except RuntimeError as e:
            print("could not connect to AP, retrying: ", e)
            continue
    print("Connected to", str(esp.ssid, "utf-8"), "\tRSSI:", esp.rssi)
    print("My IP address is", esp.pretty_ip(esp.ip_address))

    

    socket.set_interface(esp)
    socketaddr = socket.getaddrinfo(HOST, PORT)[0][4]
    
    global sock
    sock = socket.socket(type=socket.SOCK_DGRAM)

    sock.connect(socketaddr, conntype=esp.UDP_MODE)


connect()

i = 0


while True:
    if esp.is_connected:
        sock.send("{}\n".format(i))
        i = i + 1
    else:
        print("wifi is down :<")
        sock.close()
        connect()
    time.sleep(1.0)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions