UDPSocket.sendTo() in an Ticker interrupt context

If you have already implemented the functions for the other tasks (air conditioning etc.) then move them to thread task functions. Or keep them in the main thread. For example:

#include "mbed.h"

const float             ROOM_TEMP_MAX = 24.0f;

EventQueue              queue(32 * EVENTS_EVENT_SIZE);
Thread                  sendThread;
UDPSocket               sock;
SocketAddress           address("192.168.1.12");
const char              data[] = { 1, 2, 3 };
nsapi_size_t            sze = sizeof(data);
volatile nsapi_error_t  status;
Thread                  recvThread;
volatile bool           sockStateChanged = false;
SocketAddress           peer;
uint8_t                 buff[256];

Thread                  airCondThread;
float                   roomTemp;

void sendUdpMessage()
{
    status = sock.sendto(address, data, sze);
}

void onSockStateChanged()
{
    sockStateChanged = true;
}

void recvPoll()
{
    while(1) {
        if (sockStateChanged) {
            sockStateChanged = false;
            nsapi_size_or_error_t n = sock.recv(buff, sizeof(buff));
            if (n > 0) {
                nsapi_error_t   error = sock.getpeername(&peer);
                if (error == NSAPI_ERROR_OK) {
                    printf("Data received from %s:\r\n", peer.get_ip_address());
                    for (int i = 0; i < n; i++) {
                        printf("data[%i] = Ox%.2x\r\n", i, buff[i]);
                    }
                    if (strcmp(peer.get_ip_address(), "192.168.1.15") == 0) {
                        roomTemp = (float)*buff;
                    }
                }
            }
        }
        ThisThread::sleep_for(50);
    }
}

void airCondTask()
{
    while(1) {
        if (roomTemp > ROOM_TEMP_MAX) {
            printf("Turn on cooling\r\n");
            ...
        }
        if (roomTemp < (ROOM_TEMP_MAX - 1.0f) {
            printf("Turn off cooling\r\n");
            ...
        }
        ThisThread::sleep_for(1000);
    }
}

int main()
{
    sock.set_blocking(false);
    sock.sigio(callback(onSockStateChanged));
    recvThread.start(callback(recvPoll));
    airCondThread.start(callback(airCondTask));
    sendThread.start(callback(&queue, &EventQueue::dispatch_forever));
    queue.call_every(2000, sendUdpMessage);
    while(1) {
        // These tasks in main thread run in sequence (not in parallel)
        moveServo1();
        moveServo2();
        ...

        ThisThread::sleep_for(50);  // Let other threads run too
    }
}

I use EventQueue for TCP/UDP applications all the time. I have never seen such delays. Perhaps, sigio() doesn’t work the way you expect?

https://os.mbed.com/docs/mbed-os/v5.15/mbed-os-api-doxy/class_socket.html#a59ccc5950bb6b32b712444b2a92c0631

Try to sleep the main thread for a while.

int main()
{
    ...
    while(1) {
        ...

        ThisThread::sleep_for(50);  // Let other threads run too
    }
}

The main thread runs tasks that are basically an array of function pointers along with a time indicating how often they should run. The main thread does not really ever sleep, so that could be it. But shouldn’t the event queue thread still be allowed to go more or less immediately as it has a higher priority?

I’ll add in a task into the main thread’s task list that basically sleeps a while every second and see it it helps.

I have read that many times, of course. I don’t see anything there that would imply delays, just that there could be spurious calls (which is likely what I see when I do my accept() on the main server socket). But unless sigiocan be assumed to immediately call my callback, what is there to allow me to build async socket code? I can’t dedicate a thread to all my I/O needs.

On the other hand, I do Modbus and RS485 also using interrupts, and there my callbacks are always called immediately. Never any weird delays. So there’s something about the combination of sigio, TCPSocket and EventQueue that’s not working for me. Probably I’m holding them wrong, but how remains the question.

A supported async solution would be nice to have.

I did some testing with a simple Queue where I basically push function pointers to get out of the ISR context. The data also stores a time when the data was put to the queue and when it was fetched out by the “user context” thread for execution. I see that the data is on the thread maybe 10 ms in general.

So the up to 3-4 second delay I see from a client connecting and me accept() the socket to the first data becoming available on the socket has nothing to do with the EventQueue. The delay I see is either minimal (immediate reaction) or 3-4 seconds, nothing in between. Nothing my other tasks do take that long and the main thread sleeps 100 ms every 500ms. So there should be plenty of time for the data to propagate to the sigio handler for my socket. If the CPU was totally bogged down with other stuff I would assume the delay to vary from 0 to 4 seconds, not be either nothing or 3-4 seconds.

In the past I have seen LWIP totally die when handling TCP sockets, so my guess here is that there’s something fishy going on deep down in the bowels of LWIP code.

Looking closer at the delay, it seems to be pretty much always exactly 3000 ms from when the sigio on the server socket where the client is accepted to the sigio for the first data on the client socket. Always 3000 or some ms over. Leads me to start grepping for the value “3000” in the MBed sources. Looks like some timeout or similar.

I think this thread has drifted far off its original purpose. Most problems have been worked around. Thank you to all who helped!