stop() on WiFiUdp has no effect

On a recent Arduino Uno with a WiFi shield and with the latest firmware and libraries, I want my program to occasionally (say, once every day) update the time using NTP.

It generally works, and I have written code for connecting to the wifi network, sending UDP request and parsing the NTP reply. As the next step, I plan to make it robust: if the wifi connection is lost it should periodically try to reconnect, and if an NTP request fails it should periodically try again.

Specifically, in the case of lost connection, I guess (not tried yet, really) that the WiFiUdp instance needs to be reinitialised. I tried to do that with stop() and begin() but failed.

Another reason to use stop() and begin() is that I would like to allocate the WiFiUdp instance on the stack, as a local variable, because it is used only once in a while, and the same happens for other memory structures which, if allocated all together, may consume too much memory.

In short, if I have no problems with this pseudo-code:

begin();
for (;;) {
  sendrequest();
  getreply();
  delay();
}

which has worked reliably for hours with a request every ten seconds, but this one does not work:

for (;;) {
  begin();
  sendrequest();
  getreply();
  stop();
  delay();
}

In this second case, the program correctly receives the first reply, but fails from the second time on. Additionally, it seems that stop() does not deallocate the internal structures, because begin() fails from the fifth time on, meaning that there are no more sockets available.

I can prepare a minimal sketch showing the problem and post that if necessary.

Thanks for reading and for any insight into this problem.

I find this interesting:

uint8_t WiFiUDP::begin(uint16_t port) {

    uint8_t sock = WiFiClass::getSocket();
    if (sock != NO_SOCKET_AVAIL)
    {
        ServerDrv::startServer(port, sock, UDP_MODE);
        WiFiClass::_server_port[sock] = port;
        _sock = sock;
        _port = port;
        return 1;
    }
    return 0;

}

The begin() method calls the startServer() method.

void WiFiUDP::stop()
{
      if (_sock == NO_SOCKET_AVAIL)
        return;

      ServerDrv::stopClient(_sock);

      _sock = NO_SOCKET_AVAIL;
}

The stop() method calls the stopClient() method. Once the server gets started, there doesn't seem to be a way to stop and restart it.

Hm.

Thank you, I had not looked at the sources, but from your excerpt I see that the IP number is not initialised, only the port is. This looks reasonable, given that I suppose that the stack is single-homed.

What happens if the network goes down, we disconnect and reconnect and Arduino receives a different IP address on reconnection? Alright, probably nothing: the server will keep listening on the old port with the new address. As soon as I find the time I’ll try that, which would make the disconnection-reconnection case into a non-problem.

However, this means that deallocating the WiFiUdp object is not possible, maybe not a bug but definitely a missing feature and a misleading description in the manual. I reported this on https://github.com/arduino/Arduino/issues/1637.

One question remains: what is the purpose of WiFiUdp.stop()?

UDP.stop() stops the socket from listening on that port. UDP and TCP servers must listen. This should fix your problem. See my comment for the fix and the test. https://github.com/arduino/Arduino/issues/1637

edit: Note that the WiFiUdp.begin() function also needs a modification to work with client requests.

PaulS seems to be correct. I can keep the sockets from depleting, but not the stop() function from crashing the udp "server". Apparently there is a way to start a udp "client", but the udp "client" can't receive a packet (doesn't listen), only send.

The stop() function is apparently designed to stop a udp "client" socket, not a "server" socket. This is different from the ethernet shield. You can successfully start and stop udp multiple times with it. But it also handles a tcp server differently than the wifi shield.

Why do you want to start and stop the udp server? Maybe there is a workaround.

This is from WiFi/utility/server_drv.cpp.

// Start server TCP on port specified
void ServerDrv::startServer(uint16_t port, uint8_t sock, uint8_t protMode)
{
    WAIT_FOR_SLAVE_SELECT();
    // Send Command
    SpiDrv::sendCmd(START_SERVER_TCP_CMD, PARAM_NUMS_3);
    SpiDrv::sendParam(port);
    SpiDrv::sendParam(&sock, 1);
    SpiDrv::sendParam(&protMode, 1, LAST_PARAM);

    //Wait the reply elaboration
    SpiDrv::waitForSlaveReady();

    // Wait for reply
    uint8_t _data = 0;
    uint8_t _dataLen = 0;
    if (!SpiDrv::waitResponseCmd(START_SERVER_TCP_CMD, PARAM_NUMS_1, &_data, &_dataLen))
    {
        WARN("error waitResponse");
    }
    SpiDrv::spiSlaveDeselect();
}

// Start server TCP on port specified
void ServerDrv::startClient(uint32_t ipAddress, uint16_t port, uint8_t sock, uint8_t protMode)
{
    WAIT_FOR_SLAVE_SELECT();
    // Send Command
    SpiDrv::sendCmd(START_CLIENT_TCP_CMD, PARAM_NUMS_4);
    SpiDrv::sendParam((uint8_t*)&ipAddress, sizeof(ipAddress));
    SpiDrv::sendParam(port);
    SpiDrv::sendParam(&sock, 1);
    SpiDrv::sendParam(&protMode, 1, LAST_PARAM);

    //Wait the reply elaboration
    SpiDrv::waitForSlaveReady();

    // Wait for reply
    uint8_t _data = 0;
    uint8_t _dataLen = 0;
    if (!SpiDrv::waitResponseCmd(START_CLIENT_TCP_CMD, PARAM_NUMS_1, &_data, &_dataLen))
    {
        WARN("error waitResponse");
    }
    SpiDrv::spiSlaveDeselect();
}

// Start server TCP on port specified
void ServerDrv::stopClient(uint8_t sock)
{
    WAIT_FOR_SLAVE_SELECT();
    // Send Command
    SpiDrv::sendCmd(STOP_CLIENT_TCP_CMD, PARAM_NUMS_1);
    SpiDrv::sendParam(&sock, 1, LAST_PARAM);

    //Wait the reply elaboration
    SpiDrv::waitForSlaveReady();

    // Wait for reply
    uint8_t _data = 0;
    uint8_t _dataLen = 0;
    if (!SpiDrv::waitResponseCmd(STOP_CLIENT_TCP_CMD, PARAM_NUMS_1, &_data, &_dataLen))
    {
        WARN("error waitResponse");
    }
    SpiDrv::spiSlaveDeselect();
}