Add WiFi configuration loader
This commit is contained in:
parent
d0e5f90e90
commit
31568d3f53
52
data/config/WiFiManager.json
Normal file
52
data/config/WiFiManager.json
Normal file
@ -0,0 +1,52 @@
|
||||
{
|
||||
"fallbackAp": "defaultAP",
|
||||
"WiFiAp": [
|
||||
{
|
||||
"name": "defaultAP",
|
||||
"ap": true,
|
||||
"mac": "",
|
||||
"ssid": "ESP Setup",
|
||||
"bssid": "",
|
||||
"psk": "tempPW",
|
||||
"hostname": "esp-template",
|
||||
"retries": 0,
|
||||
"retryIntervall": 0,
|
||||
"ipv4": {
|
||||
"gateway": "",
|
||||
"dns": "",
|
||||
"dnsFallback": "",
|
||||
"address": ""
|
||||
},
|
||||
"ipv6": {
|
||||
"gateway": "",
|
||||
"dns": "",
|
||||
"dnsFallback": "",
|
||||
"address": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"WiFiSta": [
|
||||
{
|
||||
"name": "john doe",
|
||||
"ap": "defaultAP",
|
||||
"ssid": "john doe",
|
||||
"bssid": "",
|
||||
"psk": "",
|
||||
"hostname": "",
|
||||
"retries": 0,
|
||||
"retryIntervall": 0,
|
||||
"ipv4": {
|
||||
"gateway": "",
|
||||
"dns": "",
|
||||
"dnsFallback": "",
|
||||
"address": ""
|
||||
},
|
||||
"ipv6": {
|
||||
"gateway": "",
|
||||
"dns": "",
|
||||
"dnsFallback": "",
|
||||
"address": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
252
lib/WiFiManager/src/WiFiManager.cpp
Normal file
252
lib/WiFiManager/src/WiFiManager.cpp
Normal file
@ -0,0 +1,252 @@
|
||||
#include "WiFiManager.h"
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <ArduinoLog.h>
|
||||
#include <DNSServer.h>
|
||||
#include <LittleFS.h>
|
||||
|
||||
#ifndef DNS_PORT
|
||||
#define DNS_PORT 53
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
#include <AsyncTCP.h>
|
||||
#include <WiFi.h>
|
||||
#include <WiFiMulti.h>
|
||||
WiFiMulti wifiMulti;
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFiMulti.h>
|
||||
ESP8266WiFiMulti wifiMulti;
|
||||
#endif
|
||||
|
||||
WiFiEventHandler disconnectedEventHandler;
|
||||
std::vector<JsonObject> *connection_queue;
|
||||
JsonDocument doc;
|
||||
const uint8 *bssid;
|
||||
|
||||
// keep track of current connection
|
||||
uint currentConfig;
|
||||
uint currentTry;
|
||||
bool inRecovery = false;
|
||||
|
||||
unsigned long tryStartMillis;
|
||||
unsigned long lastCheckMillis;
|
||||
|
||||
int sortByRSSI(const void *a, const void *b) {
|
||||
return (((bss_info *)b)->rssi - ((bss_info *)a)->rssi);
|
||||
}
|
||||
|
||||
bool matches_ssid(const std::vector<const bss_info *> &networks, JsonVariant config) {
|
||||
for (auto &&network : networks) {
|
||||
if (memcmp((char *)network->ssid, config["ssid"].as<String>().c_str(), network->ssid_len) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool matches_bssid(const std::vector<const bss_info *> &networks, JsonVariant config) {
|
||||
char bssid_char[sizeof(bss_info().bssid) * 3];
|
||||
for (auto &&network : networks) {
|
||||
bssid = network->bssid;
|
||||
snprintf_P(bssid_char, sizeof(bssid_char), "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
|
||||
|
||||
for (auto &&config_bssid : config["bssid"].as<JsonArray>()) {
|
||||
if (config_bssid == bssid_char) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void disconnectHandler(const WiFiEventStationModeDisconnected &event = {}) {
|
||||
if (inRecovery) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.noticeln("In disc-handler");
|
||||
JsonVariant config = connection_queue->at(currentConfig);
|
||||
if (currentTry < config["retries"]) {
|
||||
currentTry++;
|
||||
Log.noticeln("trying again to connect");
|
||||
} else {
|
||||
if (currentConfig + 1 == connection_queue->size()) {
|
||||
Log.noticeln("Station disconnected. No more configurations to try. Starting recovery sequence");
|
||||
inRecovery = true;
|
||||
WiFi.disconnect();
|
||||
WiFi.softAP("ESP reached recovery state", "tempPW12345");
|
||||
return;
|
||||
} else {
|
||||
currentTry = 0;
|
||||
currentConfig++;
|
||||
config = connection_queue->at(currentConfig);
|
||||
}
|
||||
}
|
||||
|
||||
Log.noticeln("Station disconnected. Connecting to %s", config["name"].as<String>().c_str());
|
||||
|
||||
String psk;
|
||||
const uint8_t *bssid = nullptr;
|
||||
|
||||
if (!config["psk"].isNull()) {
|
||||
psk = config["psk"].as<String>();
|
||||
}
|
||||
|
||||
if (!config["bssid"].isNull() && config["bssid"].size() > 0) {
|
||||
Log.noticeln("Found bssid %s", config["bssid"].as<String>().c_str());
|
||||
bssid = (const uint8_t *)config["bssid"].as<String>().c_str();
|
||||
}
|
||||
|
||||
Log.noticeln("Connecting with: %s %s %s", config["ssid"].as<String>().c_str(), psk, bssid);
|
||||
tryStartMillis = millis();
|
||||
if (!config["hostname"].isNull()) {
|
||||
Log.noticeln("Set hostname %s", config["hostname"].as<String>().c_str());
|
||||
WiFi.setHostname(config["hostname"].as<String>().c_str());
|
||||
}
|
||||
|
||||
WiFi.begin(config["ssid"].as<String>(), psk, 0);
|
||||
Log.noticeln("Set Wifi to ssid: '%s' psk: '%s' bssid: %s", WiFi.SSID().c_str(), WiFi.psk().c_str(), WiFi.BSSIDstr());
|
||||
}
|
||||
|
||||
void getScanResults(int count) {
|
||||
Log.noticeln("Scan done. Found %i radios", count);
|
||||
|
||||
char bssid_char[sizeof(bss_info().bssid) * 3];
|
||||
|
||||
std::vector<const bss_info *> networks;
|
||||
networks.resize(count);
|
||||
uint8 max_ssid_length = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
networks.at(i) = WiFi.getScanInfoByIndex(i);
|
||||
max_ssid_length = max(networks.at(i)->ssid_len, max_ssid_length);
|
||||
}
|
||||
|
||||
std::sort(networks.begin(), networks.end(), sortByRSSI);
|
||||
|
||||
char buffer[128];
|
||||
|
||||
for (auto &network : networks) {
|
||||
bssid = network->bssid;
|
||||
snprintf_P(bssid_char, sizeof(bssid_char), "%02X:%02X:%02X:%02X:%02X:%02X", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]);
|
||||
snprintf_P(buffer, sizeof(buffer), "SSID: %-*s, RSSI: %i, BSSID: %s, hidden: %i", max_ssid_length, network->ssid, network->rssi, bssid_char, network->is_hidden);
|
||||
Log.noticeln("%s", buffer);
|
||||
}
|
||||
|
||||
connection_queue = new std::vector<JsonObject>;
|
||||
connection_queue->reserve(doc["WiFiSta"].size()); // worst case: all configurations are valid
|
||||
|
||||
// TODO: add every found bssid for a given ssid to the queue (in case one bssid with same ssid has a different psk)
|
||||
for (JsonVariant config : doc["WiFiSta"].as<JsonArray>()) {
|
||||
Log.noticeln("bssid is Null: %d, BSSID is empty: %d", config["bssid"].isNull(), config["bssid"].as<String>().length());
|
||||
Log.noticeln("if (%t or (%t and (%t or %t) or %t))", config["alwaysTry"].as<bool>(), matches_ssid(networks, config), config["bssid"].isNull(), config["bssid"].as<String>().length() == 0, matches_bssid(networks, config));
|
||||
if (config["alwaysTry"].as<bool>() or (matches_ssid(networks, config) and ((config["bssid"].isNull() or config["bssid"].as<String>().length() == 0) or matches_bssid(networks, config)))) {
|
||||
connection_queue->push_back(config);
|
||||
Log.noticeln("Adding %s to queue", config["name"].as<String>().c_str());
|
||||
}
|
||||
}
|
||||
disconnectHandler(); // start connecting to configurations
|
||||
}
|
||||
|
||||
wl_status_t WiFiManager::tick(unsigned long update_interval = 100) {
|
||||
unsigned long currentMillis = millis();
|
||||
if (!(currentMillis - lastCheckMillis >= update_interval)) {
|
||||
return this->lastState;
|
||||
}
|
||||
|
||||
Log.noticeln("In tick");
|
||||
|
||||
lastCheckMillis = currentMillis;
|
||||
|
||||
this->lastState = WiFi.status();
|
||||
|
||||
if (this->lastState == WL_CONNECTED) {
|
||||
return this->lastState;
|
||||
}
|
||||
|
||||
if (connection_queue == nullptr || currentConfig >= connection_queue->size()) {
|
||||
Log.warningln("Tried to tick but config still loading");
|
||||
return WL_IDLE_STATUS;
|
||||
}
|
||||
|
||||
Log.noticeln("In tick with %d configs", connection_queue->size());
|
||||
|
||||
if (currentMillis - tryStartMillis >= connection_queue->at(currentConfig)["timeout"]) {
|
||||
Log.noticeln("Disconnect from WiFi (reached timeout)");
|
||||
WiFi.disconnect();
|
||||
}
|
||||
return WL_DISCONNECTED;
|
||||
}
|
||||
|
||||
WiFiManager::WiFiManager() {}
|
||||
WiFiManager::~WiFiManager() {}
|
||||
void WiFiManager::begin() {
|
||||
|
||||
// TODO: load connections
|
||||
}
|
||||
bool WiFiManager::loadFromJson(File file) {
|
||||
|
||||
// Deserialize the JSON document
|
||||
DeserializationError error = deserializeJson(doc, file);
|
||||
if (error)
|
||||
Serial.println(F("Failed to read file, using default configuration"));
|
||||
|
||||
String setupAP;
|
||||
|
||||
Log.noticeln("Found %i configurations, overflowed: %T ", doc["WiFiSta"].size(), doc.overflowed());
|
||||
|
||||
setupAP = doc["SetupAP"].as<String>();
|
||||
|
||||
for (JsonVariant i : doc["WiFiSta"].as<JsonArray>()) {
|
||||
Serial.println(i["name"].as<String>() + i["ssid"].as<String>() + i["psk"].as<String>());
|
||||
Log.noticeln("Name: %s", i["name"].as<String>().c_str());
|
||||
Log.noticeln("SSID: %s", i["ssid"].as<String>().c_str());
|
||||
Log.noticeln("PSK: %s", i["psk"].as<String>().c_str());
|
||||
}
|
||||
|
||||
disconnectedEventHandler = WiFi.onStationModeDisconnected(disconnectHandler);
|
||||
|
||||
WiFi.scanNetworksAsync(getScanResults, true);
|
||||
|
||||
loadConnections();
|
||||
|
||||
file.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WiFiManager::loadConnections() {
|
||||
WiFi.persistent(false);
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
|
||||
IPAddress local_ip, gateway, subnet;
|
||||
local_ip.fromString("10.0.0.1");
|
||||
gateway.fromString("10.0.0.1");
|
||||
subnet.fromString(F("255.255.255.0"));
|
||||
|
||||
uint8_t newMACAddress[WL_MAC_ADDR_LENGTH] = {0x32, 0xAE, 0xA4, 0x07, 0x0D, 0x66};
|
||||
|
||||
const bool ap = true;
|
||||
|
||||
if (ap) {
|
||||
|
||||
if (setApMac(newMACAddress))
|
||||
Log.infoln("set MAC for SOFTAP_IF to %s", WiFi.softAPmacAddress().c_str());
|
||||
else
|
||||
Log.warningln("Unable to set MAC for SOFTAP_IF. Using %s instead", WiFi.softAPmacAddress().c_str());
|
||||
WiFi.softAPConfig(local_ip, gateway, subnet);
|
||||
WiFi.softAP("ESP Setup", "tempPW123", 5, 0, 1);
|
||||
Serial.println(WiFi.softAPIP());
|
||||
Serial.println(WiFi.macAddress());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WiFiManager::setApMac(uint8_t mac[WL_MAC_ADDR_LENGTH]) {
|
||||
wifi_set_macaddr(SOFTAP_IF, &mac[0]);
|
||||
|
||||
char charMacAddress[(sizeof(*mac) * 3)];
|
||||
snprintf_P(charMacAddress, sizeof(charMacAddress), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
String actualMac = WiFi.softAPmacAddress();
|
||||
return strncmp(charMacAddress, actualMac.c_str(), sizeof(charMacAddress));
|
||||
}
|
43
lib/WiFiManager/src/WiFiManager.h
Normal file
43
lib/WiFiManager/src/WiFiManager.h
Normal file
@ -0,0 +1,43 @@
|
||||
#ifdef ESP32
|
||||
#include <AsyncTCP.h>
|
||||
#include <WiFi.h>
|
||||
#include <WiFiMulti.h>
|
||||
#elif defined(ESP8266)
|
||||
#include <ESP8266WiFiMulti.h>
|
||||
#endif
|
||||
|
||||
#ifndef WL_IPV6_LENGTH
|
||||
#define WL_IPV6_LENGTH 16
|
||||
#endif
|
||||
|
||||
struct WiFiConnection {
|
||||
char *ssid;
|
||||
char *bssid;
|
||||
char *psk;
|
||||
bool hidden;
|
||||
char *hostname;
|
||||
byte mac[WL_MAC_ADDR_LENGTH];
|
||||
byte ipv4[WL_IPV4_LENGTH];
|
||||
byte ipv6[WL_IPV6_LENGTH];
|
||||
};
|
||||
|
||||
class WiFiManager {
|
||||
private:
|
||||
unsigned int reboot_count;
|
||||
WiFiConnection *allConnections;
|
||||
bool loadConfig(File file);
|
||||
bool loadConnections();
|
||||
void handleUnsuccessfulConnect();
|
||||
bool connect();
|
||||
void disconnect();
|
||||
bool setApMac(uint8_t mac[WL_MAC_ADDR_LENGTH]);
|
||||
// void getScanResults(int count);
|
||||
|
||||
public:
|
||||
wl_status_t lastState;
|
||||
WiFiManager(/* args */);
|
||||
~WiFiManager();
|
||||
void begin();
|
||||
wl_status_t tick(unsigned long update_interval);
|
||||
bool loadFromJson(File file);
|
||||
};
|
@ -8,7 +8,26 @@
|
||||
; Please visit documentation for the other options and examples
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
default_envs = nodemcuv2
|
||||
|
||||
[common_env_data]
|
||||
build_flags = '-D BUILD_VERSION="0.0.1"' -D DEBUG_ESP_PORT=Serial -D NDEBUG
|
||||
extra_scripts =
|
||||
./extra_scripts/reset_board.py
|
||||
|
||||
[env:nodemcuv2]
|
||||
platform = espressif8266
|
||||
board = nodemcuv2
|
||||
framework = arduino
|
||||
upload_speed = 921600
|
||||
monitor_speed = 74880
|
||||
monitor_filters = esp8266_exception_decoder
|
||||
board_build.filesystem = littlefs
|
||||
build_flags = ${common_env_data.build_flags} -D SERIAL_BAUD_RATE=${this.monitor_speed} -D BUILD_DEBUG=false
|
||||
extra_scripts = ${common_env_data.extra_scripts}
|
||||
lib_compat_mode = strict
|
||||
lib_ldf_mode = chain
|
||||
lib_deps =
|
||||
thijse/ArduinoLog @ ^1.1.1
|
||||
bblanchon/ArduinoJson @ ^7.4.2
|
||||
|
62
src/logger/logger.cpp
Normal file
62
src/logger/logger.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
#include <ArduinoLog.h>
|
||||
|
||||
void printTimestamp(Print *_logOutput) {
|
||||
|
||||
// Division constants
|
||||
const unsigned long MSECS_PER_SEC = 1000;
|
||||
const unsigned long SECS_PER_MIN = 60;
|
||||
const unsigned long SECS_PER_HOUR = 3600;
|
||||
const unsigned long SECS_PER_DAY = 86400;
|
||||
|
||||
// Total time
|
||||
const unsigned long msecs = millis();
|
||||
const unsigned long secs = msecs / MSECS_PER_SEC;
|
||||
|
||||
// Time in components
|
||||
const unsigned long MilliSeconds = msecs % MSECS_PER_SEC;
|
||||
const unsigned long Seconds = secs % SECS_PER_MIN;
|
||||
const unsigned long Minutes = (secs / SECS_PER_MIN) % SECS_PER_MIN;
|
||||
const unsigned long Hours = (secs % SECS_PER_DAY) / SECS_PER_HOUR;
|
||||
|
||||
// Time as string
|
||||
char timestamp[20];
|
||||
sprintf(timestamp, "%02lu:%02lu:%02lu.%03lu ", Hours, Minutes, Seconds, MilliSeconds);
|
||||
_logOutput->print(timestamp);
|
||||
}
|
||||
|
||||
void printLogLevel(Print *_logOutput, int logLevel) {
|
||||
/// Show log description based on log level
|
||||
switch (logLevel) {
|
||||
default:
|
||||
case 0:
|
||||
_logOutput->print("SILENT ");
|
||||
break;
|
||||
case 1:
|
||||
_logOutput->print("FATAL ");
|
||||
break;
|
||||
case 2:
|
||||
_logOutput->print("ERROR ");
|
||||
break;
|
||||
case 3:
|
||||
_logOutput->print("WARNING ");
|
||||
break;
|
||||
case 4:
|
||||
_logOutput->print("INFO ");
|
||||
break;
|
||||
case 5:
|
||||
_logOutput->print("TRACE ");
|
||||
break;
|
||||
case 6:
|
||||
_logOutput->print("VERBOSE ");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void printSuffix(Print *_logOutput, int logLevel) {
|
||||
_logOutput->print("");
|
||||
}
|
||||
|
||||
void printPrefix(Print *_logOutput, int logLevel) {
|
||||
printTimestamp(_logOutput);
|
||||
printLogLevel(_logOutput, logLevel);
|
||||
}
|
5
src/logger/logger.h
Normal file
5
src/logger/logger.h
Normal file
@ -0,0 +1,5 @@
|
||||
#include <ArduinoLog.h>
|
||||
|
||||
void printPrefix(Print *_logOutput, int logLevel);
|
||||
|
||||
void printSuffix(Print *_logOutput, int logLevel);
|
58
src/main.cpp
58
src/main.cpp
@ -1,18 +1,56 @@
|
||||
#include "main.h"
|
||||
#include "logger/logger.h"
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoLog.h>
|
||||
#include <LittleFS.h>
|
||||
#include <WiFiManager.h>
|
||||
|
||||
// put function declarations here:
|
||||
int myFunction(int, int);
|
||||
#ifndef SERIAL_BAUD_RATE
|
||||
#define SERIAL_BAUD_RATE 115200
|
||||
#endif
|
||||
|
||||
uint8_t mac[WL_MAC_ADDR_LENGTH];
|
||||
|
||||
WiFiManager manager;
|
||||
|
||||
unsigned long currentMillis, oldMillis;
|
||||
|
||||
void setup() {
|
||||
// put your setup code here, to run once:
|
||||
int result = myFunction(2, 3);
|
||||
Serial.begin(SERIAL_BAUD_RATE);
|
||||
Log.setPrefix(printPrefix);
|
||||
Log.setSuffix(printSuffix);
|
||||
Log.begin(LOG_LEVEL_VERBOSE, &Serial);
|
||||
delay(2000);
|
||||
|
||||
Serial.println("Starting up");
|
||||
|
||||
#ifdef ESP32
|
||||
LittleFS.begin(true);
|
||||
#else
|
||||
LittleFS.begin();
|
||||
#endif
|
||||
|
||||
File file = LittleFS.open("/config/WiFiManager.json", "r");
|
||||
if (!file) {
|
||||
Serial.println("Failed to open file for reading");
|
||||
} else {
|
||||
|
||||
manager.loadFromJson(file);
|
||||
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
void loop() {
|
||||
// put your main code here, to run repeatedly:
|
||||
}
|
||||
|
||||
// put function definitions here:
|
||||
int myFunction(int x, int y) {
|
||||
return x + y;
|
||||
}
|
||||
currentMillis = millis();
|
||||
|
||||
if (currentMillis - oldMillis >= 1000) {
|
||||
oldMillis = currentMillis;
|
||||
}
|
||||
|
||||
if (manager.tick(1000) == WL_CONNECTED) {
|
||||
// regularOperationStuff();
|
||||
}
|
||||
// importantStuff();
|
||||
}
|
||||
|
0
src/main.h
Normal file
0
src/main.h
Normal file
Loading…
Reference in New Issue
Block a user