cfileuartesp-idf

Read lines from UART using fgets and select


I'm trying to use fgets in esp-idf to read a whole line from UART.

The basic reference code is the select example. So far, here my code:

#define DLP_RFID2_BUF_SIZE 256
static char buffer[DLP_RFID2_BUF_SIZE];

uart_config_t uart_config = 
{
    .baud_rate = 115200,
    .data_bits = UART_DATA_8_BITS,
    .parity    = UART_PARITY_DISABLE,
    .stop_bits = UART_STOP_BITS_1,
    .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
    .source_clk = UART_SCLK_DEFAULT,
};

ESP_ERROR_CHECK(uart_driver_install(UART_NUM_1, DLP_RFID2_BUF_SIZE * 2, 0, 0, NULL, 0));
ESP_ERROR_CHECK(uart_param_config(UART_NUM_1, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(UART_NUM_1, PIN_DLP_RFID2_TX, PIN_DLP_RFID2_RX, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));

if ((fd = open("/dev/uart/1", O_RDWR)) == -1) 
{
    ESP_LOGE(TAG, "Cannot open UART");
    return fd;
}

uart_vfs_dev_use_driver(UART_NUM_1);

here the loop function:

int s;
fd_set rfds;
struct timeval tv = {
    .tv_sec = 0,
    .tv_usec = 20000,
};

FD_ZERO(&rfds);
FD_SET(fd, &rfds);

s = select(fd + 1, &rfds, NULL, NULL, &tv);

if (s < 0) 
{
    ESP_LOGE(TAG, "Select failed: errno %d (%s)", errno, strerror(errno));
} 
else if (s == 0) 
{
    ESP_LOGI(TAG, "Timeout has been reached and nothing has been received");
} 
else 
{
    if (FD_ISSET(fd, &rfds)) 
    {
        if (fgets(buffer, sizeof(buffer), ???))
        {
            // do something
        }
    } 
    else 
    {
        ESP_LOGE(TAG, "No FD has been set in select()");
    }
}

the fgets function requires a FILE * variable as third parameter. But I only have ints (fd, s) and fd_set (rfds).

I tried to change the code in order to use FILE *:

FILE *f = fopen("/dev/uart/1", "rw");

// ...
if (fgets(buffer, sizeof(buffer) - 1, f))
{
    // do something
}

since fgets is blocking I need to check if there are some data first. But now select requires an fd_set and not a FILE *:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);

How can I "convert" FILE * to fd_set or int to FILE *?


Solution

  • Your problem is that you need to use a file descriptor for select(2), but need a FILE for fgets(3).

    There are two possibilities here.

    Given a FILE object, you may be able to use the function fileno to get the corresponding file descriptor for use in select.

    Alternatively, if you initially open the file using open(2), to get a file descriptor, you may be able to use fdopen(3) to create a FILE object from it.

    I say may in both cases, because neither fileno nor fdopen is defined in the C standard, so if you're in a restricted environment (which your mention of UART hints at) it's possible they're not available. They're both very widely implemented, though, so if your library is big enough to have fgets, it'll surely have at least one of the two.


    Note that there's no guarantee that these functions will work. The C standard (2018, Sect.7.21) says that the FILE struct...

    is an object type capable of recording all the information needed to control a stream, including its file position indicator, a pointer to its associated buffer (if any), an error indicator that records whether a read/write error has occurred, and an end-of-file indicator that records whether the end of the file has been reached

    Thus it doesn't say anything about file descriptors, and specifically doesn't require that the struct contains any of the information that would enable either fileno or fdopen to work. So on paper, all bets are off here, and it would be quite legitimate for a C library to implement FILE in such a way. But that would be unusual in most ‘normal’ C contexts.