mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-06 07:37:37 +03:00
feat: Add network credential settings for FTP and HTTP servers, add editable credential changes for hotspot
This commit is contained in:
parent
1aaec3d98d
commit
90e20b49b7
@ -47,6 +47,7 @@ This project is **not affiliated with Xteink**; it's built as a community projec
|
||||
- [ ] Sleep wallpaper picker
|
||||
- [x] Adjustable sleep timer
|
||||
- [x] Set default folder
|
||||
- [x] Custom credentials for Hotspot and FTP/HTTP access
|
||||
|
||||
See [the user guide](./USER_GUIDE.md) for instructions on operating CrossPoint.
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ CrossPointSettings CrossPointSettings::instance;
|
||||
namespace {
|
||||
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
||||
// Increment this when adding new persisted settings fields
|
||||
constexpr uint8_t SETTINGS_COUNT = 22;
|
||||
constexpr uint8_t SETTINGS_COUNT = 28;
|
||||
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
||||
} // namespace
|
||||
|
||||
@ -49,6 +49,12 @@ bool CrossPointSettings::saveToFile() const {
|
||||
serialization::writePod(outputFile, scheduleNetworkMode);
|
||||
serialization::writePod(outputFile, scheduleHour);
|
||||
serialization::writePod(outputFile, scheduleAutoShutdown);
|
||||
serialization::writeString(outputFile, ftpUsername);
|
||||
serialization::writeString(outputFile, ftpPassword);
|
||||
serialization::writeString(outputFile, httpUsername);
|
||||
serialization::writeString(outputFile, httpPassword);
|
||||
serialization::writeString(outputFile, apSsid);
|
||||
serialization::writeString(outputFile, apPassword);
|
||||
outputFile.close();
|
||||
|
||||
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
||||
@ -119,6 +125,18 @@ bool CrossPointSettings::loadFromFile() {
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readPod(inputFile, scheduleAutoShutdown);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readString(inputFile, ftpUsername);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readString(inputFile, ftpPassword);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readString(inputFile, httpUsername);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readString(inputFile, httpPassword);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readString(inputFile, apSsid);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readString(inputFile, apPassword);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
} while (false);
|
||||
|
||||
inputFile.close();
|
||||
|
||||
@ -88,6 +88,14 @@ class CrossPointSettings {
|
||||
// Custom default folder path (used when defaultFolder == FOLDER_CUSTOM)
|
||||
std::string customDefaultFolder = "/books";
|
||||
|
||||
// Network credentials
|
||||
std::string ftpUsername = "crosspoint";
|
||||
std::string ftpPassword = "reader";
|
||||
std::string httpUsername = "crosspoint";
|
||||
std::string httpPassword = "reader";
|
||||
std::string apSsid = "CrossPoint-Reader";
|
||||
std::string apPassword = ""; // Empty = open network
|
||||
|
||||
~CrossPointSettings() = default;
|
||||
|
||||
// Get singleton instance
|
||||
|
||||
@ -18,8 +18,6 @@
|
||||
|
||||
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;
|
||||
@ -219,11 +217,11 @@ void FileTransferActivity::startAccessPoint() {
|
||||
|
||||
// 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);
|
||||
if (!SETTINGS.apPassword.empty() && SETTINGS.apPassword.length() >= 8) {
|
||||
apStarted = WiFi.softAP(SETTINGS.apSsid.c_str(), SETTINGS.apPassword.c_str(), AP_CHANNEL, false, AP_MAX_CONNECTIONS);
|
||||
} else {
|
||||
// Open network (no password)
|
||||
apStarted = WiFi.softAP(AP_SSID, nullptr, AP_CHANNEL, false, AP_MAX_CONNECTIONS);
|
||||
// Open network (no password or password too short)
|
||||
apStarted = WiFi.softAP(SETTINGS.apSsid.c_str(), nullptr, AP_CHANNEL, false, AP_MAX_CONNECTIONS);
|
||||
}
|
||||
|
||||
if (!apStarted) {
|
||||
@ -239,10 +237,10 @@ void FileTransferActivity::startAccessPoint() {
|
||||
char ipStr[16];
|
||||
snprintf(ipStr, sizeof(ipStr), "%d.%d.%d.%d", apIP[0], apIP[1], apIP[2], apIP[3]);
|
||||
connectedIP = ipStr;
|
||||
connectedSSID = AP_SSID;
|
||||
connectedSSID = SETTINGS.apSsid;
|
||||
|
||||
Serial.printf("[%lu] [WEBACT] Access Point started!\n", millis());
|
||||
Serial.printf("[%lu] [WEBACT] SSID: %s\n", millis(), AP_SSID);
|
||||
Serial.printf("[%lu] [WEBACT] SSID: %s\n", millis(), SETTINGS.apSsid.c_str());
|
||||
Serial.printf("[%lu] [WEBACT] IP: %s\n", millis(), connectedIP.c_str());
|
||||
|
||||
// Start mDNS for hostname resolution
|
||||
@ -492,6 +490,13 @@ void FileTransferActivity::renderHttpServerRunning() const {
|
||||
// Show QR code for URL
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, "or scan QR code with your phone:");
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 7, hostnameUrl);
|
||||
|
||||
// Show HTTP credentials at the bottom
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 14, "Browser will ask for credentials:");
|
||||
std::string httpUserStr = "Username: " + SETTINGS.httpUsername;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 15, httpUserStr.c_str());
|
||||
std::string httpPassStr = "Password: " + SETTINGS.httpPassword;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 16, httpPassStr.c_str());
|
||||
} else {
|
||||
// STA mode display (original behavior)
|
||||
const int startY = 65;
|
||||
@ -518,6 +523,13 @@ void FileTransferActivity::renderHttpServerRunning() const {
|
||||
// Show QR code for URL
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 6, webInfo);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "or scan QR code with your phone:");
|
||||
|
||||
// Show HTTP credentials
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 12, "Browser will ask for credentials:");
|
||||
std::string httpUserStr = "Username: " + SETTINGS.httpUsername;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 13, httpUserStr.c_str());
|
||||
std::string httpPassStr = "Password: " + SETTINGS.httpPassword;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 14, httpPassStr.c_str());
|
||||
}
|
||||
|
||||
const auto labels = mappedInput.mapLabels("« Exit", "", "", "");
|
||||
@ -542,7 +554,10 @@ void FileTransferActivity::renderFtpServerRunning() const {
|
||||
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
|
||||
std::string wifiConfig = std::string("WIFI:T:WPA;S:") + connectedSSID + ";P:" + "" + ";;";
|
||||
std::string wifiConfig = std::string("WIFI:T:") +
|
||||
(SETTINGS.apPassword.empty() ? "" : "WPA") +
|
||||
";S:" + connectedSSID +
|
||||
";P:" + SETTINGS.apPassword + ";;";
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 4, wifiConfig);
|
||||
|
||||
startY += 6 * 29 + 3 * LINE_SPACING;
|
||||
@ -554,8 +569,10 @@ void FileTransferActivity::renderFtpServerRunning() const {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 4, ftpInfo.c_str(), true, BOLD);
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "Connect with FTP client:");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, "Username: crosspoint");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 7, "Password: reader");
|
||||
std::string ftpUserStr = "Username: " + SETTINGS.ftpUsername;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, ftpUserStr.c_str());
|
||||
std::string ftpPassStr = "Password: " + SETTINGS.ftpPassword;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 7, ftpPassStr.c_str());
|
||||
} else {
|
||||
// STA mode display
|
||||
const int startY = 65;
|
||||
@ -576,8 +593,10 @@ void FileTransferActivity::renderFtpServerRunning() const {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, startY + LINE_SPACING * 3, ftpInfo.c_str(), true, BOLD);
|
||||
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, "Use FTP client to connect:");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "Username: crosspoint");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, "Password: reader");
|
||||
std::string ftpUserStr = "Username: " + SETTINGS.ftpUsername;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, ftpUserStr.c_str());
|
||||
std::string ftpPassStr = "Password: " + SETTINGS.ftpPassword;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, ftpPassStr.c_str());
|
||||
|
||||
// Show QR code for FTP URL
|
||||
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 8, ftpInfo);
|
||||
|
||||
198
src/activities/settings/CredentialSettingsActivity.cpp
Normal file
198
src/activities/settings/CredentialSettingsActivity.cpp
Normal file
@ -0,0 +1,198 @@
|
||||
#include "CredentialSettingsActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "activities/util/KeyboardEntryActivity.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
namespace {
|
||||
constexpr int FIELD_COUNT = 6;
|
||||
const char* FIELD_NAMES[FIELD_COUNT] = {
|
||||
"FTP Username",
|
||||
"FTP Password",
|
||||
"HTTP Username",
|
||||
"HTTP Password",
|
||||
"Hotspot SSID",
|
||||
"Hotspot Password"
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void CredentialSettingsActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<CredentialSettingsActivity*>(param);
|
||||
self->displayTaskLoop();
|
||||
}
|
||||
|
||||
void CredentialSettingsActivity::onEnter() {
|
||||
Activity::onEnter();
|
||||
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
selectedIndex = 0;
|
||||
updateRequired = true;
|
||||
|
||||
xTaskCreate(&CredentialSettingsActivity::taskTrampoline, "CredentialSettingsTask",
|
||||
2048, // Stack size
|
||||
this, // Parameters
|
||||
1, // Priority
|
||||
&displayTaskHandle // Task handle
|
||||
);
|
||||
}
|
||||
|
||||
void CredentialSettingsActivity::onExit() {
|
||||
ActivityWithSubactivity::onExit();
|
||||
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
}
|
||||
|
||||
void CredentialSettingsActivity::loop() {
|
||||
if (subActivity) {
|
||||
subActivity->loop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
SETTINGS.saveToFile();
|
||||
onGoBack();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
selectCurrentField();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle navigation
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||
selectedIndex = (selectedIndex > 0) ? (selectedIndex - 1) : (FIELD_COUNT - 1);
|
||||
updateRequired = true;
|
||||
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||
selectedIndex = (selectedIndex + 1) % FIELD_COUNT;
|
||||
updateRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CredentialSettingsActivity::selectCurrentField() {
|
||||
std::string* targetField = nullptr;
|
||||
const char* promptText = "";
|
||||
|
||||
switch (selectedIndex) {
|
||||
case 0: // FTP Username
|
||||
targetField = &SETTINGS.ftpUsername;
|
||||
promptText = "Enter FTP username:";
|
||||
break;
|
||||
case 1: // FTP Password
|
||||
targetField = &SETTINGS.ftpPassword;
|
||||
promptText = "Enter FTP password:";
|
||||
break;
|
||||
case 2: // HTTP Username
|
||||
targetField = &SETTINGS.httpUsername;
|
||||
promptText = "Enter HTTP username:";
|
||||
break;
|
||||
case 3: // HTTP Password
|
||||
targetField = &SETTINGS.httpPassword;
|
||||
promptText = "Enter HTTP password:";
|
||||
break;
|
||||
case 4: // Hotspot SSID
|
||||
targetField = &SETTINGS.apSsid;
|
||||
promptText = "Enter hotspot SSID:";
|
||||
break;
|
||||
case 5: // Hotspot Password
|
||||
targetField = &SETTINGS.apPassword;
|
||||
promptText = "Enter hotspot password (leave empty for open network):";
|
||||
break;
|
||||
}
|
||||
|
||||
if (targetField) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
exitActivity();
|
||||
bool isPassword = (selectedIndex == 1 || selectedIndex == 3 || selectedIndex == 5); // Password fields
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput,
|
||||
promptText, // title
|
||||
*targetField, // initialText
|
||||
10, // startY
|
||||
0, // maxLength (0 = unlimited)
|
||||
isPassword, // isPassword
|
||||
[this, targetField](const std::string& newValue) {
|
||||
*targetField = newValue;
|
||||
SETTINGS.saveToFile();
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
},
|
||||
[this] {
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
}));
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
}
|
||||
|
||||
void CredentialSettingsActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
if (updateRequired && !subActivity) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
render();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void CredentialSettingsActivity::render() const {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
// Draw header
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Network Credentials", true, BOLD);
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, 40, "Configure server and hotspot credentials");
|
||||
|
||||
// Draw selection
|
||||
renderer.fillRect(0, 70 + selectedIndex * 35 - 2, pageWidth - 1, 35);
|
||||
|
||||
// Draw fields
|
||||
for (int i = 0; i < FIELD_COUNT; i++) {
|
||||
const int fieldY = 70 + i * 35;
|
||||
const bool isSelected = (i == selectedIndex);
|
||||
|
||||
// Draw field name
|
||||
renderer.drawText(UI_10_FONT_ID, 20, fieldY, FIELD_NAMES[i], !isSelected);
|
||||
|
||||
// Draw current value (masked for passwords)
|
||||
std::string displayValue;
|
||||
switch (i) {
|
||||
case 0: // FTP Username
|
||||
displayValue = SETTINGS.ftpUsername;
|
||||
break;
|
||||
case 1: // FTP Password
|
||||
displayValue = SETTINGS.ftpPassword.empty() ? "" : std::string(SETTINGS.ftpPassword.length(), '*');
|
||||
break;
|
||||
case 2: // Hotspot SSID
|
||||
displayValue = SETTINGS.apSsid;
|
||||
break;
|
||||
case 3: // Hotspot Password
|
||||
displayValue = SETTINGS.apPassword.empty() ? "(open)" : std::string(SETTINGS.apPassword.length(), '*');
|
||||
break;
|
||||
}
|
||||
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, displayValue.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, fieldY, displayValue.c_str(), !isSelected);
|
||||
}
|
||||
|
||||
// Draw help text
|
||||
const auto labels = mappedInput.mapLabels("« Save", "Edit", "", "");
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
35
src/activities/settings/CredentialSettingsActivity.h
Normal file
35
src/activities/settings/CredentialSettingsActivity.h
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "activities/ActivityWithSubactivity.h"
|
||||
|
||||
/**
|
||||
* CredentialSettingsActivity allows users to configure credentials for:
|
||||
* - FTP server (username and password)
|
||||
* - HTTP server (username and password)
|
||||
* - WiFi Hotspot (SSID and password)
|
||||
*/
|
||||
class CredentialSettingsActivity final : public ActivityWithSubactivity {
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
bool updateRequired = false;
|
||||
int selectedIndex = 0; // Currently selected credential field
|
||||
const std::function<void()> onGoBack;
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
void render() const;
|
||||
void selectCurrentField();
|
||||
|
||||
public:
|
||||
explicit CredentialSettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onGoBack)
|
||||
: ActivityWithSubactivity("CredentialSettings", renderer, mappedInput), onGoBack(onGoBack) {}
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
};
|
||||
@ -3,6 +3,7 @@
|
||||
#include <GfxRenderer.h>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "CredentialSettingsActivity.h"
|
||||
#include "FolderPickerActivity.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "OtaUpdateActivity.h"
|
||||
@ -11,7 +12,7 @@
|
||||
|
||||
// Define the static settings list
|
||||
namespace {
|
||||
constexpr int settingsCount = 18;
|
||||
constexpr int settingsCount = 19;
|
||||
const SettingInfo settingsList[settingsCount] = {
|
||||
// Should match with SLEEP_SCREEN_MODE
|
||||
{"Sleep Screen", SettingType::ENUM, &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover"}},
|
||||
@ -51,6 +52,7 @@ const SettingInfo settingsList[settingsCount] = {
|
||||
{"Root", "Custom", "Last Used"}},
|
||||
{"Choose Custom Folder", SettingType::ACTION, nullptr, {}},
|
||||
{"Bluetooth", SettingType::TOGGLE, &CrossPointSettings::bluetoothEnabled, {}},
|
||||
{"Network Credentials", SettingType::ACTION, nullptr, {}},
|
||||
{"File Transfer Schedule", SettingType::ACTION, nullptr, {}},
|
||||
{"Check for updates", SettingType::ACTION, nullptr, {}},
|
||||
};
|
||||
@ -169,6 +171,14 @@ void SettingsActivity::toggleCurrentSetting() {
|
||||
},
|
||||
"/")); // Start from root directory
|
||||
xSemaphoreGive(renderingMutex);
|
||||
} else if (std::string(setting.name) == "Network Credentials") {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
exitActivity();
|
||||
enterNewActivity(new CredentialSettingsActivity(renderer, mappedInput, [this] {
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
}));
|
||||
xSemaphoreGive(renderingMutex);
|
||||
} else if (std::string(setting.name) == "File Transfer Schedule") {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
exitActivity();
|
||||
|
||||
@ -2,11 +2,7 @@
|
||||
|
||||
#include <WiFi.h>
|
||||
|
||||
namespace {
|
||||
// FTP server credentials
|
||||
constexpr const char* FTP_USERNAME = "crosspoint";
|
||||
constexpr const char* FTP_PASSWORD = "reader";
|
||||
} // namespace
|
||||
#include "CrossPointSettings.h"
|
||||
|
||||
CrossPointFtpServer::CrossPointFtpServer() {}
|
||||
|
||||
@ -52,15 +48,15 @@ void CrossPointFtpServer::begin() {
|
||||
}
|
||||
|
||||
// Initialize FTP server with credentials
|
||||
ftpServer->begin(FTP_USERNAME, FTP_PASSWORD);
|
||||
ftpServer->begin(SETTINGS.ftpUsername.c_str(), SETTINGS.ftpPassword.c_str());
|
||||
running = true;
|
||||
|
||||
Serial.printf("[%lu] [FTP] FTP server started on port 21\n", millis());
|
||||
// Show the correct IP based on network mode
|
||||
const String ipAddr = apMode ? WiFi.softAPIP().toString() : WiFi.localIP().toString();
|
||||
Serial.printf("[%lu] [FTP] Access at ftp://%s/\n", millis(), ipAddr.c_str());
|
||||
Serial.printf("[%lu] [FTP] Username: %s\n", millis(), FTP_USERNAME);
|
||||
Serial.printf("[%lu] [FTP] Password: %s\n", millis(), FTP_PASSWORD);
|
||||
Serial.printf("[%lu] [FTP] Username: %s\n", millis(), SETTINGS.ftpUsername.c_str());
|
||||
Serial.printf("[%lu] [FTP] Password: %s\n", millis(), SETTINGS.ftpPassword.c_str());
|
||||
Serial.printf("[%lu] [FTP] [MEM] Free heap after server.begin(): %d bytes\n", millis(), ESP.getFreeHeap());
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "html/FilesPageHtml.generated.h"
|
||||
#include "html/HomePageHtml.generated.h"
|
||||
|
||||
@ -150,7 +151,17 @@ void CrossPointWebServer::handleClient() const {
|
||||
server->handleClient();
|
||||
}
|
||||
|
||||
bool CrossPointWebServer::authenticate() const {
|
||||
if (!server->authenticate(SETTINGS.httpUsername.c_str(), SETTINGS.httpPassword.c_str())) {
|
||||
server->requestAuthentication();
|
||||
Serial.printf("[%lu] [WEB] Authentication failed\n", millis());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CrossPointWebServer::handleRoot() const {
|
||||
if (!authenticate()) return;
|
||||
server->send(200, "text/html", HomePageHtml);
|
||||
Serial.printf("[%lu] [WEB] Served root page\n", millis());
|
||||
}
|
||||
@ -162,6 +173,7 @@ void CrossPointWebServer::handleNotFound() const {
|
||||
}
|
||||
|
||||
void CrossPointWebServer::handleStatus() const {
|
||||
if (!authenticate()) return;
|
||||
// Get correct IP based on AP vs STA mode
|
||||
const String ipAddr = apMode ? WiFi.softAPIP().toString() : WiFi.localIP().toString();
|
||||
|
||||
@ -241,9 +253,13 @@ bool CrossPointWebServer::isEpubFile(const String& filename) const {
|
||||
return lower.endsWith(".epub");
|
||||
}
|
||||
|
||||
void CrossPointWebServer::handleFileList() const { server->send(200, "text/html", FilesPageHtml); }
|
||||
void CrossPointWebServer::handleFileList() const {
|
||||
if (!authenticate()) return;
|
||||
server->send(200, "text/html", FilesPageHtml);
|
||||
}
|
||||
|
||||
void CrossPointWebServer::handleFileListData() const {
|
||||
if (!authenticate()) return;
|
||||
// Get current path from query string (default to root)
|
||||
String currentPath = "/";
|
||||
if (server->hasArg("path")) {
|
||||
@ -302,6 +318,9 @@ static bool uploadSuccess = false;
|
||||
static String uploadError = "";
|
||||
|
||||
void CrossPointWebServer::handleUpload() const {
|
||||
// Check authentication
|
||||
if (!authenticate()) return;
|
||||
|
||||
static unsigned long lastWriteTime = 0;
|
||||
static unsigned long uploadStartTime = 0;
|
||||
static size_t lastLoggedSize = 0;
|
||||
@ -416,6 +435,7 @@ void CrossPointWebServer::handleUpload() const {
|
||||
}
|
||||
|
||||
void CrossPointWebServer::handleUploadPost() const {
|
||||
if (!authenticate()) return;
|
||||
if (uploadSuccess) {
|
||||
server->send(200, "text/plain", "File uploaded successfully: " + uploadFileName);
|
||||
} else {
|
||||
@ -425,6 +445,7 @@ void CrossPointWebServer::handleUploadPost() const {
|
||||
}
|
||||
|
||||
void CrossPointWebServer::handleCreateFolder() const {
|
||||
if (!authenticate()) return;
|
||||
// Get folder name from form data
|
||||
if (!server->hasArg("name")) {
|
||||
server->send(400, "text/plain", "Missing folder name");
|
||||
@ -475,6 +496,7 @@ void CrossPointWebServer::handleCreateFolder() const {
|
||||
}
|
||||
|
||||
void CrossPointWebServer::handleDelete() const {
|
||||
if (!authenticate()) return;
|
||||
// Get path from form data
|
||||
if (!server->hasArg("path")) {
|
||||
server->send(400, "text/plain", "Missing path");
|
||||
|
||||
@ -38,6 +38,9 @@ class CrossPointWebServer {
|
||||
bool apMode = false; // true when running in AP mode, false for STA mode
|
||||
uint16_t port = 80;
|
||||
|
||||
// Authentication helper
|
||||
bool authenticate() const;
|
||||
|
||||
// File scanning
|
||||
void scanFiles(const char* path, const std::function<void(FileInfo)>& callback) const;
|
||||
String formatFileSize(size_t bytes) const;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user