diff --git a/src/AutoConnect.cpp b/src/AutoConnect.cpp index 7bebde1..9de0f0b 100644 --- a/src/AutoConnect.cpp +++ b/src/AutoConnect.cpp @@ -2,8 +2,8 @@ * AutoConnect class implementation. * @file AutoConnect.cpp * @author hieromon@gmail.com - * @version 1.0.6 - * @date 2019-09-17 + * @version 1.1.0 + * @date 2019-10-08 * @copyright MIT license. */ @@ -53,7 +53,7 @@ void AutoConnect::_initialize(void) { _menuTitle = _apConfig.title; _connectTimeout = AUTOCONNECT_TIMEOUT; _scanCount = 0; - memset(&_credential, 0x00, sizeof(struct station_config)); + memset(&_credential, 0x00, sizeof(station_config_t)); #ifdef ARDUINO_ARCH_ESP32 _disconnectEventId = -1; // The member available for ESP32 only #endif @@ -111,22 +111,14 @@ bool AutoConnect::begin(const char* ssid, const char* passphrase, unsigned long _ticker->start(AUTOCONNECT_FLICKER_PERIODDC, (uint8_t)AUTOCONNECT_FLICKER_WIDTHDC); } - // Advance configuration for STA mode. -#ifdef AC_DEBUG - String staip_s = _apConfig.staip.toString(); - String staGateway_s = _apConfig.staGateway.toString(); - String staNetmask_s = _apConfig.staNetmask.toString(); - String dns1_s = _apConfig.dns1.toString(); - String dns2_s = _apConfig.dns2.toString(); - AC_DBG("WiFi.config(IP=%s, Gateway=%s, Subnetmask=%s, DNS1=%s, DNS2=%s)\n", staip_s.c_str(), staGateway_s.c_str(), staNetmask_s.c_str(), dns1_s.c_str(), dns2_s.c_str()); -#endif - if (!WiFi.config(_apConfig.staip, _apConfig.staGateway, _apConfig.staNetmask, _apConfig.dns1, _apConfig.dns2)) { - AC_DBG("failed\n"); - return false; + // Advance configuration for STA mode. Restore previous configuration of STA. + station_config_t current; + if (_getConfigSTA(¤t)) { + AC_DBG("Current:%.32s\n", current.ssid); + _loadAvailCredential(reinterpret_cast(current.ssid)); } -#ifdef ARDUINO_ARCH_ESP8266 - AC_DBG("DHCP client(%s)\n", wifi_station_dhcpc_status() == DHCP_STOPPED ? "STOPPED" : "STARTED"); -#endif + if (!_configSTA(_apConfig.staip, _apConfig.staGateway, _apConfig.staNetmask, _apConfig.dns1, _apConfig.dns2)) + return false; // If the portal is requested promptly skip the first WiFi.begin and // immediately start the portal. @@ -150,16 +142,17 @@ bool AutoConnect::begin(const char* ssid, const char* passphrase, unsigned long // Reconnect with a valid credential as the autoReconnect option is enabled. if (!cs && _apConfig.autoReconnect && (ssid == nullptr && passphrase == nullptr)) { // Load a valid credential. - if (_loadAvailCredential()) { + if (_loadAvailCredential(nullptr)) { // Try to reconnect with a stored credential. - char ssid_c[sizeof(station_config::ssid) + 1]; - char password_c[sizeof(station_config::password) + 1]; + char ssid_c[sizeof(station_config_t::ssid) + sizeof('\0')]; + char password_c[sizeof(station_config_t::password) + sizeof('\0')]; *ssid_c = '\0'; - strncat(ssid_c, reinterpret_cast(_credential.ssid), sizeof(ssid_c) - 1); + strncat(ssid_c, reinterpret_cast(_credential.ssid), sizeof(ssid_c) - sizeof('\0')); *password_c = '\0'; - strncat(password_c, reinterpret_cast(_credential.password), sizeof(password_c) - 1); + strncat(password_c, reinterpret_cast(_credential.password), sizeof(password_c) - sizeof('\0')); AC_DBG("autoReconnect loaded SSID:%s\n", ssid_c); const char* psk = strlen(password_c) ? password_c : nullptr; + _configSTA(IPAddress(_credential.config.sta.ip), IPAddress(_credential.config.sta.gateway), IPAddress(_credential.config.sta.netmask), IPAddress(_credential.config.sta.dns1), IPAddress(_credential.config.sta.dns2)); WiFi.begin(ssid_c, psk); AC_DBG("WiFi.begin(%s%s%s)\n", ssid_c, psk == nullptr ? "" : ",", psk == nullptr ? "" : psk); cs = _waitForConnect(_connectTimeout) == WL_CONNECTED; @@ -191,7 +184,7 @@ bool AutoConnect::begin(const char* ssid, const char* passphrase, unsigned long // Connection unsuccessful, launch the captive portal. #if defined(ARDUINO_ARCH_ESP8266) - if (!(_apConfig.apip == IPAddress(0, 0, 0, 0) || _apConfig.gateway == IPAddress(0, 0, 0, 0) || _apConfig.netmask == IPAddress(0, 0, 0, 0))) { + if (!_apConfig.apip && !_apConfig.gateway && !_apConfig.netmask) { _config(); } #endif @@ -199,13 +192,13 @@ bool AutoConnect::begin(const char* ssid, const char* passphrase, unsigned long do { delay(100); yield(); - } while (WiFi.softAPIP() == IPAddress(0, 0, 0, 0)); + } while (!WiFi.softAPIP()); #if defined(ARDUINO_ARCH_ESP32) - if (!(_apConfig.apip == IPAddress(0, 0, 0, 0) || _apConfig.gateway == IPAddress(0, 0, 0, 0) || _apConfig.netmask == IPAddress(0, 0, 0, 0))) { + if (!(static_cast(_apConfig.apip) == 0U || static_cast(_apConfig.gateway) == 0U || static_cast(_apConfig.netmask) == 0U)) { _config(); } #endif - if (_apConfig.apip != IPAddress(0, 0, 0, 0)) { + if (_apConfig.apip) { do { delay(100); yield(); @@ -312,6 +305,63 @@ bool AutoConnect::_config(void) { return rc; } +/** + * Advance configuration for STA mode. + * @param ip IP address + * @param gateway Gateway address + * @param netmask Netmask + * @param dns1 Primary DNS address + * @param dns2 Secondary DNS address + * @return true Station successfully configured + * @return false WiFi.config failed + */ +bool AutoConnect::_configSTA(const IPAddress& ip, const IPAddress& gateway, const IPAddress& netmask, const IPAddress& dns1, const IPAddress& dns2) { + bool rc; + + AC_DBG("WiFi.config(IP=%s, Gateway=%s, Subnetmask=%s, DNS1=%s, DNS2=%s)\n", ip.toString().c_str(), gateway.toString().c_str(), netmask.toString().c_str(), dns1.toString().c_str(), dns2.toString().c_str()); + if (!(rc = WiFi.config(ip, gateway, netmask, dns1, dns2))) + AC_DBG("failed\n"); +#ifdef ARDUINO_ARCH_ESP8266 + AC_DBG("DHCP client(%s)\n", wifi_station_dhcpc_status() == DHCP_STOPPED ? "STOPPED" : "STARTED"); +#endif + return rc; +} + +/** + * Obtains the currently established AP connection to determine if the + * station configuration needs to run before the first WiFi.begin. + * Get the SSID of the currently connected AP stored in the ESP module + * by using the SDK API natively. + * AutoConnect::begin retrieves the IP configuration from the stored + * credentials of AutoConnectCredential based on that SSID and executes + * WiFi.config before WiFi.begin. + * @param config Station configuration stored in the ESP module. + * @return true The config parameter has obtained configuration. + * @return false Station configuration does not exist. + */ +bool AutoConnect::_getConfigSTA(station_config_t* config) { + bool rc; + uint8_t* ssid; + uint8_t* bssid; + +#if defined(ARDUINO_ARCH_ESP8266) + struct station_config current; + ssid = current.ssid; + bssid = current.bssid; + rc = wifi_station_get_config(¤t); +#elif defined(ARDUINO_ARCH_ESP32) + wifi_config_t current; + ssid = current.sta.ssid; + bssid = current.sta.bssid; + rc = (esp_wifi_get_config(WIFI_IF_STA, ¤t) == ESP_OK); +#endif + if (rc) { + memcpy(config->ssid, ssid, sizeof(station_config_t::ssid)); + memcpy(config->bssid, bssid, sizeof(station_config_t::bssid)); + } + return rc; +} + /** * Put a user site's home URI. * The URI specified by home is linked from "HOME" in the AutoConnect @@ -469,10 +519,13 @@ void AutoConnect::handleRequest(void) { if (WiFi.status() == WL_CONNECTED) _disconnectWiFi(true); + // Leave current AP, reconfigure station + _configSTA(_apConfig.staip, _apConfig.staGateway, _apConfig.staNetmask, _apConfig.dns1, _apConfig.dns2); + // An attempt to establish a new AP. int32_t ch = _connectCh == 0 ? _apConfig.channel : _connectCh; - char ssid_c[sizeof(station_config::ssid) + 1]; - char password_c[sizeof(station_config::password) + 1]; + char ssid_c[sizeof(station_config_t::ssid) + 1]; + char password_c[sizeof(station_config_t::password) + 1]; *ssid_c = '\0'; strncat(ssid_c, reinterpret_cast(_credential.ssid), sizeof(ssid_c) - 1); *password_c = '\0'; @@ -481,7 +534,7 @@ void AutoConnect::handleRequest(void) { WiFi.begin(ssid_c, password_c, ch); if (_waitForConnect(_connectTimeout) == WL_CONNECTED) { if (WiFi.BSSID() != NULL) { - memcpy(_credential.bssid, WiFi.BSSID(), sizeof(station_config::bssid)); + memcpy(_credential.bssid, WiFi.BSSID(), sizeof(station_config_t::bssid)); _currentHostIP = WiFi.localIP(); _redirectURI = String(F(AUTOCONNECT_URI_SUCCESS)); @@ -592,26 +645,40 @@ void AutoConnect::onNotFound(WebServerClass::THandlerFunction fn) { /** * Load stored credentials that match nearby WLANs. + * @param ssid SSID which should be loaded. If nullptr is assigned, search SSID with WiFi.scan. * @return true A matched credential of BSSID was loaded. */ -bool AutoConnect::_loadAvailCredential(void) { +bool AutoConnect::_loadAvailCredential(const char* ssid) { AutoConnectCredential credential(_apConfig.boundaryOffset); if (credential.entries() > 0) { // Scan the vicinity only when the saved credentials are existing. - WiFi.scanDelete(); - int8_t nn = WiFi.scanNetworks(false, true); - AC_DBG("%d network(s) found\n", (int)nn); - if (nn > 0) { - // Determine valid credentials by BSSID. - for (uint8_t i = 0; i < credential.entries(); i++) { - credential.load(i, &_credential); - for (uint8_t n = 0; n < nn; n++) { - if (!memcmp(_credential.bssid, WiFi.BSSID(n), sizeof(station_config::bssid))) - return true; + if (!ssid) { + WiFi.scanDelete(); + int8_t nn = WiFi.scanNetworks(false, true); + AC_DBG("%d network(s) found\n", (int)nn); + if (nn > 0) { + // Determine valid credentials by BSSID. + for (uint8_t i = 0; i < credential.entries(); i++) { + credential.load(i, &_credential); + for (uint8_t n = 0; n < nn; n++) { + if (!memcmp(_credential.bssid, WiFi.BSSID(n), sizeof(station_config_t::bssid))) + return true; + } } } } + else if (strlen(ssid)) + if (credential.load(ssid, &_credential) >= 0) { + if (_credential.dhcp == STA_STATIC) { + _apConfig.staip = static_cast(_credential.config.sta.ip); + _apConfig.staGateway = static_cast(_credential.config.sta.gateway); + _apConfig.staNetmask = static_cast(_credential.config.sta.netmask); + _apConfig.dns1 = static_cast(_credential.config.sta.dns1); + _apConfig.dns2 = static_cast(_credential.config.sta.dns2); + } + return true; + } } return false; } @@ -738,11 +805,14 @@ String AutoConnect::_induceConnect(PageArgument& args) { if (args.hasArg(String(F(AUTOCONNECT_PARAMID_CRED)))) { // Read from EEPROM AutoConnectCredential credential(_apConfig.boundaryOffset); - struct station_config entry; + station_config_t entry; credential.load(args.arg(String(F(AUTOCONNECT_PARAMID_CRED))).c_str(), &entry); strncpy(reinterpret_cast(_credential.ssid), reinterpret_cast(entry.ssid), sizeof(_credential.ssid)); strncpy(reinterpret_cast(_credential.password), reinterpret_cast(entry.password), sizeof(_credential.password)); - AC_DBG("Credential loaded:%.*s\n", sizeof(station_config::ssid), _credential.ssid); +#ifdef AC_DEBUG + IPAddress staip = IPAddress(_credential.config.sta.ip); + AC_DBG("Credential loaded:%.*s(%s)\n", sizeof(station_config_t::ssid), reinterpret_cast(_credential.ssid), _credential.dhcp == STA_DHCP ? "DHCP" : staip.toString().c_str()); +#endif } else { AC_DBG("Queried SSID:%s\n", args.arg(AUTOCONNECT_PARAMID_SSID).c_str()); @@ -755,12 +825,39 @@ String AutoConnect::_induceConnect(PageArgument& args) { _connectCh = 0; for (uint8_t nn = 0; nn < _scanCount; nn++) { String ssid = WiFi.SSID(nn); - if (!strncmp(ssid.c_str(), reinterpret_cast(_credential.ssid), sizeof(station_config::ssid))) { + if (!strncmp(ssid.c_str(), reinterpret_cast(_credential.ssid), sizeof(station_config_t::ssid))) { _connectCh = WiFi.channel(nn); break; } } + // Static IP detection + _credential.config.sta.ip = _credential.config.sta.gateway = _credential.config.sta.netmask = _credential.config.sta.dns1 = _credential.config.sta.dns2 = 0U; + if (!args.hasArg(String(F(AUTOCONNECT_PARAMID_DHCP)))) { + _credential.dhcp = STA_STATIC; + if (args.hasArg(String(F(AUTOCONNECT_PARAMID_STAIP)))) { + _apConfig.staip.fromString(args.arg(String(F(AUTOCONNECT_PARAMID_STAIP)))); + _credential.config.sta.ip = (uint32_t)_apConfig.staip; + } + if (args.hasArg(String(F(AUTOCONNECT_PARAMID_GTWAY)))) { + _apConfig.staGateway.fromString(args.arg(String(F(AUTOCONNECT_PARAMID_GTWAY)))); + _credential.config.sta.gateway = (uint32_t)_apConfig.staGateway; + } + if (args.hasArg(String(F(AUTOCONNECT_PARAMID_NTMSK)))) { + _apConfig.staNetmask.fromString(args.arg(String(F(AUTOCONNECT_PARAMID_NTMSK)))); + _credential.config.sta.netmask = (uint32_t)_apConfig.staNetmask; + } + if (args.hasArg(String(F(AUTOCONNECT_PARAMID_DNS1)))) { + _apConfig.dns1.fromString(args.arg(String(F(AUTOCONNECT_PARAMID_DNS1)))); + _credential.config.sta.dns1 = (uint32_t)_apConfig.dns1; + } + if (args.hasArg(String(F(AUTOCONNECT_PARAMID_DNS2)))) + _apConfig.dns2.fromString(args.arg(String(F(AUTOCONNECT_PARAMID_DNS2)))); + _credential.config.sta.dns2 = (uint32_t)_apConfig.dns2; + } + else + _credential.dhcp = STA_DHCP; + // Turn on the trigger to start WiFi.begin(). _rfConnect = true; @@ -811,6 +908,7 @@ String AutoConnect::_invokeResult(PageArgument& args) { // This is the specification as before. redirect += _currentHostIP.toString(); #endif + AC_DBG("Redirect to %s\n", redirect.c_str()); redirect += _redirectURI; _webServer->sendHeader(String(F("Location")), redirect, true); _webServer->send(302, String(F("text/plain")), _emptyString); diff --git a/src/AutoConnect.h b/src/AutoConnect.h index 415d569..f1f385b 100644 --- a/src/AutoConnect.h +++ b/src/AutoConnect.h @@ -2,8 +2,8 @@ * Declaration of AutoConnect class and accompanying AutoConnectConfig class. * @file AutoConnect.h * @author hieromon@gmail.com - * @version 1.0.0 - * @date 2019-08-15 + * @version 1.1.0 + * @date 2019-10-07 * @copyright MIT license. */ @@ -229,10 +229,12 @@ class AutoConnect { } AC_STARECONNECT_t; void _initialize(void); bool _config(void); + bool _configSTA(const IPAddress& ip, const IPAddress& gateway, const IPAddress& netmask, const IPAddress& dns1, const IPAddress& dns2); + bool _getConfigSTA(station_config_t* config); void _startWebServer(void); void _startDNSServer(void); void _handleNotFound(void); - bool _loadAvailCredential(void); + bool _loadAvailCredential(const char* ssid); void _stopPortal(void); bool _classifyHandle(HTTPMethod mothod, String uri); void _handleUpload(const String& requestUri, const HTTPUpload& upload); @@ -289,8 +291,8 @@ class AutoConnect { std::unique_ptr _update; /** Saved configurations */ - AutoConnectConfig _apConfig; - struct station_config _credential; + AutoConnectConfig _apConfig; + station_config_t _credential; uint8_t _hiddenSSIDCount; int16_t _scanCount; uint8_t _connectCh; @@ -374,6 +376,11 @@ class AutoConnect { String _token_LIST_SSID(PageArgument& args); String _token_SSID_COUNT(PageArgument& args); String _token_HIDDEN_COUNT(PageArgument& args); + String _token_CONFIG_STAIP(PageArgument& args); + String _token_CONFIG_STAGATEWAY(PageArgument& args); + String _token_CONFIG_STANETMASK(PageArgument& args); + String _token_CONFIG_STADNS1(PageArgument& args); + String _token_CONFIG_STADNS2(PageArgument& args); String _token_OPEN_SSID(PageArgument& args); String _token_UPTIME(PageArgument& args); String _token_BOOTURI(PageArgument& args); diff --git a/src/AutoConnectCredential.cpp b/src/AutoConnectCredential.cpp index c4de8d2..116a6bd 100644 --- a/src/AutoConnectCredential.cpp +++ b/src/AutoConnectCredential.cpp @@ -2,8 +2,8 @@ * AutoConnectCredential class dispatcher. * @file AutoConnectCredential.cpp * @author hieromon@gmail.com - * @version 1.0.2 - * @date 2019-09-16 + * @version 1.1.0 + * @date 2019-10-07 * @copyright MIT license. */ @@ -16,16 +16,23 @@ * AutoConnectCredential constructor takes the available count of saved * entries. * A stored credential data structure in EEPROM. - * 0 7 8 9a b (t) - * +--------+-+--+-----------------+-----------------+--+ - * |AC_CREDT|e|ss|ssid\0pass\0bssid|ssid\0pass\0bssid|\0| - * +--------+-+--+-----------------+-----------------+--+ + * 0 7 8 9a b (u) (u+16) (t) + * +--------+-+--+-----------------+-+--+--+--+----+----+-----------------+--+ + * |AC_CREDT|e|ss|ssid\0pass\0bssid|d|ip|gw|nm|dns1|dns2|ssid\0pass\0bssid|\0| + * +--------+-+--+-----------------+-+--+--+--+----+----+-----------------+--+ * AC_CREDT : Identifier. 8 characters. * e : Number of contained entries(uint8_t). * ss : Container size, excluding ID and number of entries(uint16_t). * ssid: SSID string with null termination. * password : Password string with null termination. * bssid : BSSID 6 bytes. + * d : DHCP is in available. 0:DCHP 1:Static IP + * ip - dns2 : Optional fields for static IPs configuration, these fields are available when d=1. + * ip : Static IP (uint32_t) + * gw : Gateway address (uint32_t) + * nm : Netmask (uint32_t) + * dns1 : Primary DNS (uint32) + * dns2 : Secondary DNS (uint32_t) * t : The end of the container is a continuous '\0'. * The AC_CREDT identifier is at the beginning of the area. * SSID and PASSWORD are terminated by '\ 0'. @@ -81,7 +88,7 @@ AutoConnectCredential::~AutoConnectCredential() { * false Could not deleted. */ bool AutoConnectCredential::del(const char* ssid) { - struct station_config entry; + station_config_t entry; bool rc = false; if (load(ssid, &entry) >= 0) { @@ -100,9 +107,15 @@ bool AutoConnectCredential::del(const char* ssid) { // Erase BSSID _eeprom->write(_dp++, 0xff); - for (uint8_t i = 0; i < sizeof(station_config::bssid); i++) + for (uint8_t i = 0; i < sizeof(station_config_t::bssid); i++) _eeprom->write(_dp++, 0xff); + // Erase ip configuration extention + if (_eeprom->read(_dp) == STA_STATIC) { + for (uint8_t i = 0; i < sizeof(station_config_t::_config); i++) + _eeprom->write(_dp++, 0xff); + } + // End 0xff writing, update headers. _entries--; _eeprom->write(_offset + static_cast(sizeof(AC_IDENTIFIER)) - 1, _entries); @@ -124,15 +137,15 @@ bool AutoConnectCredential::del(const char* ssid) { * @retval The entry number of the SSID in EEPROM. If the number less than 0, * the specified SSID was not found. */ -int8_t AutoConnectCredential::load(const char* ssid, struct station_config* config) { +int8_t AutoConnectCredential::load(const char* ssid, station_config_t* config) { int8_t entry = -1; _dp = AC_HEADERSIZE; if (_entries) { _eeprom->begin(AC_HEADERSIZE + _containSize); for (uint8_t i = 0; i < _entries; i++) { - _retrieveEntry(reinterpret_cast(config->ssid), reinterpret_cast(config->password), config->bssid); - if (!strcmp(ssid, (const char*)config->ssid)) { + _retrieveEntry(config); + if (!strcmp(ssid, reinterpret_cast(config->ssid))) { entry = i; break; } @@ -151,12 +164,12 @@ int8_t AutoConnectCredential::load(const char* ssid, struct station_config* conf * @retval true The entry number of the SSID in EEPROM. * false The number is not available. */ -bool AutoConnectCredential::load(int8_t entry, struct station_config* config) { +bool AutoConnectCredential::load(int8_t entry, station_config_t* config) { _dp = AC_HEADERSIZE; if (_entries && entry < _entries) { _eeprom->begin(AC_HEADERSIZE + _containSize); while (entry-- >= 0) - _retrieveEntry(reinterpret_cast(config->ssid), reinterpret_cast(config->password), config->bssid); + _retrieveEntry(config); _eeprom->end(); return true; } @@ -174,18 +187,18 @@ bool AutoConnectCredential::load(int8_t entry, struct station_config* config) { * @retval true Successfully saved. * @retval false EEPROM commit failed. */ -bool AutoConnectCredential::save(const struct station_config* config) { +bool AutoConnectCredential::save(const station_config_t* config) { static const char _id[] = AC_IDENTIFIER; - struct station_config stage; + station_config_t stage; int8_t entry; bool rep = false; bool rc; // Detect same entry for replacement. - entry = load((const char*)(config->ssid), &stage); + entry = load(reinterpret_cast(config->ssid), &stage); // Saving start. - _eeprom->begin(AC_HEADERSIZE + _containSize + sizeof(struct station_config)); + _eeprom->begin(AC_HEADERSIZE + _containSize + sizeof(station_config_t)); // Determine insertion or replacement. if (entry >= 0) { @@ -196,9 +209,15 @@ bool AutoConnectCredential::save(const struct station_config* config) { dm--; _eeprom->write(_dp, 0xff); // Clear SSID, Passphrase } - for (uint8_t i = 0; i < sizeof(station_config::bssid); i++) { + for (uint8_t i = 0; i < sizeof(station_config_t::bssid); i++) { _eeprom->write(_dp++, 0xff); // Clear BSSID } + uint8_t ss = _eeprom->read(_dp); // Read dhcp assignment flag + _eeprom->write(_dp++, 0xff); // Clear dhcp + if (ss == (uint8_t)STA_STATIC) { + for (uint8_t i = 0 ; i < sizeof(station_config_t::_config); i++) + _eeprom->write(_dp++, 0xff); // Clear static IPs + } } else { // Same entry not found. increase the entry. @@ -213,7 +232,11 @@ bool AutoConnectCredential::save(const struct station_config* config) { delay(10); // Seek insertion point, evaluate capacity to insert the new entry. - uint16_t eSize = strlen((const char*)config->ssid) + strlen((const char*)config->password) + sizeof(station_config::bssid) + 2; + uint16_t eSize = strlen(reinterpret_cast(config->ssid)) + strlen(reinterpret_cast(config->password)) + sizeof(station_config_t::bssid) + sizeof(station_config_t::dhcp); + if (config->dhcp == (uint8_t)STA_STATIC) + eSize += sizeof(station_config_t::_config); + eSize += sizeof('\0') + sizeof('\0'); + for (_dp = AC_HEADERSIZE; _dp < _containSize + AC_HEADERSIZE; _dp++) { uint8_t c = _eeprom->read(_dp); if (c == 0xff) { @@ -241,9 +264,17 @@ bool AutoConnectCredential::save(const struct station_config* config) { c = *dt++; _eeprom->write(_dp++, c); } while (c != '\0'); - for (uint8_t i = 0; i < sizeof(station_config::bssid); i++) { + for (uint8_t i = 0; i < sizeof(station_config_t::bssid); i++) _eeprom->write(_dp++, config->bssid[i]); // write BSSID + _eeprom->write(_dp++, config->dhcp); // write dhcp flag + if (config->dhcp == (uint8_t)STA_STATIC) { + for (uint8_t e = 0; e < sizeof(station_config_t::_config::addr) / sizeof(uint32_t); e++) { + uint32_t ip = config->config.addr[e]; + for (uint8_t b = 1; b <= sizeof(ip); b++) + _eeprom->write(_dp++, ((uint8_t*)&ip)[sizeof(ip) - b]); + } } + // Terminate container, mark to the end of credential area. // When the entry is replaced, not mark a terminator. if (!rep) { @@ -268,27 +299,41 @@ bool AutoConnectCredential::save(const struct station_config* config) { * @param ssid A SSID storing address. * @param password A password storing address. */ -void AutoConnectCredential::_retrieveEntry(char* ssid, char* password, uint8_t* bssid) { +void AutoConnectCredential::_retrieveEntry(station_config_t* config) { uint8_t ec; // Skip unavailable entry. while ((ec = _eeprom->read(_dp++)) == 0xff) {} + _ep = _dp - 1; // Retrieve SSID - _ep = _dp - 1; - *ssid++ = ec; + uint8_t* bp = config->ssid; + *bp++ = ec; do { ec = _eeprom->read(_dp++); - *ssid++ = ec; + *bp++ = ec; } while (ec != '\0'); // Retrieve Password + bp = config->password; do { ec = _eeprom->read(_dp++); - *password++ = ec; + *bp++ = ec; } while (ec != '\0'); // Retrieve BSSID - for (uint8_t i = 0; i < sizeof(station_config::bssid); i++) - bssid[i] = _eeprom->read(_dp++); + for (uint8_t i = 0; i < sizeof(station_config_t::bssid); i++) + config->bssid[i] = _eeprom->read(_dp++); + // Extended readout for static IP + config->dhcp = _eeprom->read(_dp++); + if (config->dhcp == (uint8_t)STA_STATIC) { + for (uint8_t e = 0; e < sizeof(station_config_t::_config::addr) / sizeof(uint32_t); e++) { + uint32_t* ip = &config->config.addr[e]; + *ip = 0; + for (uint8_t b = 0; b < sizeof(uint32_t); b++) { + *ip <<= 8; + *ip += _eeprom->read(_dp++); + } + } + } } #else @@ -299,19 +344,24 @@ void AutoConnectCredential::_retrieveEntry(char* ssid, char* password, uint8_t* * The credential area in the flash used by AutoConnect was moved from * EEPROM to NVS with v.1.0.0. A stored credential data structure of * Preferences is as follows. It has no identifier as AC_CREDT. - * 0 12 3 (t) - * +-+--+-----------------+-----------------+--+ - * |e|ss|ssid\0pass\0bssid|ssid\0pass\0bssid|\0| - * +-+--+-----------------+-----------------+--+ + * 0 12 3 (u) (u+16) (t) + * +-+--+-----------------+-+--+--+--+----+----+-----------------+--+ + * |e|ss|ssid\0pass\0bssid|d|ip|gw|nm|dns1|dns2|ssid\0pass\0bssid|\0| + * +-+--+-----------------+-+--+--+--+----+----+-----------------+--+ * e : Number of contained entries(uint8_t). * ss : Container size, excluding ID and number of entries(uint16_t). * ssid: SSID string with null termination. * password : Password string with null termination. * bssid : BSSID 6 bytes. + * d : DHCP is in available. 0:DCHP 1:Static IP + * ip - dns2 : Optional fields for static IPs configuration, these fields are available when d=1. + * ip : Static IP (uint32_t) + * gw : Gateway address (uint32_t) + * nm : Netmask (uint32_t) + * dns1 : Primary DNS (uint32) + * dns2 : Secondary DNS (uint32_t) * t : The end of the container is a continuous '\0'. - * The AC_CREDT identifier is at the beginning of the area. * SSID and PASSWORD are terminated by '\ 0'. - * Free area are filled with FF, which is reused as an area for insertion. */ AutoConnectCredential::AutoConnectCredential() { _allocateEntry(); @@ -362,7 +412,7 @@ inline uint8_t AutoConnectCredential::entries(void) { * @retval The entry number of the SSID. If the number less than 0, * the specified SSID was not found. */ -int8_t AutoConnectCredential::load(const char* ssid, struct station_config* config) { +int8_t AutoConnectCredential::load(const char* ssid, station_config_t* config) { // Determine the number in entries int8_t en = 0; _entries = _import(); // Reload the saved credentials @@ -386,7 +436,7 @@ int8_t AutoConnectCredential::load(const char* ssid, struct station_config* conf * @retval true The entry number of the SSID. * false The number is not available. */ -bool AutoConnectCredential::load(int8_t entry, struct station_config* config) { +bool AutoConnectCredential::load(int8_t entry, station_config_t* config) { _entries = _import(); for (decltype(_credit)::iterator it = _credit.begin(), e = _credit.end(); it != e; ++it) { if (!entry--) { @@ -406,7 +456,7 @@ bool AutoConnectCredential::load(int8_t entry, struct station_config* config) { * @retval true Successfully saved. * @retval false Preferences commit failed. */ -bool AutoConnectCredential::save(const struct station_config* config) { +bool AutoConnectCredential::save(const station_config_t* config) { if (_add(config)) { return _commit() > 0 ? true : false; } @@ -432,6 +482,9 @@ bool AutoConnectCredential::_add(const station_config_t* config) { AC_CREDTBODY_t credtBody; credtBody.password = String(reinterpret_cast(config->password)); memcpy(credtBody.bssid, config->bssid, sizeof(AC_CREDTBODY_t::bssid)); + credtBody.dhcp = config->dhcp; + for (uint8_t e = 0; e < sizeof(AC_CREDTBODY_t::ip) / sizeof(uint32_t); e++) + credtBody.ip[e] = credtBody.dhcp == (uint8_t)STA_STATIC ? config->config.addr[e] : 0U; std::pair rc = _credit.insert(std::make_pair(ssid, credtBody)); _entries = _credit.size(); #ifdef AC_DBG @@ -455,7 +508,11 @@ size_t AutoConnectCredential::_commit(void) { for (const auto& credt : _credit) { ssid = credt.first; credtBody = credt.second; - sz += ssid.length() + sizeof('\0') + credtBody.password.length() + sizeof('\0') + sizeof(AC_CREDTBODY_t::bssid); + sz += ssid.length() + sizeof('\0') + credtBody.password.length() + sizeof('\0') + sizeof(AC_CREDTBODY_t::bssid) + sizeof(AC_CREDTBODY_t::dhcp); + if (credtBody.dhcp == (uint32_t)STA_STATIC) { + for (uint8_t e = 0; e < sizeof(AC_CREDTBODY_t::ip) / sizeof(uint32_t); e++) + sz += sizeof(uint32_t); + } } // When the entry is not empty, the size of container terminator as '\0' must be added. _containSize = sz + (_entries ? sizeof('\0') : 0); @@ -484,6 +541,16 @@ size_t AutoConnectCredential::_commit(void) { dp += itemLen; memcpy(&credtPool[dp], credtBody.bssid, sizeof(station_config_t::bssid)); dp += sizeof(station_config_t::bssid); + // DHCP/Static IP indicator + credtPool[dp++] = (uint8_t)credtBody.dhcp; + // Static IP configuration + if (credtBody.dhcp == STA_STATIC) { + for (uint8_t e = 0; e < sizeof(AC_CREDTBODY_t::ip) / sizeof(uint32_t); e++) { + // uint32_t ip = credtBody.ip[e]; + for (uint8_t b = 1; b <= sizeof(credtBody.ip[e]); b++) + credtPool[dp++] = ((uint8_t*)&credtBody.ip[e])[sizeof(credtBody.ip[e]) - b]; + } + } } if (_credit.size() > 0) credtPool[dp] = '\0'; // Terminates a container @@ -552,9 +619,22 @@ uint8_t AutoConnectCredential::_import(void) { // BSSID dp += credtBody.password.length() + sizeof('\0'); memcpy(credtBody.bssid, &credtPool[dp], sizeof(AC_CREDTBODY_t::bssid)); + dp += sizeof(AC_CREDTBODY_t::bssid); + // DHCP/Static IP indicator + credtBody.dhcp = credtPool[dp++]; + // Static IP configuration + for (uint8_t e = 0; e < sizeof(AC_CREDTBODY_t::ip) / sizeof(uint32_t); e++) { + uint32_t* ip = &credtBody.ip[e]; + *ip = 0U; + if (credtBody.dhcp == (uint8_t)STA_STATIC) { + for (uint8_t b = 0; b < sizeof(uint32_t); b++) { + *ip <<= 8; + *ip += credtPool[dp++]; + } + } + } // Make an entry _credit.insert(std::make_pair(ssid, credtBody)); - dp += sizeof(AC_CREDTBODY_t::bssid); } free(credtPool); } @@ -585,6 +665,9 @@ void AutoConnectCredential::_obtain(AC_CREDT_t::iterator const& it, station_conf ssid.toCharArray(reinterpret_cast(config->ssid), sizeof(station_config_t::ssid)); credtBody.password.toCharArray(reinterpret_cast(config->password), sizeof(station_config_t::password)); memcpy(config->bssid, credtBody.bssid, sizeof(station_config_t::bssid)); + config->dhcp = credtBody.dhcp; + for (uint8_t e = 0; e < sizeof(AC_CREDTBODY_t::ip) / sizeof(uint32_t); e++) + config->config.addr[e] = credtBody.dhcp == (uint8_t)STA_STATIC ? credtBody.ip[e] : 0U; } #endif diff --git a/src/AutoConnectCredential.h b/src/AutoConnectCredential.h index ec6b641..de75003 100644 --- a/src/AutoConnectCredential.h +++ b/src/AutoConnectCredential.h @@ -2,8 +2,8 @@ * Declaration of AutoConnectCredential class. * @file AutoConnectCredential.h * @author hieromon@gmail.com - * @version 1.0.2 - * @date 2019-09-16 + * @version 1.1.0 + * @date 2019-10-07 * @copyright MIT license. */ @@ -32,13 +32,6 @@ extern "C" { #define AC_CREDENTIAL_PREFERENCES 0 #endif #include -struct station_config { -uint8_t ssid[32]; -uint8_t password[64]; -uint8_t bssid_set; -uint8_t bssid[6]; -wifi_fast_scan_threshold_t threshold; -}; #endif #include "AutoConnectDefs.h" @@ -59,15 +52,37 @@ wifi_fast_scan_threshold_t threshold; #define AC_IDENTIFIER "AC_CREDT" #endif +typedef enum { + STA_DHCP, + STA_STATIC +} station_config_dhcp; + +typedef struct { + uint8_t ssid[32]; + uint8_t password[64]; + uint8_t bssid[6]; + uint8_t dhcp; /**< 1:DHCP, 2:Static IP */ + union _config { + uint32_t addr[5]; + struct _sta { + uint32_t ip; + uint32_t gateway; + uint32_t netmask; + uint32_t dns1; + uint32_t dns2; + } sta; + } config; +} station_config_t; + class AutoConnectCredentialBase { public: explicit AutoConnectCredentialBase() : _entries(0), _containSize(0) {} virtual ~AutoConnectCredentialBase() {} virtual uint8_t entries(void) { return _entries; } virtual bool del(const char* ssid) = 0; - virtual int8_t load(const char* ssid, struct station_config* config) = 0; - virtual bool load(int8_t entry, struct station_config* config) = 0; - virtual bool save(const struct station_config* config) = 0; + virtual int8_t load(const char* ssid, station_config_t* config) = 0; + virtual bool load(int8_t entry, station_config_t* config) = 0; + virtual bool save(const station_config_t* config) = 0; protected: virtual void _allocateEntry(void) = 0; /**< Initialize storage for credentials. */ @@ -88,15 +103,15 @@ class AutoConnectCredential : public AutoConnectCredentialBase { explicit AutoConnectCredential(uint16_t offset); ~AutoConnectCredential(); bool del(const char* ssid) override; - int8_t load(const char* ssid, struct station_config* config) override; - bool load(int8_t entry, struct station_config* config) override; - bool save(const struct station_config* config) override; + int8_t load(const char* ssid, station_config_t* config) override; + bool load(int8_t entry, station_config_t* config) override; + bool save(const station_config_t* config) override; protected: void _allocateEntry(void) override; /**< Initialize storage for credentials. */ private: - void _retrieveEntry(char* ssid, char* password, uint8_t* bssid); /**< Read an available entry. */ + void _retrieveEntry(station_config_t* config); /**< Read an available entry. */ int _dp; /**< The current address in EEPROM */ int _ep; /**< The current entry address in EEPROM */ @@ -127,20 +142,22 @@ class AutoConnectCredential : public AutoConnectCredentialBase { ~AutoConnectCredential(); bool del(const char* ssid) override; uint8_t entries(void) override; - int8_t load(const char* ssid, struct station_config* config) override; - bool load(int8_t entry, struct station_config* config) override; - bool save(const struct station_config* config) override; + int8_t load(const char* ssid, station_config_t* config) override; + bool load(int8_t entry, station_config_t* config) override; + bool save(const station_config_t* config) override; protected: void _allocateEntry(void) override; /**< Initialize storage for credentials. */ private: typedef struct { - String password; - uint8_t bssid[6]; + String password; + uint8_t bssid[6]; + uint8_t dhcp; /**< 1:DHCP, 2:Static IP */ + uint32_t ip[5]; } AC_CREDTBODY_t; /**< Credential entry */ typedef std::map AC_CREDT_t; - typedef station_config station_config_t; + // typedef station_config station_config_t; bool _add(const station_config_t* config); /**< Add an entry */ size_t _commit(void); /**< Write back to the nvs */ diff --git a/src/AutoConnectPage.cpp b/src/AutoConnectPage.cpp index 269fc95..24b0257 100644 --- a/src/AutoConnectPage.cpp +++ b/src/AutoConnectPage.cpp @@ -2,8 +2,8 @@ * AutoConnect portal site web page implementation. * @file AutoConnectPage.h * @author hieromon@gmail.com - * @version 1.0.2 - * @date 2019-09-17 + * @version 1.1.0 + * @date 2019-10-07 * @copyright MIT license. */ @@ -38,12 +38,12 @@ const char AutoConnect::_CSS_BASE[] PROGMEM = { ".base-panel{" "margin:0 22px 0 22px" "}" - ".base-panel>*>label{" + ".base-panel * label{" "display:inline-block;" "width:3.0em;" "text-align:right" "}" - ".base-panel>*>label.slist{" + ".base-panel * .slist{" "width:auto;" "font-size:0.9em;" "margin-left:10px;" @@ -100,34 +100,33 @@ const char AutoConnect::_CSS_BASE[] PROGMEM = { /**< non-marked list for UL */ const char AutoConnect::_CSS_UL[] PROGMEM = { - "ul.noorder{" + ".noorder,.exp{" "padding:0;" "list-style:none;" "display:table" "}" - "ul.noorder li{" - "display:table-row" + ".noorder li,.exp{" + "display:table-row-group" "}" - "ul.noorder>*>label{" + ".noorder li label, .exp li *{" "display:table-cell;" "width:auto;" - "margin-right:10px;" "text-align:right;" "padding:10px 0.5em" "}" - "ul.noorder input[type=\"checkbox\"]{" + ".noorder input[type=\"checkbox\"]{" "-moz-appearance:checkbox;" "-webkit-appearance:checkbox" "}" - "ul.noorder input[type=\"radio\"]{" + ".noorder input[type=\"radio\"]{" "margin-right:0.5em;" "-moz-appearance:radio;" "-webkit-appearance:radio" "}" - "ul.noorder input[type=\"text\"]{" + ".noorder input[type=\"text\"]{" "width:auto" "}" - "ul.noorder input[type=\"text\"]:invalid{" + ".noorder input[type=\"text\"]:invalid{" "background:#fce4d6" "}" }; @@ -223,7 +222,8 @@ const char AutoConnect::_CSS_INPUT_TEXT[] PROGMEM = { "color:#D9434E" "}" ".aux-page label{" - "padding:10px 0.5em" + "display:inline;" + "padding:10px 0.5em;" "}" }; @@ -683,18 +683,44 @@ const char AutoConnect::_PAGE_CONFIGNEW[] PROGMEM = { "" "" "" - "
  • " + "
  • " + "" + "" + "
  • " + "
  • " + "" + "" + "
  • " + "
  • " + "" + "" + "
  • " + "
  • " + "" + "" + "
  • " + "
  • " + "" + "" + "
  • " + "
  • " + "" + "" + "
  • " + "
  • " "" "" "" "" - "" "" + "" "" }; @@ -1189,10 +1215,35 @@ String AutoConnect::_token_HIDDEN_COUNT(PageArgument& args) { return String(_hiddenSSIDCount); } +String AutoConnect::_token_CONFIG_STAIP(PageArgument& args) { + AC_UNUSED(args); + return !_apConfig.staip ? String(F("0.0.0.0")) : _apConfig.staip.toString(); +} + +String AutoConnect::_token_CONFIG_STAGATEWAY(PageArgument& args) { + AC_UNUSED(args); + return !_apConfig.staGateway ? String(F("0.0.0.0")) : _apConfig.staGateway.toString(); +} + +String AutoConnect::_token_CONFIG_STANETMASK(PageArgument& args) { + AC_UNUSED(args); + return !_apConfig.staNetmask ? String(F("0.0.0.0")) : _apConfig.staNetmask.toString(); +} + +String AutoConnect::_token_CONFIG_STADNS1(PageArgument& args) { + AC_UNUSED(args); + return !_apConfig.dns1 ? String(F("0.0.0.0")) : _apConfig.dns1.toString(); +} + +String AutoConnect::_token_CONFIG_STADNS2(PageArgument& args) { + AC_UNUSED(args); + return !_apConfig.dns2 ? String(F("0.0.0.0")) : _apConfig.dns2.toString(); +} + String AutoConnect::_token_OPEN_SSID(PageArgument& args) { AC_UNUSED(args); AutoConnectCredential credit(_apConfig.boundaryOffset); - struct station_config entry; + station_config_t entry; String ssidList; String rssiSym; @@ -1210,7 +1261,7 @@ String AutoConnect::_token_OPEN_SSID(PageArgument& args) { ssidList += String(F("(entry.ssid)) + String(F("\">")); for (int8_t sc = 0; sc < (int8_t)_scanCount; sc++) { - if (!memcmp(entry.bssid, WiFi.BSSID(sc), sizeof(station_config::bssid))) { + if (!memcmp(entry.bssid, WiFi.BSSID(sc), sizeof(station_config_t::bssid))) { _connectCh = WiFi.channel(sc); rssiSym = String(AutoConnect::_toWiFiQuality(WiFi.RSSI(sc))) + String(F("% Ch.")) + String(_connectCh) + String(F("")); if (WiFi.encryptionType(sc) != ENC_TYPE_NONE) @@ -1240,7 +1291,7 @@ String AutoConnect::_token_BOOTURI(PageArgument& args) { String AutoConnect::_token_CURRENT_SSID(PageArgument& args) { AC_UNUSED(args); - char ssid_c[sizeof(station_config::ssid) + 1]; + char ssid_c[sizeof(station_config_t::ssid) + 1]; *ssid_c = '\0'; strncat(ssid_c, reinterpret_cast(_credential.ssid), sizeof(ssid_c) - 1); String ssid = String(ssid_c); @@ -1307,6 +1358,11 @@ PageElement* AutoConnect::_setupPage(String uri) { elm->addToken(String(FPSTR("LIST_SSID")), std::bind(&AutoConnect::_token_LIST_SSID, this, std::placeholders::_1)); elm->addToken(String(FPSTR("SSID_COUNT")), std::bind(&AutoConnect::_token_SSID_COUNT, this, std::placeholders::_1)); elm->addToken(String(FPSTR("HIDDEN_COUNT")), std::bind(&AutoConnect::_token_HIDDEN_COUNT, this, std::placeholders::_1)); + elm->addToken(String(FPSTR("CONFIG_IP")), std::bind(&AutoConnect::_token_CONFIG_STAIP, this, std::placeholders::_1)); + elm->addToken(String(FPSTR("CONFIG_GW")), std::bind(&AutoConnect::_token_CONFIG_STAGATEWAY, this, std::placeholders::_1)); + elm->addToken(String(FPSTR("CONFIG_NM")), std::bind(&AutoConnect::_token_CONFIG_STANETMASK, this, std::placeholders::_1)); + elm->addToken(String(FPSTR("CONFIG_DNS1")), std::bind(&AutoConnect::_token_CONFIG_STADNS1, this, std::placeholders::_1)); + elm->addToken(String(FPSTR("CONFIG_DNS2")), std::bind(&AutoConnect::_token_CONFIG_STADNS2, this, std::placeholders::_1)); } else if (uri == String(AUTOCONNECT_URI_CONNECT)) { diff --git a/src/AutoConnectPage.h b/src/AutoConnectPage.h index 3a53869..748b945 100644 --- a/src/AutoConnectPage.h +++ b/src/AutoConnectPage.h @@ -2,8 +2,8 @@ * AutoConnect portal site web page declaration. * @file AutoConnectPage.h * @author hieromon@gmail.com - * @version 1.0.0 - * @date 2019-08-15 + * @version 1.1.0 + * @date 2019-10-04 * @copyright MIT license. */ @@ -15,6 +15,12 @@ #define AUTOCONNECT_PARAMID_SSID "SSID" #define AUTOCONNECT_PARAMID_PASS "Passphrase" #define AUTOCONNECT_PARAMID_CRED "Credential" +#define AUTOCONNECT_PARAMID_DHCP "dhcp" +#define AUTOCONNECT_PARAMID_STAIP "staip" +#define AUTOCONNECT_PARAMID_GTWAY "gateway" +#define AUTOCONNECT_PARAMID_NTMSK "netmask" +#define AUTOCONNECT_PARAMID_DNS1 "dns1" +#define AUTOCONNECT_PARAMID_DNS2 "dns2" // AutoConnect menu hyper-link as image #define AUTOCONNECT_GLYPH_COG_16 "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAA" \