Add FTP server support with protocol selection and QR codes
This commit integrates FTP server functionality alongside the existing HTTP server, allowing users to choose their preferred file transfer protocol. Key changes: - Added SimpleFTPServer library dependency (configured for SdFat2) - Created CrossPointFtpServer wrapper for FTP server management - Added network credentials to CrossPointSettings (ftpUsername, ftpPassword, httpUsername, httpPassword) - Created ProtocolSelectionActivity for HTTP/FTP choice - Created FileTransferActivity to replace CrossPointWebServerActivity - Supports both HTTP and FTP protocols - Shows QR codes for WiFi credentials (AP mode) and server URLs - Displays FTP credentials on screen for easy access - Updated main.cpp to use FileTransferActivity instead of CrossPointWebServerActivity The FTP server provides an alternative file transfer method that works well with dedicated FTP clients and offers better performance for large file transfers. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
487
src/activities/network/FileTransferActivity.cpp
Normal file
487
src/activities/network/FileTransferActivity.cpp
Normal file
@@ -0,0 +1,487 @@
|
||||
#include "FileTransferActivity.h"
|
||||
|
||||
#include <DNSServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <WiFi.h>
|
||||
#include <qrcode.h>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "WifiSelectionActivity.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
namespace {
|
||||
// AP Mode configuration
|
||||
constexpr const char* AP_SSID = "CrossPoint-Reader";
|
||||
constexpr const char* AP_PASSWORD = nullptr; // Open network for ease of use
|
||||
constexpr const char* AP_HOSTNAME = "crosspoint";
|
||||
constexpr uint8_t AP_CHANNEL = 1;
|
||||
constexpr uint8_t AP_MAX_CONNECTIONS = 4;
|
||||
|
||||
// DNS server for captive portal (redirects all DNS queries to our IP)
|
||||
DNSServer* dnsServer = nullptr;
|
||||
constexpr uint16_t DNS_PORT = 53;
|
||||
|
||||
void drawQRCode(const GfxRenderer& renderer, const int x, const int y, const std::string& data) {
|
||||
// Implementation of QR code calculation
|
||||
// The structure to manage the QR code
|
||||
QRCode qrcode;
|
||||
uint8_t qrcodeBytes[qrcode_getBufferSize(4)];
|
||||
Serial.printf("[%lu] [FTACT] QR Code (%lu): %s\n", millis(), data.length(), data.c_str());
|
||||
|
||||
qrcode_initText(&qrcode, qrcodeBytes, 4, ECC_LOW, data.c_str());
|
||||
const uint8_t px = 6; // pixels per module
|
||||
for (uint8_t cy = 0; cy < qrcode.size; cy++) {
|
||||
for (uint8_t cx = 0; cx < qrcode.size; cx++) {
|
||||
if (qrcode_getModule(&qrcode, cx, cy)) {
|
||||
renderer.fillRect(x + px * cx, y + px * cy, px, px, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void FileTransferActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<FileTransferActivity*>(param);
|
||||
self->displayTaskLoop();
|
||||
}
|
||||
|
||||
void FileTransferActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
|
||||
Serial.printf("[%lu] [FTACT] [MEM] Free heap at onEnter: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
|
||||
// Reset state
|
||||
state = FileTransferActivityState::PROTOCOL_SELECTION;
|
||||
selectedProtocol = FileTransferProtocol::HTTP;
|
||||
networkMode = NetworkMode::JOIN_NETWORK;
|
||||
isApMode = false;
|
||||
connectedIP.clear();
|
||||
connectedSSID.clear();
|
||||
lastHandleClientTime = 0;
|
||||
updateRequired = true;
|
||||
|
||||
xTaskCreate(&FileTransferActivity::taskTrampoline, "FileTransferTask",
|
||||
2048, // Stack size
|
||||
this, // Parameters
|
||||
1, // Priority
|
||||
&displayTaskHandle // Task handle
|
||||
);
|
||||
|
||||
// Launch protocol selection subactivity
|
||||
Serial.printf("[%lu] [FTACT] Launching ProtocolSelectionActivity...\n", millis());
|
||||
enterNewActivity(new ProtocolSelectionActivity(renderer, mappedInput,
|
||||
[this](const FileTransferProtocol protocol) {
|
||||
onProtocolSelected(protocol);
|
||||
}));
|
||||
}
|
||||
|
||||
void FileTransferActivity::onExit() {
|
||||
ActivityWithSubactivity::onExit();
|
||||
|
||||
Serial.printf("[%lu] [FTACT] [MEM] Free heap at onExit start: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||
|
||||
state = FileTransferActivityState::SHUTTING_DOWN;
|
||||
|
||||
// Stop the server first (before disconnecting WiFi)
|
||||
stopServer();
|
||||
|
||||
// Stop mDNS
|
||||
MDNS.end();
|
||||
|
||||
// Stop DNS server if running (AP mode)
|
||||
if (dnsServer) {
|
||||
Serial.printf("[%lu] [FTACT] Stopping DNS server...\n", millis());
|
||||
dnsServer->stop();
|
||||
delete dnsServer;
|
||||
dnsServer = nullptr;
|
||||
}
|
||||
|
||||
// CRITICAL: Wait for LWIP stack to flush any pending packets
|
||||
Serial.printf("[%lu] [FTACT] Waiting 500ms for network stack to flush pending packets...\n", millis());
|
||||
delay(500);
|
||||
|
||||
// Disconnect WiFi gracefully
|
||||
if (isApMode) {
|
||||
Serial.printf("[%lu] [FTACT] Stopping WiFi AP...\n", millis());
|
||||
WiFi.softAPdisconnect(true);
|
||||
} else {
|
||||
Serial.printf("[%lu] [FTACT] Disconnecting WiFi (graceful)...\n", millis());
|
||||
WiFi.disconnect(false); // false = don't erase credentials, send disconnect frame
|
||||
}
|
||||
delay(100); // Allow disconnect frame to be sent
|
||||
|
||||
Serial.printf("[%lu] [FTACT] Setting WiFi mode OFF...\n", millis());
|
||||
WiFi.mode(WIFI_OFF);
|
||||
delay(100); // Allow WiFi hardware to fully power down
|
||||
|
||||
Serial.printf("[%lu] [FTACT] [MEM] Free heap after WiFi disconnect: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||
|
||||
// Acquire mutex before deleting task
|
||||
Serial.printf("[%lu] [FTACT] Acquiring rendering mutex before task deletion...\n", millis());
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
|
||||
// Delete the display task
|
||||
Serial.printf("[%lu] [FTACT] Deleting display task...\n", millis());
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
Serial.printf("[%lu] [FTACT] Display task deleted\n", millis());
|
||||
}
|
||||
|
||||
// Delete the mutex
|
||||
Serial.printf("[%lu] [FTACT] Deleting mutex...\n", millis());
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
Serial.printf("[%lu] [FTACT] Mutex deleted\n", millis());
|
||||
|
||||
Serial.printf("[%lu] [FTACT] [MEM] Free heap at onExit end: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||
}
|
||||
|
||||
void FileTransferActivity::onProtocolSelected(const FileTransferProtocol protocol) {
|
||||
Serial.printf("[%lu] [FTACT] Protocol selected: %s\n", millis(), protocol == FileTransferProtocol::HTTP ? "HTTP" : "FTP");
|
||||
|
||||
selectedProtocol = protocol;
|
||||
|
||||
// Exit protocol selection subactivity
|
||||
exitActivity();
|
||||
|
||||
// Launch network mode selection
|
||||
state = FileTransferActivityState::MODE_SELECTION;
|
||||
Serial.printf("[%lu] [FTACT] Launching NetworkModeSelectionActivity...\n", millis());
|
||||
enterNewActivity(new NetworkModeSelectionActivity(
|
||||
renderer, mappedInput, [this](const NetworkMode mode) { onNetworkModeSelected(mode); },
|
||||
[this]() { onGoBack(); } // Cancel goes back to home
|
||||
));
|
||||
}
|
||||
|
||||
void FileTransferActivity::onNetworkModeSelected(const NetworkMode mode) {
|
||||
Serial.printf("[%lu] [FTACT] Network mode selected: %s\n", millis(),
|
||||
mode == NetworkMode::JOIN_NETWORK ? "Join Network" : "Create Hotspot");
|
||||
|
||||
networkMode = mode;
|
||||
isApMode = (mode == NetworkMode::CREATE_HOTSPOT);
|
||||
|
||||
// Exit mode selection subactivity
|
||||
exitActivity();
|
||||
|
||||
if (mode == NetworkMode::JOIN_NETWORK) {
|
||||
// STA mode - launch WiFi selection
|
||||
Serial.printf("[%lu] [FTACT] Turning on WiFi (STA mode)...\n", millis());
|
||||
WiFi.mode(WIFI_STA);
|
||||
|
||||
state = FileTransferActivityState::WIFI_SELECTION;
|
||||
Serial.printf("[%lu] [FTACT] Launching WifiSelectionActivity...\n", millis());
|
||||
enterNewActivity(new WifiSelectionActivity(renderer, mappedInput,
|
||||
[this](const bool connected) { onWifiSelectionComplete(connected); }));
|
||||
} else {
|
||||
// AP mode - start access point
|
||||
state = FileTransferActivityState::AP_STARTING;
|
||||
updateRequired = true;
|
||||
startAccessPoint();
|
||||
}
|
||||
}
|
||||
|
||||
void FileTransferActivity::onWifiSelectionComplete(const bool connected) {
|
||||
Serial.printf("[%lu] [FTACT] WifiSelectionActivity completed, connected=%d\n", millis(), connected);
|
||||
|
||||
if (connected) {
|
||||
// Get connection info before exiting subactivity
|
||||
connectedIP = static_cast<WifiSelectionActivity*>(subActivity.get())->getConnectedIP();
|
||||
connectedSSID = WiFi.SSID().c_str();
|
||||
isApMode = false;
|
||||
|
||||
exitActivity();
|
||||
|
||||
// Start mDNS for hostname resolution
|
||||
if (MDNS.begin(AP_HOSTNAME)) {
|
||||
Serial.printf("[%lu] [FTACT] mDNS started: %s.local\n", millis(), AP_HOSTNAME);
|
||||
}
|
||||
|
||||
// Start the server
|
||||
startServer();
|
||||
} else {
|
||||
// User cancelled - go back to mode selection
|
||||
exitActivity();
|
||||
state = FileTransferActivityState::MODE_SELECTION;
|
||||
enterNewActivity(new NetworkModeSelectionActivity(
|
||||
renderer, mappedInput, [this](const NetworkMode mode) { onNetworkModeSelected(mode); },
|
||||
[this]() { onGoBack(); }));
|
||||
}
|
||||
}
|
||||
|
||||
void FileTransferActivity::startAccessPoint() {
|
||||
Serial.printf("[%lu] [FTACT] Starting Access Point mode...\n", millis());
|
||||
Serial.printf("[%lu] [FTACT] [MEM] Free heap before AP start: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||
|
||||
// Configure and start the AP
|
||||
WiFi.mode(WIFI_AP);
|
||||
delay(100);
|
||||
|
||||
// Start soft AP
|
||||
bool apStarted;
|
||||
if (AP_PASSWORD && strlen(AP_PASSWORD) >= 8) {
|
||||
apStarted = WiFi.softAP(AP_SSID, AP_PASSWORD, AP_CHANNEL, false, AP_MAX_CONNECTIONS);
|
||||
} else {
|
||||
// Open network (no password)
|
||||
apStarted = WiFi.softAP(AP_SSID, nullptr, AP_CHANNEL, false, AP_MAX_CONNECTIONS);
|
||||
}
|
||||
|
||||
if (!apStarted) {
|
||||
Serial.printf("[%lu] [FTACT] ERROR: Failed to start Access Point!\n", millis());
|
||||
onGoBack();
|
||||
return;
|
||||
}
|
||||
|
||||
delay(100); // Wait for AP to fully initialize
|
||||
|
||||
// Get AP IP address
|
||||
const IPAddress apIP = WiFi.softAPIP();
|
||||
char ipStr[16];
|
||||
snprintf(ipStr, sizeof(ipStr), "%d.%d.%d.%d", apIP[0], apIP[1], apIP[2], apIP[3]);
|
||||
connectedIP = ipStr;
|
||||
connectedSSID = AP_SSID;
|
||||
|
||||
Serial.printf("[%lu] [FTACT] Access Point started!\n", millis());
|
||||
Serial.printf("[%lu] [FTACT] SSID: %s\n", millis(), AP_SSID);
|
||||
Serial.printf("[%lu] [FTACT] IP: %s\n", millis(), connectedIP.c_str());
|
||||
|
||||
// Start mDNS for hostname resolution
|
||||
if (MDNS.begin(AP_HOSTNAME)) {
|
||||
Serial.printf("[%lu] [FTACT] mDNS started: %s.local\n", millis(), AP_HOSTNAME);
|
||||
} else {
|
||||
Serial.printf("[%lu] [FTACT] WARNING: mDNS failed to start\n", millis());
|
||||
}
|
||||
|
||||
// Start DNS server for captive portal behavior
|
||||
// This redirects all DNS queries to our IP, making any domain typed resolve to us
|
||||
dnsServer = new DNSServer();
|
||||
dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
|
||||
dnsServer->start(DNS_PORT, "*", apIP);
|
||||
Serial.printf("[%lu] [FTACT] DNS server started for captive portal\n", millis());
|
||||
|
||||
Serial.printf("[%lu] [FTACT] [MEM] Free heap after AP start: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||
|
||||
// Start the server
|
||||
startServer();
|
||||
}
|
||||
|
||||
void FileTransferActivity::startServer() {
|
||||
Serial.printf("[%lu] [FTACT] Starting %s server...\n", millis(),
|
||||
selectedProtocol == FileTransferProtocol::HTTP ? "HTTP" : "FTP");
|
||||
|
||||
if (selectedProtocol == FileTransferProtocol::HTTP) {
|
||||
// Create and start HTTP server
|
||||
webServer.reset(new CrossPointWebServer());
|
||||
webServer->begin();
|
||||
|
||||
if (webServer->isRunning()) {
|
||||
state = FileTransferActivityState::SERVER_RUNNING;
|
||||
Serial.printf("[%lu] [FTACT] HTTP server started successfully\n", millis());
|
||||
} else {
|
||||
Serial.printf("[%lu] [FTACT] ERROR: Failed to start HTTP server!\n", millis());
|
||||
webServer.reset();
|
||||
onGoBack();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Create and start FTP server
|
||||
ftpServer.reset(new CrossPointFtpServer());
|
||||
if (ftpServer->begin()) {
|
||||
state = FileTransferActivityState::SERVER_RUNNING;
|
||||
Serial.printf("[%lu] [FTACT] FTP server started successfully\n", millis());
|
||||
} else {
|
||||
Serial.printf("[%lu] [FTACT] ERROR: Failed to start FTP server!\n", millis());
|
||||
ftpServer.reset();
|
||||
onGoBack();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Force an immediate render since we're transitioning from a subactivity
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
render();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
Serial.printf("[%lu] [FTACT] Rendered File Transfer screen\n", millis());
|
||||
}
|
||||
|
||||
void FileTransferActivity::stopServer() {
|
||||
if (webServer && webServer->isRunning()) {
|
||||
Serial.printf("[%lu] [FTACT] Stopping HTTP server...\n", millis());
|
||||
webServer->stop();
|
||||
Serial.printf("[%lu] [FTACT] HTTP server stopped\n", millis());
|
||||
}
|
||||
webServer.reset();
|
||||
|
||||
if (ftpServer && ftpServer->running()) {
|
||||
Serial.printf("[%lu] [FTACT] Stopping FTP server...\n", millis());
|
||||
ftpServer->stop();
|
||||
Serial.printf("[%lu] [FTACT] FTP server stopped\n", millis());
|
||||
}
|
||||
ftpServer.reset();
|
||||
}
|
||||
|
||||
void FileTransferActivity::loop() {
|
||||
if (subActivity) {
|
||||
// Forward loop to subactivity
|
||||
subActivity->loop();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle different states
|
||||
if (state == FileTransferActivityState::SERVER_RUNNING) {
|
||||
// Handle DNS requests for captive portal (AP mode only)
|
||||
if (isApMode && dnsServer) {
|
||||
dnsServer->processNextRequest();
|
||||
}
|
||||
|
||||
// Handle server requests
|
||||
if (selectedProtocol == FileTransferProtocol::HTTP && webServer && webServer->isRunning()) {
|
||||
const unsigned long timeSinceLastHandleClient = millis() - lastHandleClientTime;
|
||||
|
||||
// Log if there's a significant gap between handleClient calls (>100ms)
|
||||
if (lastHandleClientTime > 0 && timeSinceLastHandleClient > 100) {
|
||||
Serial.printf("[%lu] [FTACT] WARNING: %lu ms gap since last handleClient\n", millis(),
|
||||
timeSinceLastHandleClient);
|
||||
}
|
||||
|
||||
// Call handleClient multiple times to process pending requests faster
|
||||
constexpr int HANDLE_CLIENT_ITERATIONS = 10;
|
||||
for (int i = 0; i < HANDLE_CLIENT_ITERATIONS && webServer->isRunning(); i++) {
|
||||
webServer->handleClient();
|
||||
}
|
||||
lastHandleClientTime = millis();
|
||||
} else if (selectedProtocol == FileTransferProtocol::FTP && ftpServer && ftpServer->running()) {
|
||||
ftpServer->handleClient();
|
||||
}
|
||||
|
||||
// Handle exit on Back button
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onGoBack();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileTransferActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
if (updateRequired) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
render();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void FileTransferActivity::render() const {
|
||||
// Only render our own UI when server is running
|
||||
// Subactivities handle their own rendering
|
||||
if (state == FileTransferActivityState::SERVER_RUNNING) {
|
||||
renderer.clearScreen();
|
||||
renderServerRunning();
|
||||
renderer.displayBuffer();
|
||||
} else if (state == FileTransferActivityState::AP_STARTING) {
|
||||
renderer.clearScreen();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, "Starting Hotspot...", true, EpdFontFamily::BOLD);
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
}
|
||||
|
||||
void FileTransferActivity::renderServerRunning() const {
|
||||
// Use consistent line spacing
|
||||
constexpr int LINE_SPACING = 28; // Space between lines
|
||||
|
||||
const char* protocolName = selectedProtocol == FileTransferProtocol::HTTP ? "HTTP" : "FTP";
|
||||
const std::string title = std::string("File Transfer (") + protocolName + ")";
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, title.c_str(), true, EpdFontFamily::BOLD);
|
||||
|
||||
if (isApMode) {
|
||||
// AP mode display - center the content block
|
||||
int startY = 55;
|
||||
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY, "Hotspot Mode", true, EpdFontFamily::BOLD);
|
||||
|
||||
std::string ssidInfo = "Network: " + connectedSSID;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING, ssidInfo.c_str());
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 2, "Connect your device to this WiFi network");
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3,
|
||||
"or scan QR code with your phone to connect to WiFi:");
|
||||
// Show QR code for WiFi
|
||||
const std::string wifiConfig = std::string("WIFI:S:") + connectedSSID + ";;";
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 4, wifiConfig);
|
||||
|
||||
startY += 6 * 29 + 3 * LINE_SPACING;
|
||||
|
||||
// Show URL based on protocol
|
||||
std::string serverUrl;
|
||||
if (selectedProtocol == FileTransferProtocol::HTTP) {
|
||||
serverUrl = std::string("http://") + AP_HOSTNAME + ".local/";
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 3, serverUrl.c_str(), true, EpdFontFamily::BOLD);
|
||||
|
||||
std::string ipUrl = "or http://" + connectedIP + "/";
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, ipUrl.c_str());
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "Open this URL in your browser");
|
||||
} else {
|
||||
// FTP URL with credentials
|
||||
serverUrl = std::string("ftp://") + SETTINGS.ftpUsername + ":" + SETTINGS.ftpPassword + "@" + connectedIP + "/";
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 3, serverUrl.c_str(), true, EpdFontFamily::BOLD);
|
||||
|
||||
std::string ftpInfo = "User: " + SETTINGS.ftpUsername + " | Pass: " + SETTINGS.ftpPassword;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, ftpInfo.c_str());
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "Use FTP client or scan QR code:");
|
||||
}
|
||||
|
||||
// Show QR code for server URL
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 6, serverUrl);
|
||||
} else {
|
||||
// STA mode display
|
||||
const int startY = 65;
|
||||
|
||||
std::string ssidInfo = "Network: " + connectedSSID;
|
||||
if (ssidInfo.length() > 28) {
|
||||
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
||||
}
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY, ssidInfo.c_str());
|
||||
|
||||
std::string ipInfo = "IP Address: " + connectedIP;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING, ipInfo.c_str());
|
||||
|
||||
// Show server URL based on protocol
|
||||
std::string serverUrl;
|
||||
if (selectedProtocol == FileTransferProtocol::HTTP) {
|
||||
serverUrl = "http://" + connectedIP + "/";
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 2, serverUrl.c_str(), true, EpdFontFamily::BOLD);
|
||||
|
||||
std::string hostnameUrl = std::string("or http://") + AP_HOSTNAME + ".local/";
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, hostnameUrl.c_str());
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, "Open this URL in your browser");
|
||||
} else {
|
||||
// FTP URL with credentials
|
||||
serverUrl = std::string("ftp://") + SETTINGS.ftpUsername + ":" + SETTINGS.ftpPassword + "@" + connectedIP + "/";
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 2, serverUrl.c_str(), true, EpdFontFamily::BOLD);
|
||||
|
||||
std::string ftpInfo = "User: " + SETTINGS.ftpUsername + " | Pass: " + SETTINGS.ftpPassword;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 3, ftpInfo.c_str());
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, "Use FTP client or scan QR code:");
|
||||
}
|
||||
|
||||
// Show QR code for server URL
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "or scan QR code with your phone:");
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 6, serverUrl);
|
||||
}
|
||||
|
||||
const auto labels = mappedInput.mapLabels("« Exit", "", "", "");
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
}
|
||||
Reference in New Issue
Block a user