In this tutorial, I will walk through using UDP and mDNS with the ESP32 platform. I’ll cover how to set up UDP communication between an ESP32 and a client device, along with using mDNS to allow the client to discover the ESP32 on the network without needing to know its IP address. The code provided will demonstrate how to send data between the ESP32 and a client, handle heartbeats, and ensure the client can reconnect if communication is lost.
Prerequisites
Basic knowledge of ESP32 and its Wi-Fi capabilities.
Familiarity with Arduino IDE or PlatformIO for ESP32 development.
Understanding of network communication concepts such as UDP and mDNS.
What is UDP and mDNS?
UDP (User Datagram Protocol) is a communication protocol that allows devices to exchange messages over a network. It’s a connectionless protocol, meaning data is sent without establishing a dedicated connection, which makes it faster but less reliable than TCP.
mDNS (Multicast DNS) allows devices on a local network to discover each other without the need for a central DNS server. It’s very useful for local network services, as it enables devices to advertise their presence and be discovered by name (like smart_knob.local in our case).
Setting up the ESP32 as a UDP Server with mDNS
First, we set up the ESP32 to connect to Wi-Fi, initialize an mDNS service, and create a UDP server to handle communication.
Here’s the core setup code for the ESP32:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
voidWifiTask::run(){ setupWiFi();
// MDNS if (!MDNS.begin("smart_knob")) { ESP_LOGI(TAG, "mDNS failed to start"); return; } ESP_LOGI(TAG, "mDNS started successfully, device name is smart_knob.local"); MDNS.addService(udpServiceName, "udp", localUdpPort);
udp.begin(localUdpPort); heartbeatUdp.begin(heartbeatPort); tcpServer.begin(); ESP_LOGI(TAG, "UDP service started, port number: %d", localUdpPort); ESP_LOGI(TAG, "Heartbeat service started, port number: %d", heartbeatPort); ESP_LOGI(TAG, "TCP service started, waiting for IP configuration, port number: %d", 4321); }
In this block of code:
The setupWiFi() function connects the ESP32 to the Wi-Fi network.
mDNS is initialized with the name smart_knob.local using MDNS.begin(). This allows the ESP32 to be discovered on the network without needing its IP address.
UDP services for data transmission (localUdpPort) and heartbeat monitoring (heartbeatPort) are started.
A TCP server is also initialized to receive the client’s IP configuration.
Handling UDP Heartbeats
The heartbeat mechanism ensures that communication between the client and ESP32 is kept alive. If the ESP32 doesn’t receive a heartbeat within the specified timeout period, it assumes the client has disconnected.
1 2 3 4 5 6 7 8 9 10 11 12
int packetSize = heartbeatUdp.parsePacket(); if (packetSize) { char incomingPacket[255]; int len = heartbeatUdp.read(incomingPacket, 255); if (len > 0) { incomingPacket[len] = 0; } String message = String(incomingPacket); if (message == "heartbeat") { lastHeartbeat = millis(); } }
This code block listens for incoming heartbeat messages and resets the timer whenever it receives the expected heartbeat message.
Creating a Client in Python
On the client side, I will use Python to send and receive UDP messages and handle mDNS discovery.
# Resolve mDNS name to IP address try: self.esp32_ip = socket.gethostbyname(self.esp32_host) except socket.gaierror: return # Bind the UDP socket to the client’s IP self.client_udp_socket.bind((self.client_ip, self.udp_port))
Create a UDP connection to communicate with the ESP32.
Periodically send heartbeat messages to ensure the connection remains active.
Conclusion
In this project, I demonstrated how to use UDP and mDNS on the ESP32 for seamless communication with a client device. The ESP32 was set up as a UDP server, handling both data transmission and heartbeat monitoring, while the client communicated with it using Python.
This setup is highly efficient for local networks where devices need to be discovered and communicate without requiring a central server or hardcoded IP addresses.
Full Code
In this section, I provide the full code for the ESP32 and Python client to implement the UDP and mDNS communication. For the ESP32 codes, FreeRTOS is used to handle the tasks and queues for communication.
// publish is used for the logger voidWifiTask::publish(const PB_SmartKnobConfig & config){ for (auto listener : listeners_) { xQueueOverwrite(listener, &config); // xQueueOverwrite will overwrite the queue if it is full } }
voidWifiTask::run(){ setupWiFi();
// MDNS if (!MDNS.begin("esp32")) { ESP_LOGI(TAG, "mDNS failed to start"); return; } ESP_LOGI(TAG, "mDNS started successfully, device name is esp32.local"); MDNS.addService(udpServiceName, "udp", localUdpPort);
udp.begin(localUdpPort); heartbeatUdp.begin(heartbeatPort); tcpServer.begin(); ESP_LOGI(TAG, "UDP service started, port number: %d", localUdpPort); ESP_LOGI(TAG, "Heartbeat service started, port number: %d", heartbeatPort); ESP_LOGI(TAG, "TCP service started, waiting for IP configuration, port number: %d", 4321);
for (;;) { // Check if we have received the client's IP over TCP if (clientIp == "") { WiFiClient tcpClient = tcpServer.available(); if(isUDPReconnected){ Serial.print("."); vTaskDelay(1000 / portTICK_PERIOD_MS); } if (tcpClient) { ESP_LOGI(TAG, "Client connected via TCP."); while (tcpClient.connected() && tcpClient.available() == 0) { delay(100); } if (tcpClient.available()) { clientIp = tcpClient.readString(); ESP_LOGI(TAG, "Received client IP: %s", clientIp.c_str()); } isUDPConnected = true; isUDPReconnected = true; tcpClient.stop(); } } else { // Check for UDP heartbeat int packetSize = heartbeatUdp.parsePacket(); if (packetSize) { char incomingPacket[255]; int len = heartbeatUdp.read(incomingPacket, 255); if (len > 0) { incomingPacket[len] = 0; } String message = String(incomingPacket); if (message == "heartbeat") { lastHeartbeat = millis(); } }
// Check heartbeat timeout if (millis() - lastHeartbeat > heartbeatTimeout) { ESP_LOGW(TAG, "No heartbeat received, stopping communication."); ESP_LOGW(TAG, "Waiting for client to reconnect."); clientIp = ""; isUDPConnected = false; tcpServer.begin(); } } } }
defsend_ip_configuration(self): """Send the client IP address to ESP32 over TCP.""" try: # Connect to ESP32 TCP server self.client_tcp_socket.connect((self.esp32_ip, self.esp32_tcp_port))
# Send local IP address of this machine to ESP32 local_ip = self.client_ip self.client_tcp_socket.sendall(local_ip.encode())
# Close TCP connection self.client_tcp_socket.close() except Exception as e: # rospy.logerr(f"Error during TCP communication: {e}") pass
defrequest_callback(self, msg): # Callback function when a message is received on 'esp32_request' topic self.send_udp_message(msg.data)
defsend_heartbeat(self): """Send heartbeat messages periodically to the ESP32.""" # while not rospy.is_shutdown(): whileTrue: message = "heartbeat" try: # Send heartbeat message to ESP32 self.client_hearbeat_socket.sendto(message.encode(), (self.esp32_ip, self.heartbeat_udp_port))
except Exception as e: pass time.sleep(self.heartbeat_interval)
defreceive_udp_messages(self): # while not rospy.is_shutdown(): whileTrue: try: # Receive data from ESP32 data, addr = self.client_udp_socket.recvfrom(1024) response = data.decode() print(f"Received response from {addr}: {response}") # Publish the response to a ROS topic self.pub.publish(response)
except socket.timeout: print("Timeout occurred, no data received.") continue except Exception as e: pass
defrun(self): whileTrue: pass
if __name__ == '__main__': try: client = ESP32UDPClient() client.run()
except KeyboardInterrupt: pass
I also provide a simple mDNS service discovery code in Python to discover the ESP32 device on the network: