Support the Config New with static IP

pull/137/head
Hieromon Ikasamo 5 years ago
parent becd58ec63
commit 275cb2d398
  1. 186
      src/AutoConnect.cpp
  2. 17
      src/AutoConnect.h
  3. 159
      src/AutoConnectCredential.cpp
  4. 61
      src/AutoConnectCredential.h
  5. 100
      src/AutoConnectPage.cpp
  6. 10
      src/AutoConnectPage.h

@ -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(&current)) {
AC_DBG("Current:%.32s\n", current.ssid);
_loadAvailCredential(reinterpret_cast<const char*>(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<const char*>(_credential.ssid), sizeof(ssid_c) - 1);
strncat(ssid_c, reinterpret_cast<const char*>(_credential.ssid), sizeof(ssid_c) - sizeof('\0'));
*password_c = '\0';
strncat(password_c, reinterpret_cast<const char*>(_credential.password), sizeof(password_c) - 1);
strncat(password_c, reinterpret_cast<const char*>(_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<uint32_t>(_apConfig.apip) == 0U || static_cast<uint32_t>(_apConfig.gateway) == 0U || static_cast<uint32_t>(_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(&current);
#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, &current) == 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<const char*>(_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<IPAddress>(_credential.config.sta.ip);
_apConfig.staGateway = static_cast<IPAddress>(_credential.config.sta.gateway);
_apConfig.staNetmask = static_cast<IPAddress>(_credential.config.sta.netmask);
_apConfig.dns1 = static_cast<IPAddress>(_credential.config.sta.dns1);
_apConfig.dns2 = static_cast<IPAddress>(_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<char*>(_credential.ssid), reinterpret_cast<const char*>(entry.ssid), sizeof(_credential.ssid));
strncpy(reinterpret_cast<char*>(_credential.password), reinterpret_cast<const char*>(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<const char*>(_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<const char*>(_credential.ssid), sizeof(station_config::ssid))) {
if (!strncmp(ssid.c_str(), reinterpret_cast<const char*>(_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);

@ -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<AutoConnectUpdate> _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);

@ -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<int>(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<char*>(config->ssid), reinterpret_cast<char*>(config->password), config->bssid);
if (!strcmp(ssid, (const char*)config->ssid)) {
_retrieveEntry(config);
if (!strcmp(ssid, reinterpret_cast<const char*>(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<char*>(config->ssid), reinterpret_cast<char*>(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<const char*>(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<const char*>(config->ssid)) + strlen(reinterpret_cast<const char*>(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<const char*>(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<AC_CREDT_t::iterator, bool> 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<char*>(config->ssid), sizeof(station_config_t::ssid));
credtBody.password.toCharArray(reinterpret_cast<char*>(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

@ -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 <esp_wifi.h>
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<String, AC_CREDTBODY_t> 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 */

@ -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 = {
"<label for=\"passphrase\">Passphrase</label>"
"<input id=\"passphrase\" type=\"password\" name=\"" AUTOCONNECT_PARAMID_PASS "\" placeholder=\"Passphrase\">"
"</li>"
"<br><li><input type=\"submit\" value=\"Apply\"></li>"
"<li>"
"<label for=\"dhcp\">Enable DHCP</label>"
"<input id=\"dhcp\" type=\"checkbox\" name=\"dhcp\" value=\"en\" checked onclick=\"vsw(this.checked);\">"
"</li>"
"<li class=\"exp\" style=\"display:none\">"
"<label for=\"sip\">IP Address</label>"
"<input id=\"sip\" type=\"text\" name=\"staip\" value=\"{{CONFIG_IP}}\">"
"</li>"
"<li class=\"exp\" style=\"display:none\">"
"<label for=\"gw\">Gateway</label>"
"<input id=\"gw\" type=\"text\" name=\"gateway\" value=\"{{CONFIG_GW}}\">"
"</li>"
"<li class=\"exp\" style=\"display:none\">"
"<label for=\"nm\">Netmask</label>"
"<input id=\"nm\" type=\"text\" name=\"netmask\" value=\"{{CONFIG_NM}}\">"
"</li>"
"<li class=\"exp\" style=\"display:none\">"
"<label for=\"ns1\">DNS1</label>"
"<input id=\"ns1\" type=\"text\" name=\"dns1\" value=\"{{CONFIG_DNS1}}\">"
"</li>"
"<li class=\"exp\" style=\"display:none\">"
"<label for=\"ns2\">DNS2</label>"
"<input id=\"ns2\" type=\"text\" name=\"dns2\" value=\"{{CONFIG_DNS2}}\">"
"</li>"
"<li><input type=\"submit\" value=\"Apply\"></li>"
"</ul>"
"</form>"
"</div>"
"</div>"
"</body>"
"<script type=\"text/javascript\">"
"function onFocus(value){"
"document.getElementById('ssid').value=value;"
"document.getElementById('passphrase').focus();"
"function onFocus(e){"
"document.getElementById('ssid').value=e,document.getElementById('passphrase').focus()"
"}"
"function vsw(e){"
"var t;t=e?'none':'table-row';for(const e of document.getElementsByClassName('exp'))e.style.display=t;e||document.getElementById('sip').focus()"
"}"
"</script>"
"</body>"
"</html>"
};
@ -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("<input id=\"sb\" type=\"submit\" name=\"" AUTOCONNECT_PARAMID_CRED "\" value=\"")) + String(reinterpret_cast<char*>(entry.ssid)) + String(F("\"><label class=\"slist\">"));
rssiSym = String(F("N/A</label>"));
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("&#037;&ensp;Ch.")) + String(_connectCh) + String(F("</label>"));
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<char*>(_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)) {

@ -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" \

Loading…
Cancel
Save