Introduction
In this post we are going to learn how to perform the resolution of a host name without the necessity for any dedicated infra-structure, using the mDNS protocol. We will be using two ESP32 devices and the Arduino core.
In many of the tutorials we have covered before with WiFi, it is common that we need to print the local IP address of the device, which is assigned after it connects to the network. The IP is needed for us to be able to reach the device somehow (to ping it, to perform a HTTP request, etc..). This is necessary because we don’t know beforehand which local IP will be assigned to the device and the IP can change over time, if we connect the device multiple times.
Although this process may be acceptable for simple use cases, in a real scenario application (for example, a commercial product), it would be hard to manage such a configuration process.
Although one option could be to assign a static IP address (check here how to do it), we don’t have guarantees that the address is already taken in a given network.
As such, mDNS is a viable possibility to overcome this issue. mDNS is a protocol that allows to make the resolution of locally defined names to IPs without the need for having a dedicated infra-structure (such as a DNS server) [1]. The mDNS protocol works operates over multicast UDP [2].
Note that we have already covered in the past how to setup an ESP32 to support host name resolution through mDNS (tutorial here). Nonetheless we have used a computer to perform the inquiry. In this tutorial we will focus in a scenario where it will be another ESP32 trying to obtain the IP address from a host name.
This is important since there are many Internet of Things architectures where devices talk with each other on a WiFi network. Having the possibility to perform the resolution of domain names into IPs without a dedicated infra-structure allows for a much simpler deploy of such solutions.
In terms of nomenclature, I’ll be referring to “Host device” to the ESP32 that will have a host name defined, and “Client device” to the ESP32 that will inquire the IP address for the host name.
The tests were performed using the Arduino core version 2.0.0 and the Arduino IDE version 1.8.16, working on Windows 8.1. The code was also tested on Platformio. Since two ESP32 devices were needed, a ESP32-E FireBeetle board and a ESP32 Firebeetle board were used (note that these are two distinct boards based on the ESP32 SoC).
The host device
We will start our code by the library includes. We will need the WiFi.h library, which will allow us to connect the ESP32 to a WiFi network, and the ESPmDNS.h library, which exposes the mDNS related functionality. Note that, by including the ESPmDNS.h lib, we will have access to an extern variable called MDNS, which we will use in the code below.
#include <WiFi.h>
#include <ESPmDNS.h>
Naturally, to be able to connect the ESP32 to a WiFi network, we will need its credentials. As such, we will define two global variables for the SSID (network name) and password. Please make sure to replace the placeholders I’ll be using by the the actual credentials of your network.
const char* ssid = "yourNetworkName";
const char* password = "yourNetworkPassword";
On the Arduino setup, we will start by opening a serial connection. This will allow us to print the IP address assigned to the host device once it connects to the WiFi network, which should match the one we will obtain after the client performs the resolution. Naturally, the point of the tutorial is to not need to print the IP address at all, and we are only doing it to confirm the resolution on the client is done correctly.
Serial.begin(115200);
Then we will connect the ESP32 to the WiFi network, using the previously defined credentials.
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
After the connection is established, we will print the local IP address assigned to the device.
Serial.println(WiFi.localIP());
Then, we will setup the mDNS responder with a call to the begin method on the MDNS extern variable. As input, we will pass a string with the host name we want to use. I’ll be using “esp32Host” for illustration purposes, but you can use something else if you prefer. Naturally, this is the same host name we will later use in the client code.
This method returns true on success and false otherwise. We will use this value for error checking and to retry the initialization in case it fails.
while(!MDNS.begin("esp32Host")) {
Serial.println("Starting mDNS...");
delay(1000);
}
After this, the ESP32 should now be able to respond to host name inquiries. Since all of that is handled automatically under the hood without the necessity for us to write more code, there’s nothing else we need to do in the Arduino setup nor in the loop. The full code is available below and contains some additional prints to help debugging.
#include <WiFi.h>
#include <ESPmDNS.h>
const char* ssid = "yourNetworkName";
const char* password = "yourNetworkPassword";
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
Serial.println(WiFi.localIP());
while(!MDNS.begin("esp32Host")) {
Serial.println("Starting mDNS...");
delay(1000);
}
Serial.println("MDNS started");
}
void loop() {}
The client device
Like before, we start by including the necessary libraries and the network credentials (SSID and password).
#include <WiFi.h>
#include <ESPmDNS.h>
const char* ssid = "yourNetworkName";
const char* password = "yourNetworkPass";
In the Arduino setup, we will start by opening a serial connection.
WiFi.begin(ssid, password);
Then we will connect the ESP32 to the WiFi network. For this device the IP address that is assigned to it by the router is irrelevant for our test, which is why we won’t be printing it after the connection is established.
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
Then we are going to initialize the mDNS interface. This time, we won’t follow the same approach we did with the host device, as it requires us to provide a host name. Since the client device only wants to perform the resolution of the host name into an IP address, it doesn’t need to have its own host name defined.
So, we will use the mdns_init IDF function instead, which doesn’t require us to provide a host name for the device. Still, we will be able to perform the resolution of the host name of another device.
On success, this function returns ESP_OK. As such, we will stay in a loop until we are able to perform the initialization successfully, since there’s no point in advancing if we cannot initialize mDNS. Naturally, in a real application scenario we would do a more adequate error handling.
while(mdns_init()!= ESP_OK){
delay(1000);
Serial.println("Starting MDNS...");
}
Once we initialize the mDNS interface, we can start the procedure to translate the host name into an IP address. The method we are going to use will return to us an object of class IPAddress. Consequently, we are going to create an empty IPAddress object to later assign the result of the method we need to call.
IPAddress serverIp;
Then, to perform the resolution, we simply need to call the queryHost method on the MDNS extern variable, passing as input a string with the host name. As output, like already mentioned, we will obtain an IPAddress object. As can be seen here, in its implementation the queryHost method will call IDF’s mdns_query_a function.
When calling this method, if the resolution of the address cannot be performed, we will receive an empty IPAddress object, which will contain the “0.0.0.0” address. As such, we will keep looking until we obtain a value different from this.
while (serverIp.toString() == "0.0.0.0") {
Serial.println("Resolving host...");
delay(250);
serverIp = MDNS.queryHost("esp32Host");
}
After we are able to resolve the host name, we will print its IP to the serial port. Naturally, it should match exactly the same value we obtain in the serial monitor when we run the code in the host device.
Serial.println(serverIp.toString());
The complete code is shown below.
#include <WiFi.h>
#include <ESPmDNS.h>
const char* ssid = "yourNetworkName";
const char* password = "yourNetworkPass";
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
while(mdns_init()!= ESP_OK){
delay(1000);
Serial.println("Starting MDNS...");
}
Serial.println("MDNS started");
IPAddress serverIp;
while (serverIp.toString() == "0.0.0.0") {
Serial.println("Resolving host...");
delay(250);
serverIp = MDNS.queryHost("esp32Host");
}
Serial.println("Host address resolved:");
Serial.println(serverIp.toString());
}
void loop() {}
Testing the code
To test the code, simply compile and upload the code from the two previous sections to each one of your ESP32 devices. The order doesn’t matter since the client will wait until the host is available, in case the client starts running first.
After uploading the program to your devices, open a serial monitor tool of your choice (ex: the Arduino IDE serial monitor or the platformio terminal) and connect to the host device. You should see a result similar to figure 1, where the IP address gets printed after the device connects to the WiFi network. Also, make sure to confirm that mDNS was started (a message will also be printed after a successful initialization).
Then connect to the client device. Once the host name resolution is performed, you should obtain the exact same IP address from before (the host IP address), as shown in figure 2.
Suggested ESP32 readings
References
[1] https://tools.ietf.org/html/rfc6762
[2] http://stackoverflow.com/questions/11835782/how-exactly-does-mdns-resolve-addresses
Thank you very much for the examples. I tried the server and the address esp32Host.local is know to computer on the same local network. But after a few minutes this doesn’t work any more.
Shouldn’t there be a MDNS.upkeep or MDNS.run or something like that in de loop()?