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: