our product uses the ESP32's WiFi to connect to the internet. To allow our end-customers to enter their WiFi SSID and Password, we enable both station mode and access point mode at the same time. Using LWIP, we create a socket(), bind() it to port 80, listen() and serve a configuration interface with an input form when someone connect via HTTP.
So far, so good. When we get WiFi credentials, we attempt to connect the ESP32's WiFi station to the given access point. This itself works well, it will connect successfully. The trouble is with the HTTP server running on our access point: Most of the time (not always), it stops responding to incoming requests. The ESP32's WiFi access point remains up, other devices are still connected to it, but they can't reach its port 80 any more.
Further observations:
- The HTTP server doesn't break if the SSID provided by the user does not exist
- The HTTP server does break if the SSID exists, but the password is wrong
- I tried to close() the server socket and re-initialize it as soon as the ESP32's station connects to get a fresh socket, but it doesn't help either.
- Debugging into the HTTP server process revealed that the select() we use to wait for incoming connections times out every time as soon as the station connects - just as if nobody attempted to connect.
HTTP server socket initialization:
Code: Select all
struct sockaddr_in server_addr = { 0, 0, 0, 0, 0 };
std::memset(&server_addr, 0, sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(serverPort);
this->serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if(this->serverSocket < 0) {
LOG_ERROR(LOG_TAG, "Failed to create server socket: %d", errno);
return;
}
// Timeval struct for timeout - {secs, usecs}
struct timeval tv = { 1, 0 };
if(setsockopt(this->serverSocket, SOL_SOCKET, SO_RCVTIMEO, (const char*) &tv, sizeof(struct timeval)) < 0) {
LOG_ERROR(LOG_TAG, "Failed to set socket timeout: %d", errno);
}
int reuseAddress = 1;
if(setsockopt(this->serverSocket, SOL_SOCKET, SO_REUSEADDR, &reuseAddress, sizeof(reuseAddress)) < 0) {
LOG_ERROR(LOG_TAG, "Failed to enable address reuse for socket: %d", errno);
}
if(bind(this->serverSocket, (struct sockaddr*) (&server_addr), sizeof(struct sockaddr)) < 0) {
LOG_ERROR(LOG_TAG, "Failed to bind socket to port %d: %d", serverPort, errno);
return;
}
if(listen(this->serverSocket, 5) < 0) {
LOG_ERROR(LOG_TAG, "Failed to set socket to listen mode: %d", errno);
return;
}
Code: Select all
struct sockaddr_in client_addr = { 0, 0, 0, 0, 0 };
socklen_t sin_size = sizeof(client_addr);
// Create socketSet to be able to use SELECT instaed of ACCEPT to enable timeouts.
static fd_set socketSet;
// Initialize the set of active sockets.
FD_ZERO (&socketSet);
FD_SET (serverSocket, &socketSet);
// Set timeout for select {secs, usecs}
struct timeval timeout = { 1, 0 };
int clientSocket = -1;
int rv = select(serverSocket + 1, &socketSet, nullptr, nullptr, &timeout);
if (rv < 0) {
// Error on select.
LOG_ERROR(LOG_TAG, "select failed with %d", errno);
return;
} else if (rv == 0) {
// Timeout occurred. This is where we land after the station connects.
return;
} else {
// Client actually connected -> accept,
clientSocket = accept(serverSocket, (struct sockaddr*) &client_addr, &sin_size);
}
// Do HTTP server stuff with clientSocket ...
- Is there something fundamentally wrong with my approach?
- Can I bind a socket to a specific interface (ESP_IF_WIFI_AP in this case)?
- Do you need other sections of our code to find a solution?
David