How to advise customer of WiFi.localIP address after connection to WiFi

I am creating a device for measuring the water level in a water tank using a sonar device and then sending that info by WiFi in a web page created on the ESP32 Dev Module on the local WiFi network.

Question:
The code posted here works fine but my question is How do I inform the customer of the WiFi.localIP address assigned by WiFiManager (tzapu) after connection?
The customer needs to know the IP address assigned by DHCP to see the web page.

In the code posted here I have assigned a static IP, but not all routers in the world have the same IP numbering or gateway address, so this is not a solution.
My experience level is intermediate.
Any help with this is much appreciated.

[code]
/*
 WiFiManager with saved textboxes Demo
 wfm-text-save-demo.ino
 Saves data in JSON file on ESP32
 Uses SPIFFS

 DroneBot Workshop 2022
 https://dronebotworkshop.com

 Functions based upon sketch by Brian Lough
 https://github.com/witnessmenow/ESP32-WiFi-Manager-Examples
*/
// last modified by Graham Payne 10:00 01/11/2022

#define ESP_DRD_USE_SPIFFS true

// Include Libraries
#include <Timer.h>                                              //interval timer library
#include <WebServer.h>                                          //webserver library
#include <NewPing.h>                                            //ultrasound library
#include <ESPmDNS.h>                                            //domain name server library
#include <WiFiUdp.h>                                            //update library
#include <WiFi.h>                                               // WiFi Library
#include <FS.h>                                                 // File System Library
#include <SPIFFS.h>                                             // SPI Flash Syetem Library
#include <WiFiManager.h>                                        // WiFiManager Library
#include <ArduinoJson.h>                                        // Arduino JSON library

#define JSON_CONFIG_FILE "/test_config.json"                    // JSON configuration file

#define PI 3.1415926535897932384626433832795     
#define PingPin       5                                         // GPIO5
#define EchoPin       4                                         // GPIO4
#define LED_PIN       13                                        // LED GPIO13, D7

bool shouldSaveConfig = false;                                  // Flag for saving data
unsigned int Period = 5000;                                     //period between pings, in milliseconds.
unsigned int WaterDepth, PingDepth, tickEvent;  
unsigned int testNumber1, testNumber2, Diameter, Depth;         //Variables to hold data from custom textboxes
float Area, Litres;

WebServer server(80);                                           //initiate server
WiFiManager wm;                                                 // Define WiFiManager Object
Timer t;                                                        //initiate timer
NewPing sonar(PingPin, EchoPin, 400);                           // sensor's trigger pin, echo pin, and max distance (cm) to ping.

//*****************************************************************************
void saveConfigFile(){                                          // Save Config in JSON format
 Serial.println(F("Saving configuration..."));
 StaticJsonDocument<512> json;                                 // Create a JSON document

 json["testNumber1"] = testNumber1;
 json["testNumber2"] = testNumber2;
 
 File configFile = SPIFFS.open(JSON_CONFIG_FILE, "w");         // Open config file
 if (!configFile){                                             // Error, file did not open
   Serial.println("failed to open config file for writing");
 }
 serializeJsonPretty(json, Serial);                            // Serialize JSON data to write to file
 if (serializeJson(json, configFile) == 0){
   Serial.println(F("Failed to write to file"));               // Error writing file
 }
 configFile.close();                                           // Close file
}

//*****************************************************************************
bool loadConfigFile(){                                          // Load existing configuration file
 // SPIFFS.format();                                           // Uncomment if we need to format filesystem
 Serial.println("Mounting File System...");                    // Read configuration from FS json
 if (SPIFFS.begin(false) || SPIFFS.begin(true)){               // May need to make it begin(true) first time you are using SPIFFS
   Serial.println("mounted file system");
   if (SPIFFS.exists(JSON_CONFIG_FILE)){                       // The file exists, reading and loading
     Serial.println("reading config file");
     File configFile = SPIFFS.open(JSON_CONFIG_FILE, "r");
     if (configFile){
       Serial.println("Opened configuration file");
       StaticJsonDocument<512> json;
       DeserializationError error = deserializeJson(json, configFile);
       serializeJsonPretty(json, Serial);
       if (!error){
         Serial.println("Parsing JSON");
         testNumber1 = json["testNumber1"].as<int>();
         testNumber2 = json["testNumber2"].as<int>();
         return true;
       } else {
         Serial.println("Failed to load json config");         // Error loading JSON data
       }
     }
   }
 } else {
   Serial.println("Failed to mount FS");                       // Error mounting file system
 }
 return false;
}

//*****************************************************************************
void saveConfigCallback(){                                      // Callback notifying us of the need to save configuration
 Serial.println("Should save config");
 shouldSaveConfig = true;
}

//*****************************************************************************
void configModeCallback(WiFiManager *myWiFiManager){            // Called when config mode launched
 Serial.println("Entered Configuration Mode");
 Serial.print("Config SSID: ");
 Serial.println(myWiFiManager->getConfigPortalSSID());
 Serial.print("Config IP Address: ");
 Serial.println(WiFi.softAPIP());
}

//*****************************************************************************
void ConfirmNumbers(){
 Serial.println();
 Serial.print("Diameter: ");
 Serial.println(Diameter);
 Serial.print("depth: ");
 Serial.println(Depth);
 Serial.print("area: ");
 Serial.println(Area);
}

//*****************************************************************************
void sendSensorReadings() {
 PingDepth = sonar.ping_cm();
 WaterDepth = Depth - PingDepth;                               //calculate the depth of the water
 Litres = (Area * WaterDepth) / 1000;                          //calculate the volume of the water in litres

 while (PingDepth > Depth) {      //while (Litres > Area*Depth) {                                 //error trap
   delay(60);
   PingDepth = sonar.ping_cm();
   WaterDepth = Depth - PingDepth;                             //calculate the depth of the water
   Litres = (Area * WaterDepth) / 1000;                        //calculate the volume of the water in litres (1 ltr = 1000 cm2)
 }

 server.send(200, "text/html", SendHTML(Litres));
 Serial.println(Litres);

 digitalWrite(LED_PIN, HIGH);                                  //flash the LED on D7, just to let us know it's running
 delay(50);
 digitalWrite(LED_PIN, LOW);
}

//*****************************************************************************
void setup(){                    
 Serial.begin(115200);                                         // Setup Serial monitor
 delay(10);
 
 bool forceConfig = true;                                     // Change to true when testing to force configuration every time we run **********************
 bool spiffsSetup = loadConfigFile();
 if (!spiffsSetup){
   Serial.println(F("Forcing config mode as there is no saved config"));
   forceConfig = true;
 }
 
 WiFi.mode(WIFI_STA);                                          // Explicitly set WiFi mode
 
 wm.resetSettings();                                           // Reset settings (only for development)
 
 wm.setSaveConfigCallback(saveConfigCallback);                 // Set config save notify callback
 
 wm.setAPCallback(configModeCallback);                         // Set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode

 // Custom elements
 char convertedValue1[6];                                      // Need to convert numerical input to string to display the default value.
 sprintf(convertedValue1, "%d", testNumber1); 

 char convertedValue2[6];
 sprintf(convertedValue2, "%d", testNumber2); 
  
 WiFiManagerParameter custom_text_box_num1("key_num1", "Tank Diameter in cm", convertedValue1, 7); // Text box (Number) - 7 characters maximum
 WiFiManagerParameter custom_text_box_num2("key_num2", "Sensor Height in cm", convertedValue2, 7); 
  
 //*wm.addParameter(&custom_text_box);                         // Add all defined parameters
 wm.addParameter(&custom_text_box_num1);
 wm.addParameter(&custom_text_box_num2);

 //set a static IP, my router stsrts at 100 so don't know if this number will work
 wm.setSTAStaticIPConfig(IPAddress(192,168,1,2), IPAddress(192,168,1,1), IPAddress(255,255,255,0)); // optional DNS 4th argument
 
 if (forceConfig){                                             // Run if we need a configuration
   if (!wm.startConfigPortal("TANK_AP", "12345678")){
     Serial.println("failed to connect and hit timeout");
     delay(3000);
     ESP.restart();                                            //reset and try again, or maybe put it to deep sleep
     delay(5000);
   }
 } else {
   if (!wm.autoConnect("TANK_AP", "12345678")){
     Serial.println("failed to connect and hit timeout");
     delay(3000);
     ESP.restart();                                            // if we still have not connected restart and try all over again
     delay(5000);
   }
 }

 Serial.println("");                                           // If we get here, we are connected to the WiFi
 Serial.println("WiFi connected");
 Serial.print("IP address: ");
 Serial.println(WiFi.localIP());
 
 testNumber1 = atoi(custom_text_box_num1.getValue());          //Convert the number value
 Serial.print("testNumber1: ");
 Serial.println(testNumber1);

 testNumber2 = atoi(custom_text_box_num2.getValue());          //Convert the number value
 Serial.print("testNumber2: ");
 Serial.println(testNumber2);
 
 if (shouldSaveConfig){                                        // Save the custom parameters to FS
   saveConfigFile();
 }

 pinMode(LED_PIN, OUTPUT);  //LED D7 GPIO13
 pinMode(PingPin, OUTPUT);
 pinMode(EchoPin, INPUT_PULLUP);

 Diameter = testNumber1;                                       //internal Radius of tank in cm
 Depth = testNumber2;                                          //total depth of tank in cm from sensor to base inside
 Area = PI * ((Diameter/2) * (Diameter/2));                    //area of base of tank 1
 
 ConfirmNumbers();                                             //for testing only ********************************************************
  
 server.on("/", handle_OnConnect);                             //start the server
 server.onNotFound(handle_NotFound);
 server.begin();
 Serial.println("HTTP server started");
 
 tickEvent = t.every(5000, sendSensorReadings);                //timer start
}

//*****************************************************************************
void loop(){
 t.update();
 server.handleClient();
}

//*****************************************************************************
void handle_OnConnect() {
 server.send(200, "text/html", SendHTML(Litres));
}

//*****************************************************************************
void handle_NotFound() {
 server.send(404, "text/plain", "Not found");
}

//*****************************************************************************
String SendHTML(float Litres) {
 String ptr = "<!DOCTYPE html> <html>\n";
 ptr += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
 ptr += "<title>ESP32 Water Level</title>\n";
 ptr += "<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
 ptr += "body{margin-top: 50px;} h1 {color: #444444;margin: 50px auto 30px;}\n";
 ptr += "p {font-size: 24px;color: #444444;margin-bottom: 10px;}\n";
 ptr += "</style>\n";
 ptr += "</head>\n";
 ptr += "<body>\n";
 ptr += "<div id=\"webpage\">\n";
 ptr += "<h1>Tank Water Level</h1>\n";
 ptr += "<h2>Tank Water Liters</h1>\n";
 ptr += "<h2>by Graham Payne</h1>\n";
 ptr += "<p>Litres: ";
 ptr += Litres;
 //ptr += "&deg;C</p>";
 // ptr += "<p>Humidity: ";
 // ptr += humidity;
 // ptr += "%</p>";
 // ptr += "<p>Pressure: ";
 // ptr += pressure;
 // ptr += "hPa</p>";
 // ptr += "<p>Altitude: ";
 // ptr += altitude;
 //ptr += "m</p>";
 ptr += "</div>\n";
 ptr += "</body>\n";
 ptr += "</html>\n";
 return ptr;
}
[/code]

there are several libraries for integrating a autogenerated fallback strategy that your device acts as an access-point to a factory default SSID non-crypted and a factory-default IP-adress like 192.168.1.2 or something similar.

Your customer connects to this WLAN and then types the IP-adress and then a website is displayed that scans the environment for WLAN SSID and your customer can choose on and then these WiFi-Credential were stored in the flash-memory of the ESP32 for future connection to the chosen WLAN.

here is such a library

best regards Stefan

1 Like

Thank you Stefan for your suggestion.
I am loath to rewrite all my code, I'm hoping for a simpler solution.
However, your suggestion is under consideration.

You could go for a fixed I/p and a method to change it - , if there is a conflict that’s a common route .

Have a look at “controlbyweb “ and their instructions relating to this on their devices for inspiration .

Write a procedure in startup , separate from your existing code , initiated by a link on a digital input

thanks for your reply hammy.
I am really hoping for a solution within wifimanager (tzapu), I'm sure it must be in there somewhere as it would be a common requisite.

I'm afraid I don't understand the problem. Doesn't your code below print the IP address obtained via DHCP after WiFiManager does its thing? What else are you trying to do?

Lock an IP address to the MAC address in whatever is providing DHCP (often the router), then you will know the IP address.

thanks for your reply gfvalvo.
As I mentioned not all routers have 192.186.1.1 as the gateway. I had a Xiaomi router that was 192.186.31.1 as the gateway.
So it's impossible to hard code a static IP as the gateway address may by different and unknown to me.

Thanks for your reply Perry.
OK, can you explain how to do this?

There's no one answer to this, it depends on what is providing DHCP. I expect that if you look at the configuration of whatever is providing DHCP ( probably the router on a small network, or possibly as part of a server) you should find a table that allows you to assign a particular IP address to a specific MAC address. You need to find the MAC address for your device and pair it with the desired IP address. Once you have done that when your device requests an IP it will always get the one you assigned.

How you do this depends on the device doing DHCP, you have to study the manual. I would expect that whoever looks after your customer's IT network to know how to do it for their network. They then know what IP address your customer will have to use.

this depends on the routers GUI that your WiFi-device shall connect to
Somewhere inside the WLAN-routers GUI there is an option
give device with mac-adress .... always the IP-adress .....

adding a display to your device that is showing the IP-adress

Adding sending an UDP-message to the UDP-broadcast-adress so that any device inside the same local network can receive this UDP-message . Writing a small python-script that can receive these UDP-messages and convert to a small stand-alone exe-file that can run on windows computers
recommend your customer a android / iOS-app that is able to receive UDP-messages to see the IP-adress.

As the internet and the data transmitted shall NOT be accessible to all and everybody some limitations are implemented and this will require some additional coding / rewriting code if you haven't thought about this aspect in the design-stage of your project

best regards Stefan

Hi,
me, in my projects with wifi. I use this feature.
It's not the best solution, it's simply a solution.
There is an app for Android and Apple (I think there may be others as well), which lists all devices on the network.
Use it on a mobile to know the network gateway and so using wifimanager,
configure the project with ESP.
The app is Fing.
" https://play.google.com/store/apps/details?id=com.overlook.android.fing&hl=pt_BR&gl=US "

I'm not sure why you directed this reply at me. I did not make a suggestion about a gateway. In fact, I didn't make any suggestion at all. I asked a question. Go back and read it HERE.

Yes it puts the "hard coded" info the AP screen. As I said I cannot hard code a static IP because I must also include the gateway which could be different. It's not by DHCP, that gives no info of the local IP obtained by the AP provisioning.

Thanks for your reply.
I am hoping to sell this product but I will not be in attendance while it is being installed.
The customer will most likely have no IT experience.
It has to be simple, WiFiManager does the provisioning well but leaves the customer without knowledge of the address to view the website from the ESP32.

I seem to have solved the problem.
entering this line in the code as I did to set a static IP:

wm.setSTAStaticIPConfig(IPAddress(192,168,1,3), IPAddress(192,168,1,1), IPAddress(255,255,255,0)); // optional DNS 4th argument

It doesn't seem to matter what address is in the second argument (Gateway), I tried several different address, it finds the router, assigns the local IP (first argument) and connects.
This info is displayed in the access point created by WiFiManager, so the customer can note the IP address and can then log onto the website running on the ESP32.
Thanks for all your help.

You can't set a static IP address on your device and expect it to work on some unspecified and unknown network. It might clash with an IP address assigned by the router, which causes weird problems if you don't know about it. It might not be in the same subnet, which probably won't work.

@gfvalvo has asked you a question twice, his question should lead to the answer to your problem.

Yes, right. That's what my query is all about.
Sure I can let the provisioning AP assign a DHCP address instead of static, but my query is - How to inform the customer of the IP address assigned while still in the AP.
Sure as gfvalvo says, I can see the address in the serial port of Arduino IDE, but the customer is not going to see that.
There must be a method or call in WiFiManager to display the Local IP.
I shall have to read the wiki a lot more.

Did you look at my suggestion ?

Controlbyweb send their devices out with a fixed IP. If you want to change it, you connect to it with a cable and your pc, (set to be on the same network ) and change it via setup page then reboot to accept the change Simples!

They sell lots of stuff so….

Here

@hammy

if using a network-cable with RJ45 plugs. Is it enough to just plug in the network-cable and the computer is able to configure the IP-adresses fully automatically ?

If the network-cable still requires to setup in the network-options the correct IP-adress
the WLAN solution offers to not needing sockets and network-cable.