Files
OpenEPaperLink/ESP32_AP-Flasher/src/wifimanager.cpp
Nic Limper b4836e81f3 tag flasher / timestamp content / bugfixes (#217)
- added webinterface for tag flasher
- added tcp transport for communicating with tag flasher (OTG USB also still works)
- added content 'timestamp', makes use of preloaded images and buttons on the 2.9" M3
- webinterface is now aware of C6 and flasher capabilities
- AP can run without ieee801.15.4 radio (i.e. flasher only) by shorting FLASHER_AP_TXD and FLASHER_AP_RXD
- added tcp transport option to OEPL-Flasher.py (serial also still works)
- added new environment OpenEPaperLink_Mini_AP_v4
- lots of finetuning and bug fixes
2024-02-04 20:30:52 +01:00

609 lines
18 KiB
C++

#include "wifimanager.h"
#include <Preferences.h>
#include <WiFi.h>
#include <esp_wifi.h>
#include "newproto.h"
#include "system.h"
#include "tag_db.h"
#include "udp.h"
#include "web.h"
#include "ips_display.h"
#include "tag_db.h"
uint8_t WifiManager::apClients = 0;
uint8_t x_buffer[100];
uint8_t x_position = 0;
WifiManager::WifiManager() {
_reconnectIntervalCheck = 5000;
_retryIntervalCheck = 5 * 60000;
_connectionTimeout = 15000;
_nextReconnectCheck = 0;
_connected = false;
_savewhensuccessfull = false;
_ssid = "";
_APstarted = false;
wifiStatus = NOINIT;
WiFi.onEvent(WiFiEvent);
WiFiEventId_t eventID = WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info) {
Serial.print("WiFi lost connection. Reason: ");
Serial.println(info.wifi_sta_disconnected.reason);
}, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_DISCONNECTED);
}
void WifiManager::terminalLog(String text) {
Serial.println(text);
#ifdef HAS_TFT
TFTLog(text);
#endif
}
void WifiManager::poll() {
if (wifiStatus == AP && millis() > _nextReconnectCheck && _ssid != "") {
if (apClients == 0) {
terminalLog("Attempting to reconnect to WiFi.");
logLine("Attempting to reconnect to WiFi.");
_APstarted = false;
wifiStatus = NOINIT;
connectToWifi();
} else {
_nextReconnectCheck = millis() + _retryIntervalCheck;
}
}
if (wifiStatus == CONNECTED && millis() > _nextReconnectCheck) {
if (WiFi.status() != WL_CONNECTED) {
_connected = false;
terminalLog("WiFi connection lost. Attempting to reconnect.");
logLine("WiFi connection lost. Attempting to reconnect.");
WiFi.reconnect();
waitForConnection();
} else {
_nextReconnectCheck = millis() + _reconnectIntervalCheck;
}
}
#ifndef HAS_USB
// ap_and_flasher has gpio0 in use as FLASHER_AP_POWER
if (digitalRead(0) == LOW) {
Serial.println("GPIO0 LOW");
long starttime = millis();
while (digitalRead(0) == LOW && millis() - starttime < 5000) {
}
if (digitalRead(0) == LOW) {
Serial.println("Resetting WIFI settings");
Preferences preferences;
preferences.begin("wifi", false);
preferences.putString("ssid", "");
preferences.putString("pw", "");
preferences.putString("ip", "");
preferences.putString("mask", "");
preferences.putString("gw", "");
preferences.putString("dns", "");
preferences.end();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
delay(2000);
if (esp_wifi_restore() != ESP_OK) {
Serial.println("WiFi is not initialized by esp_wifi_init ");
} else {
Serial.println("WiFi Configurations Cleared!");
}
delay(100);
ESP.restart();
}
}
#endif
pollSerial();
}
bool WifiManager::connectToWifi() {
Preferences preferences;
preferences.begin("wifi", false);
_ssid = preferences.getString("ssid", WiFi_SSID());
_pass = preferences.getString("pw", WiFi_psk());
if (_ssid == "") {
terminalLog("No connection info saved");
logLine("No connection information saved");
startManagementServer();
return false;
}
terminalLog("ssid: " + String(_ssid));
String ip = preferences.getString("ip", "");
String mask = preferences.getString("mask", "");
String gw = preferences.getString("gw", "");
String dns = preferences.getString("dns", "");
if (ip.length() > 0) {
IPAddress staticIP, subnetMask, gatewayIP, dnsIP;
if (staticIP.fromString(ip) && subnetMask.fromString(mask) && gatewayIP.fromString(gw)) {
if (dns.length() > 0) dnsIP.fromString(dns);
WiFi.config(staticIP, gatewayIP, subnetMask, dnsIP);
terminalLog("Setting static IP: " + ip);
}
}
_connected = connectToWifi(_ssid, _pass, false);
return _connected;
}
bool WifiManager::connectToWifi(String ssid, String pass, bool savewhensuccessfull) {
_ssid = ssid;
_pass = pass;
_savewhensuccessfull = savewhensuccessfull;
_APstarted = false;
WiFi.disconnect(true, true);
delay(100);
WiFi.mode(WIFI_MODE_NULL);
delay(100);
char hostname[32] = "OpenEpaperLink-";
uint8_t mac[6];
esp_read_mac(mac, ESP_MAC_WIFI_STA);
char lastTwoBytes[5];
sprintf(lastTwoBytes, "%02X%02X", mac[4], mac[5]);
strcat(hostname, lastTwoBytes);
if (config.alias[0] != '\0') {
int len = strlen(config.alias);
int j = 0;
for (int i = 0; i < len; i++) {
char c = config.alias[i];
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-') {
hostname[j] = c;
j++;
}
}
hostname[j] = '\0';
}
WiFi.setHostname(hostname);
WiFi.mode(WIFI_STA);
WiFi.setSleep(WIFI_PS_MIN_MODEM);
terminalLog("Connecting to WiFi...");
// logLine("Connecting to WiFi...");
WiFi.persistent(savewhensuccessfull);
WiFi.begin(_ssid.c_str(), _pass.c_str());
_connected = waitForConnection();
return _connected;
}
bool WifiManager::waitForConnection() {
unsigned long timeout = millis() + _connectionTimeout;
wifiStatus = WAIT_CONNECTING;
while (WiFi.status() != WL_CONNECTED) {
if (millis() > timeout) {
terminalLog("!Unable to connect to WiFi");
logLine("Unable to connect to WiFi");
startManagementServer();
return false;
}
vTaskDelay(250 / portTICK_PERIOD_MS);
}
if (_savewhensuccessfull) {
Preferences preferences;
preferences.begin("wifi", false);
preferences.putString("ssid", _ssid);
preferences.putString("pass", _pass);
preferences.end();
_savewhensuccessfull = false;
}
WiFi.setAutoReconnect(true);
WiFi.persistent(true);
IPAddress IP = WiFi.localIP();
terminalLog("Connected!");
// logLine("Connected!");
_nextReconnectCheck = millis() + _reconnectIntervalCheck;
wifiStatus = CONNECTED;
return true;
}
void WifiManager::startManagementServer() {
if (!_APstarted) {
terminalLog("Starting config AP, ssid: OpenEPaperLink");
logLine("Starting configuration AP, ssid OpenEPaperLink");
WiFi.disconnect(true, true);
delay(100);
WiFi.mode(WIFI_AP);
WiFi.softAP("OpenEPaperLink", "", 1, false);
WiFi.softAPsetHostname("OpenEPaperLink");
IPAddress IP = WiFi.softAPIP();
terminalLog("Connect to it, visit http://" + String(IP.toString().c_str()) + "/setup");
_APstarted = true;
_nextReconnectCheck = millis() + _retryIntervalCheck;
wifiStatus = AP;
}
}
String WifiManager::WiFi_SSID() {
wifi_config_t conf;
esp_wifi_get_config(WIFI_IF_STA, &conf);
return String(reinterpret_cast<const char *>(conf.sta.ssid));
}
String WifiManager::WiFi_psk() {
if (WiFiGenericClass::getMode() == WIFI_MODE_NULL) {
return String();
}
wifi_config_t conf;
esp_wifi_get_config(WIFI_IF_STA, &conf);
return String(reinterpret_cast<char *>(conf.sta.password));
}
void WifiManager::pollSerial() {
while (Serial.available() > 0) {
char receivedChar = Serial.read();
if (parse_improv_serial_byte(x_position, receivedChar, x_buffer, onCommandCallback, onErrorCallback)) {
x_buffer[x_position++] = receivedChar;
if (x_position > 100) {
x_position = 0;
Serial.println("buffer full!");
}
} else {
x_position = 0;
}
}
}
void WifiManager::WiFiEvent(WiFiEvent_t event) {
Serial.printf("[WiFi-event %d] ", event);
String eventname="";
switch (event) {
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
eventname = "Connected to access point";
break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
// eventname = "Disconnected from WiFi access point";
break;
case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE:
eventname = "Authentication mode of access point has changed";
break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
eventname = "Obtained IP address: " + String(WiFi.localIP().toString().c_str());
init_udp();
break;
case ARDUINO_EVENT_WIFI_STA_LOST_IP:
eventname = "Lost IP address and IP address is reset to 0";
break;
case ARDUINO_EVENT_WIFI_AP_START:
// eventname = "WiFi access point started";
break;
case ARDUINO_EVENT_WIFI_AP_STOP:
// eventname = "WiFi access point stopped";
break;
case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
apClients++;
// eventname = "Client connected";
break;
case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED:
apClients--;
// eventname = "Client disconnected";
break;
case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED:
// eventname = "Assigned IP address to client";
break;
default:
break;
}
if (eventname) terminalLog(eventname);
// logLine("WiFi event [" + String(event) + "]: " + eventname);
}
// *** Improv
// https : // github.com/jnthas/improv-wifi-demo
#define STR_IMPL(x) #x
#define STR(x) STR_IMPL(x)
#ifndef BUILD_ENV_NAME
#define BUILD_ENV_NAME unknown
#endif
#ifndef BUILD_TIME
#define BUILD_TIME 0
#endif
#ifndef BUILD_VERSION
#define BUILD_VERSION custom
#endif
std::vector<std::string> getLocalUrl() {
return {String("http://" + WiFi.localIP().toString()).c_str()};
}
void onErrorCallback(improv::Error err) {
}
bool onCommandCallback(improv::ImprovCommand cmd) {
switch (cmd.command) {
case improv::Command::GET_CURRENT_STATE: {
if ((WiFi.status() == WL_CONNECTED)) {
set_state(improv::State::STATE_PROVISIONED);
std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_CURRENT_STATE, getLocalUrl(), false);
send_response(data);
} else {
set_state(improv::State::STATE_AUTHORIZED);
}
break;
}
case improv::Command::WIFI_SETTINGS: {
if (cmd.ssid.length() == 0) {
set_error(improv::Error::ERROR_INVALID_RPC);
break;
}
set_state(improv::STATE_PROVISIONING);
ws.enable(false);
refreshAllPending();
saveDB("/current/tagDB.json");
ws.closeAll();
delay(100);
if (wm.connectToWifi(String(cmd.ssid.c_str()), String(cmd.password.c_str()), true)) {
Preferences preferences;
preferences.begin("wifi", false);
preferences.putString("ssid", cmd.ssid.c_str());
preferences.putString("pw", cmd.password.c_str());
preferences.end();
ws.enable(true);
set_state(improv::STATE_PROVISIONED);
std::vector<uint8_t> data = improv::build_rpc_response(improv::WIFI_SETTINGS, getLocalUrl(), false);
send_response(data);
} else {
set_state(improv::STATE_STOPPED);
set_error(improv::Error::ERROR_UNABLE_TO_CONNECT);
}
break;
}
case improv::Command::GET_DEVICE_INFO: {
std::vector<std::string> infos = {
// Firmware name
"OpenEPaperLink",
// Firmware version
STR(BUILD_VERSION),
// Hardware chip/variant
STR(BUILD_ENV_NAME),
// Device name
"Access Point"};
std::vector<uint8_t> data = improv::build_rpc_response(improv::GET_DEVICE_INFO, infos, false);
send_response(data);
break;
}
case improv::Command::GET_WIFI_NETWORKS: {
getAvailableWifiNetworks();
break;
}
default: {
set_error(improv::ERROR_UNKNOWN_RPC);
return false;
}
}
return true;
}
void getAvailableWifiNetworks() {
int networkNum = WiFi.scanNetworks();
for (int id = 0; id < networkNum; ++id) {
std::vector<uint8_t> data = improv::build_rpc_response(
improv::GET_WIFI_NETWORKS, {WiFi.SSID(id), String(WiFi.RSSI(id)), (WiFi.encryptionType(id) == WIFI_AUTH_OPEN ? "NO" : "YES")}, false);
send_response(data);
delay(1);
}
// final response
std::vector<uint8_t> data =
improv::build_rpc_response(improv::GET_WIFI_NETWORKS, std::vector<std::string>{}, false);
send_response(data);
}
void set_state(improv::State state) {
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
data.resize(11);
data[6] = improv::IMPROV_SERIAL_VERSION;
data[7] = improv::TYPE_CURRENT_STATE;
data[8] = 1;
data[9] = state;
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data[10] = checksum;
Serial.write(data.data(), data.size());
}
void send_response(std::vector<uint8_t> &response) {
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
data.resize(9);
data[6] = improv::IMPROV_SERIAL_VERSION;
data[7] = improv::TYPE_RPC_RESPONSE;
data[8] = response.size();
data.insert(data.end(), response.begin(), response.end());
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data.push_back(checksum);
Serial.write(data.data(), data.size());
}
void set_error(improv::Error error) {
std::vector<uint8_t> data = {'I', 'M', 'P', 'R', 'O', 'V'};
data.resize(11);
data[6] = improv::IMPROV_SERIAL_VERSION;
data[7] = improv::TYPE_ERROR_STATE;
data[8] = 1;
data[9] = error;
uint8_t checksum = 0x00;
for (uint8_t d : data)
checksum += d;
data[10] = checksum;
Serial.write(data.data(), data.size());
}
// **** improv ****
namespace improv {
ImprovCommand parse_improv_data(const std::vector<uint8_t> &data, bool check_checksum) {
return parse_improv_data(data.data(), data.size(), check_checksum);
}
ImprovCommand parse_improv_data(const uint8_t *data, size_t length, bool check_checksum) {
ImprovCommand improv_command;
Command command = (Command)data[0];
uint8_t data_length = data[1];
if (data_length != length - 2 - check_checksum) {
improv_command.command = UNKNOWN;
return improv_command;
}
if (check_checksum) {
uint8_t checksum = data[length - 1];
uint32_t calculated_checksum = 0;
for (uint8_t i = 0; i < length - 1; i++) {
calculated_checksum += data[i];
}
if ((uint8_t)calculated_checksum != checksum) {
improv_command.command = BAD_CHECKSUM;
return improv_command;
}
}
if (command == WIFI_SETTINGS) {
uint8_t ssid_length = data[2];
uint8_t ssid_start = 3;
size_t ssid_end = ssid_start + ssid_length;
uint8_t pass_length = data[ssid_end];
size_t pass_start = ssid_end + 1;
size_t pass_end = pass_start + pass_length;
std::string ssid(data + ssid_start, data + ssid_end);
std::string password(data + pass_start, data + pass_end);
return {.command = command, .ssid = ssid, .password = password};
}
improv_command.command = command;
return improv_command;
}
bool parse_improv_serial_byte(size_t position, uint8_t byte, const uint8_t *buffer,
std::function<bool(ImprovCommand)> &&callback, std::function<void(Error)> &&on_error) {
if (position == 0)
return byte == 'I';
if (position == 1)
return byte == 'M';
if (position == 2)
return byte == 'P';
if (position == 3)
return byte == 'R';
if (position == 4)
return byte == 'O';
if (position == 5)
return byte == 'V';
if (position == 6)
return byte == IMPROV_SERIAL_VERSION;
if (position <= 8)
return true;
uint8_t type = buffer[7];
uint8_t data_len = buffer[8];
if (position <= 8 + data_len)
return true;
if (position == 8 + data_len + 1) {
uint8_t checksum = 0x00;
for (size_t i = 0; i < position; i++)
checksum += buffer[i];
if (checksum != byte) {
on_error(ERROR_INVALID_RPC);
return false;
}
if (type == TYPE_RPC) {
auto command = parse_improv_data(&buffer[9], data_len, false);
return callback(command);
}
}
return false;
}
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<std::string> &datum, bool add_checksum) {
std::vector<uint8_t> out;
uint32_t length = 0;
out.push_back(command);
for (const auto &str : datum) {
uint8_t len = str.length();
length += len + 1;
out.push_back(len);
out.insert(out.end(), str.begin(), str.end());
}
out.insert(out.begin() + 1, length);
if (add_checksum) {
uint32_t calculated_checksum = 0;
for (uint8_t byte : out) {
calculated_checksum += byte;
}
out.push_back(calculated_checksum);
}
return out;
}
std::vector<uint8_t> build_rpc_response(Command command, const std::vector<String> &datum, bool add_checksum) {
std::vector<uint8_t> out;
uint32_t length = 0;
out.push_back(command);
for (const auto &str : datum) {
uint8_t len = str.length();
length += len;
out.push_back(len);
out.insert(out.end(), str.begin(), str.end());
}
out.insert(out.begin() + 1, length);
if (add_checksum) {
uint32_t calculated_checksum = 0;
for (uint8_t byte : out) {
calculated_checksum += byte;
}
out.push_back(calculated_checksum);
}
return out;
}
} // namespace improv