Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions embedded/libs/esp-web-server/src/esp_web_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,9 @@ void ESPWebServer::handleRoot() {
if (status.connected) {
el.className = 'status connected';
el.innerHTML = 'Connected to <strong>' + status.ssid + '</strong><br>IP: ' + status.ip + ' | Signal: ' + status.rssi + ' dBm';
} else if (status.ap_mode) {
el.className = 'status disconnected';
el.innerHTML = 'Access Point Mode<br>IP: ' + status.ip + '<br><small>Connect to a WiFi network below</small>';
} else {
el.className = 'status disconnected';
el.textContent = 'Not connected';
Expand Down Expand Up @@ -453,6 +456,11 @@ void ESPWebServer::handleWiFiConnect() {
const char* ssid = doc["ssid"];
const char* password = doc["password"] | "";

// Stop AP mode if running before connecting to a new network
if (WiFiMgr.isAPMode()) {
WiFiMgr.stopAP();
}

WiFiMgr.connect(ssid, password);

sendJson(200, "{\"success\":true,\"message\":\"Connecting...\"}");
Expand All @@ -463,9 +471,10 @@ void ESPWebServer::handleWiFiStatus() {
JsonDocument doc;

doc["connected"] = WiFiMgr.isConnected();
doc["ssid"] = WiFiMgr.getSSID();
doc["ip"] = WiFiMgr.getIP();
doc["rssi"] = WiFiMgr.getRSSI();
doc["ap_mode"] = WiFiMgr.isAPMode();
doc["ssid"] = WiFiMgr.isAPMode() ? "" : WiFiMgr.getSSID();
doc["ip"] = WiFiMgr.isAPMode() ? WiFiMgr.getAPIP() : WiFiMgr.getIP();
doc["rssi"] = WiFiMgr.isAPMode() ? 0 : WiFiMgr.getRSSI();

sendJson(200, doc);
}
Expand Down
63 changes: 63 additions & 0 deletions embedded/libs/lilygo-display/src/lilygo_display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,69 @@ void LilyGoDisplay::showConfigPortal(const char* apName, const char* ip) {
_display.setTextDatum(lgfx::top_left);
}

void LilyGoDisplay::showSetupScreen(const char* apName) {
_display.fillScreen(COLOR_BACKGROUND);

// Header
_display.setFont(&fonts::FreeSansBold9pt7b);
_display.setTextColor(COLOR_ACCENT);
_display.setTextDatum(lgfx::top_center);
_display.drawString("WiFi Setup", SCREEN_WIDTH / 2, 8);

// Step 1: Connect to WiFi AP
_display.setFont(&fonts::Font2);
_display.setTextColor(COLOR_TEXT);
_display.drawString("1. Connect to WiFi:", SCREEN_WIDTH / 2, 38);

_display.setFont(&fonts::FreeSansBold9pt7b);
_display.setTextColor(COLOR_STATUS_OK);
_display.drawString(apName, SCREEN_WIDTH / 2, 58);

// QR Code section - generate QR for http://192.168.4.1
const char* configUrl = "http://192.168.4.1";
QRCode qrCode;
qrcode_initText(&qrCode, _qrCodeData, QR_VERSION, ECC_LOW, configUrl);

// Calculate QR code size and position
int qrSize = qrCode.size;
int pixelSize = 100 / qrSize; // Target ~100px QR code
if (pixelSize < 1) pixelSize = 1;

int actualQrSize = pixelSize * qrSize;
int qrX = (SCREEN_WIDTH - actualQrSize) / 2;
int qrY = 90;

// Draw white background for QR code
_display.fillRect(qrX - 4, qrY - 4, actualQrSize + 8, actualQrSize + 8, COLOR_QR_BG);

// Draw QR code modules
for (uint8_t y = 0; y < qrSize; y++) {
for (uint8_t x = 0; x < qrSize; x++) {
if (qrcode_getModule(&qrCode, x, y)) {
_display.fillRect(qrX + x * pixelSize, qrY + y * pixelSize, pixelSize, pixelSize, COLOR_QR_FG);
}
}
}

// Step 2: Instructions below QR code
int instructionY = qrY + actualQrSize + 16;

_display.setFont(&fonts::Font2);
_display.setTextColor(COLOR_TEXT);
_display.drawString("2. Scan QR code or", SCREEN_WIDTH / 2, instructionY);
_display.drawString("open in browser:", SCREEN_WIDTH / 2, instructionY + 18);

_display.setFont(&fonts::FreeSansBold9pt7b);
_display.setTextColor(COLOR_ACCENT);
_display.drawString("192.168.4.1", SCREEN_WIDTH / 2, instructionY + 40);

_display.setFont(&fonts::Font0);
_display.setTextColor(COLOR_TEXT_DIM);
_display.drawString("to configure settings", SCREEN_WIDTH / 2, instructionY + 65);

_display.setTextDatum(lgfx::top_left);
}

void LilyGoDisplay::setSessionId(const char* sessionId) {
_sessionId = sessionId ? sessionId : "";
}
Expand Down
1 change: 1 addition & 0 deletions embedded/libs/lilygo-display/src/lilygo_display.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ class LilyGoDisplay {
void showConnecting();
void showError(const char* message, const char* ipAddress = nullptr);
void showConfigPortal(const char* apName, const char* ip);
void showSetupScreen(const char* apName);

// Climb display
void showClimb(const char* name, const char* grade, const char* gradeColor, int angle, const char* uuid,
Expand Down
50 changes: 50 additions & 0 deletions embedded/libs/wifi-utils/src/wifi_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,47 @@ void WiFiUtils::disconnect() {
setState(WiFiConnectionState::DISCONNECTED);
}

bool WiFiUtils::startAP(const char* apName) {
// Stop any existing connection first
WiFi.disconnect();

// Clear in-memory credentials to prevent checkConnection() from
// attempting reconnection with stale credentials if we later
// transition out of AP mode
currentSSID = "";
currentPassword = "";

// Configure AP mode
WiFi.mode(WIFI_AP);

// Start the access point
bool success = WiFi.softAP(apName);
if (success) {
setState(WiFiConnectionState::AP_MODE);
}
return success;
}

void WiFiUtils::stopAP() {
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
setState(WiFiConnectionState::DISCONNECTED);
}

bool WiFiUtils::isAPMode() {
return state == WiFiConnectionState::AP_MODE;
}

String WiFiUtils::getAPIP() {
return WiFi.softAPIP().toString();
}

bool WiFiUtils::hasSavedCredentials() {
String ssid = Config.getString(KEY_SSID);
return ssid.length() > 0;
}

bool WiFiUtils::isConnected() {
return WiFi.status() == WL_CONNECTED;
}
Expand Down Expand Up @@ -83,6 +124,11 @@ void WiFiUtils::setState(WiFiConnectionState newState) {
}

void WiFiUtils::checkConnection() {
// Don't check STA connection in AP mode
if (state == WiFiConnectionState::AP_MODE) {
return;
}

bool connected = WiFi.status() == WL_CONNECTED;

switch (state) {
Expand All @@ -109,5 +155,9 @@ void WiFiUtils::checkConnection() {
connect(currentSSID.c_str(), currentPassword.c_str(), false);
}
break;

case WiFiConnectionState::AP_MODE:
// Handled at the top of the function
break;
}
}
11 changes: 10 additions & 1 deletion embedded/libs/wifi-utils/src/wifi_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@

#define WIFI_CONNECT_TIMEOUT_MS 30000
#define WIFI_RECONNECT_INTERVAL_MS 5000
#define DEFAULT_AP_NAME "Boardsesh-Setup"
#define DEFAULT_AP_IP "192.168.4.1"

enum class WiFiConnectionState { DISCONNECTED, CONNECTING, CONNECTED, CONNECTION_FAILED };
enum class WiFiConnectionState { DISCONNECTED, CONNECTING, CONNECTED, CONNECTION_FAILED, AP_MODE };

typedef void (*WiFiStateCallback)(WiFiConnectionState state);

Expand All @@ -24,6 +26,13 @@ class WiFiUtils {
bool connectSaved();
void disconnect();

// Access Point mode
bool startAP(const char* apName = DEFAULT_AP_NAME);
void stopAP();
bool isAPMode();
String getAPIP();
bool hasSavedCredentials();

bool isConnected();
WiFiConnectionState getState();

Expand Down
Loading