feat: Add network credential settings for FTP and HTTP servers, add editable credential changes for hotspot
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user