/* ConfigIP.ino, Example for the AutoConnect library. Copyright (c) 2019, Hieromon Ikasamo https://github.com/Hieromon/AutoConnect This software is released under the MIT License. https://opensource.org/licenses/MIT This sketch implements an example of enabling a custom web page according to the state of an externally equipped switch. The custom web page configures and provides a static IP to the ESP module. This example needs to equip an external switch circuit that pulls up with a resistor (1K to 10K ohms) and then drops it to GND via a push switch to connect to any GPIO of the ESP module. An external switch circuit: 3.3V --+-- | +-+ | | 1K ~ 10K +-+ | +--> D2 (for ESP8266, ex: GPIO16 in case of ESP32) | | O --+ | O | --+-- GND */ #if defined(ARDUINO_ARCH_ESP8266) #include #include #define EXTERNAL_SWITCH_PIN 4 #elif defined(ARDUINO_ARCH_ESP32) #include #include #define EXTERNAL_SWITCH_PIN 16 #endif #include #include static const char AUX_CONFIGIP[] PROGMEM = R"( { "title": "Config IP", "uri": "/configip", "menu": true, "element": [ { "name": "caption", "type": "ACText", "value": "Module IP configuration", "style": "color:steelblue" }, { "name": "mac", "type": "ACText", "format": "MAC: %s" }, { "name": "staip", "type": "ACInput", "label": "IP", "pattern": "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", "global": true }, { "name": "gateway", "type": "ACInput", "label": "Gateway", "pattern": "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", "global": true }, { "name": "netmask", "type": "ACInput", "label": "Netmask", "pattern": "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", "global": true }, { "name": "dns1", "type": "ACInput", "label": "DNS", "pattern": "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$", "global": true }, { "name": "ok", "type": "ACSubmit", "value": "OK", "uri": "/restart" }, { "name": "cancel", "type": "ACSubmit", "value": "Cancel", "uri": "/_ac" } ] } )"; static const char AUX_RESTART[] PROGMEM = R"( { "title": "Config IP", "uri": "/restart", "menu": false, "element": [ { "name": "caption", "type": "ACText", "value": "Settings", "style": "font-family:Arial;font-weight:bold;text-align:center;margin-bottom:10px;color:steelblue" }, { "name": "staip", "type": "ACText", "format": "IP: %s", "global": true }, { "name": "gateway", "type": "ACText", "format": "Gateway: %s", "global": true }, { "name": "netmask", "type": "ACText", "format": "Netmask: %s", "global": true }, { "name": "dns1", "type": "ACText", "format": "DNS1: %s", "global": true }, { "name": "result", "type": "ACText", "posterior": "par" } ] } )"; AutoConnect portal; AutoConnectConfig config; AutoConnectAux auxIPConfig; AutoConnectAux auxRestart; // Pin assignment for an external configuration switch uint8_t ConfigPin = EXTERNAL_SWITCH_PIN; uint8_t ActiveLevel = LOW; // EEPROM saving structure typedef union { struct { uint32_t ip; uint32_t gateway; uint32_t netmask; uint32_t dns1; } ipconfig; uint8_t ipraw[sizeof(uint32_t) * 4]; } IPCONFIG; // Load IP configuration from EEPROM void loadConfig(IPCONFIG* ipconfig) { EEPROM.begin(sizeof(IPCONFIG)); int dp = 0; for (uint8_t i = 0; i < 4; i++) { for (uint8_t c = 0; c < sizeof(uint32_t); c++) ipconfig->ipraw[c + i * sizeof(uint32_t)] = EEPROM.read(dp++); } EEPROM.end(); // Unset value screening if (ipconfig->ipconfig.ip == 0xffffffffL) ipconfig->ipconfig.ip = 0U; if (ipconfig->ipconfig.gateway == 0xffffffffL) ipconfig->ipconfig.gateway = 0U; if (ipconfig->ipconfig.netmask == 0xffffffffL) ipconfig->ipconfig.netmask = 0U; if (ipconfig->ipconfig.dns1 == 0xffffffffL) ipconfig->ipconfig.dns1 = 0U; Serial.println("IP configuration loaded"); Serial.printf("Sta IP :0x%08lx\n", ipconfig->ipconfig.ip); Serial.printf("Gateway:0x%08lx\n", ipconfig->ipconfig.gateway); Serial.printf("Netmask:0x%08lx\n", ipconfig->ipconfig.netmask); Serial.printf("DNS1 :0x%08lx\n", ipconfig->ipconfig.dns1); } // Save current IP configuration to EEPROM void saveConfig(const IPCONFIG* ipconfig) { // EEPROM.begin will truncate the area to the size given by the argument. // The part overflowing from the specified size is filled with 0xff, // so if the argument value is too small, the credentials may be lost. EEPROM.begin(128); int dp = 0; for (uint8_t i = 0; i < 4; i++) { for (uint8_t d = 0; d < sizeof(uint32_t); d++) EEPROM.write(dp++, ipconfig->ipraw[d + i * sizeof(uint32_t)]); } EEPROM.end(); delay(100); } // Custom web page handler to set current configuration to the page String getConfig(AutoConnectAux& aux, PageArgument& args) { IPCONFIG ipconfig; loadConfig(&ipconfig); // Fetch MAC address String macAddress; uint8_t mac[6]; WiFi.macAddress(mac); for (uint8_t i = 0; i < 6; i++) { char buf[3]; sprintf(buf, "%02X", mac[i]); macAddress += buf; if (i < 5) macAddress += ':'; } aux["mac"].value = macAddress; // Fetch each IP address configuration from EEPROM IPAddress staip = IPAddress(ipconfig.ipconfig.ip); IPAddress gateway = IPAddress(ipconfig.ipconfig.gateway); IPAddress netmask = IPAddress(ipconfig.ipconfig.netmask); IPAddress dns1 = IPAddress(ipconfig.ipconfig.dns1); // Echo back the IP settings aux["staip"].value = staip.toString(); aux["gateway"].value = gateway.toString(); aux["netmask"].value = netmask.toString(); aux["dns1"].value = dns1.toString(); return String(); } // Convert IP address from AutoConnectInput string value void getIPAddress(String ipString, uint32_t* ip) { IPAddress ipAddress; if (ipString.length()) ipAddress.fromString(ipString); *ip = (uint32_t)ipAddress; } // Custom web page handler to save the configuration to AutoConnectConfig String setConfig(AutoConnectAux& aux, PageArgument& args) { IPCONFIG ipconfig; // Retrieve each IP address from AutoConnectInput field getIPAddress(aux["staip"].value, &ipconfig.ipconfig.ip); getIPAddress(aux["gateway"].value, &ipconfig.ipconfig.gateway); getIPAddress(aux["netmask"].value, &ipconfig.ipconfig.netmask); getIPAddress(aux["dns1"].value, &ipconfig.ipconfig.dns1); // Make a result message if (auxIPConfig.isValid()) { saveConfig(&ipconfig); aux["result"].value = "Reset by AutoConnect menu will restart with the above."; } else aux["result"].value = "Invalid IP address specified."; return String(); } // Sense the external switch to enter the configuraton mode bool senseSW(const uint8_t pin, const uint8_t activeLevel) { bool sw = digitalRead(pin) == activeLevel; if (sw) { // Cut-off the chattering noise unsigned long tm = millis(); while (digitalRead(pin) == activeLevel) { if (millis() - tm > 1000) break; delay(1); } } return sw; } void setup() { delay(1000); Serial.begin(115200); Serial.println(); // Shift the credentials storage to reserve saving IPCONFIG config.boundaryOffset = sizeof(IPCONFIG); // Load current IP configuration IPCONFIG ipconfig; loadConfig(&ipconfig); // Configure pre-loaded static IPs config.staip = IPAddress(ipconfig.ipconfig.ip); config.staGateway = IPAddress(ipconfig.ipconfig.gateway); config.staNetmask = IPAddress(ipconfig.ipconfig.netmask); config.dns1 = IPAddress(ipconfig.ipconfig.dns1); portal.config(config); // Sense the configuration button (external switch) pinMode(ConfigPin, INPUT); if (senseSW(ConfigPin, ActiveLevel)) { Serial.println("IP configuration enable"); auxIPConfig.load(AUX_CONFIGIP); auxIPConfig.on(getConfig); auxRestart.load(AUX_RESTART); auxRestart.on(setConfig); portal.join({ auxIPConfig, auxRestart }); } portal.begin(); } void loop() { // User sketch process is here. portal.handleClient(); }