ESP32: сохранить до 8 Wi-Fi сетей и ускорить burn

This commit is contained in:
AidarKC 2026-06-10 13:24:50 +04:00
parent 9ca469a075
commit f095182673
6 changed files with 191 additions and 26 deletions

View File

@ -20,6 +20,9 @@
- `SELECT NETWORK` запускает скан;
- после скана показывается список доступных SSID;
- выбор SSID открывает общий экран редактирования текста для пароля;
- если для этого SSID пароль уже сохранялся раньше, он автоматически подставляется в редактор;
- если затем ввести пароль для другого SSID, пароль первой сети не теряется;
- одновременно хранится до `8` паролей для разных SSID;
- на этом экране видно старое значение, курсор стоит в конце;
- две верхние служебные строки над полем ввода отсутствуют;
- при вводе пароля Wi-Fi текст показывается открыто, без точек;
@ -40,6 +43,7 @@
- медленный свайп по экрану не должен превращаться в случайное нажатие кнопки;
- `ABC/123`, `SHIFT`, `DEL`, `SAVE`, `CANCEL` работают;
- при успехе SSID и пароль сохраняются, а `HOME` показывает `Wi-Fi connected`;
- если после подключения ко второй сети снова выбрать первую, её старый пароль уже подставлен и достаточно нажать `SAVE`;
- при ошибке показывается `Connection failed`;
- `CLEAR SAVED WI-FI` очищает сохранённые настройки;
- если сеть была ранее успешно сохранена, после потери связи устройство автоматически пытается переподключиться;
@ -62,6 +66,7 @@
- `EDIT MANUALLY` открывает общий экран редактирования и сохраняет значение в NVS;
- `Secret` открывает экран-заглушку, где сказано, что настройка ещё не реализована;
- `Secret` теперь открывает меню секрета с показом секрета, ручным вводом и генерацией;
- в `SHOW SECRET` сам секрет показывается увеличенным шрифтом и разбит по `10` символов в строке;
- при смене `login` сохранённый секрет сбрасывается в `not set`;
- во время генерации секрета есть `CANCEL` и подтверждение остановки;
- при отмене генерации старый секрет, если он был, не должен теряться;

View File

@ -90,7 +90,7 @@
Показывает:
- текущий Wi-Fi статус;
- сохранённый SSID;
- сохранённый SSID и число известных сетей;
- статусное сообщение;
- кнопку `SELECT NETWORK`;
- кнопку `CLEAR SAVED WI-FI`;
@ -109,6 +109,13 @@
Нажатие на SSID открывает `TEXT_EDIT_SCREEN` для ввода пароля.
Поведение:
- если для выбранного SSID пароль уже был сохранён раньше, он сразу подставляется в поле ввода;
- если пароль меняют для другого SSID, старые сохранённые пароли других сетей не теряются;
- после успешного подключения выбранная сеть становится текущей `wifi_ssid/wifi_pass`;
- одновременно хранится до `8` известных сетей `SSID -> password`;
- `CLEAR SAVED WI-FI` очищает все сохранённые сети и текущую сеть.
Переходы:
- свайп вправо из любого режима `WIFI_SCREEN` -> `SETTINGS_MENU`
- кнопка `BACK` -> `SETTINGS_MENU`
@ -162,16 +169,26 @@
## ACCOUNT_SECRET_SCREEN
Пока это заглушка.
Показывает:
- текущий статус секрета `set/not set`;
- сообщение, что настройка секрета пока не реализована;
- кнопку `BACK`.
- кнопку `SHOW SECRET` или серую `SECRET NOT SET`, если секрета ещё нет;
- кнопку `ENTER SECRET MANUALLY (NOT RECOMMENDED)`;
- кнопку `GENERATE SECRET`.
Переходы:
- свайп вправо -> `ACCOUNT_SCREEN`
- `BACK` -> `ACCOUNT_SCREEN`
- `SHOW SECRET` -> экран просмотра секрета
- `ENTER SECRET MANUALLY (NOT RECOMMENDED)` -> `TEXT_EDIT_SCREEN`
- `GENERATE SECRET` -> экран подтверждения генерации
## SECRET_SHOW_SCREEN
Показывает:
- заголовок `SECRET`;
- подпись `Current secret in base58:`;
- полный секрет открытым текстом;
- увеличенный шрифт для значения секрета;
- разбиение секрета по строкам по `10` символов;
- кнопку `BACK`.
## TEXT_EDIT_SCREEN
@ -211,8 +228,13 @@
- `wifi_ssid`
- `wifi_pass`
- `wifi_known_good`
- до `8` сохранённых пар `SSID -> password`
При старте устройства, если сохранён SSID, выполняется попытка подключения к сохранённой сети.
При старте устройства, если сохранён SSID, выполняется попытка подключения к текущей сохранённой сети.
Если пользователь уже вводил пароль для сети раньше:
- при повторном выборе этого SSID старый пароль сразу подставляется в editor screen;
- сохранение пароля для другой сети не удаляет уже сохранённые пароли остальных сетей.
Если сеть раньше уже была успешно подключена и помечена как валидная:
- после потери связи устройство автоматически пытается переподключиться;

View File

@ -1,6 +1,10 @@
# Test Device
Скрипт заливает официальные Arduino-примеры для быстрой проверки платы.
`burn.sh` теперь:
- сам пытается найти USB-порт ESP32;
- сначала делает быструю инкрементальную сборку;
- если быстрая сборка не удалась, автоматически повторяет полную `clean`-сборку.
Для режимов `widgets`, `audio` и `hello` рядом должен лежать локальный checkout `official-demo/` из официального репозитория Waveshare. В основной git он не добавляется, потому что это большой внешний набор примеров, библиотек, прошивок и артефактов.

View File

@ -5,11 +5,29 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BOARD_DIR="$(cd "${ROOT_DIR}/.." && pwd)"
DEMO_BASE="${BOARD_DIR}/official-demo/examples/Arduino-v3.3.5"
MODE="${1:-widgets}"
PORT="${PORT:-/dev/ttyACM0}"
PORT="${PORT:-}"
FQBN="${FQBN:-esp32:esp32:esp32s3:USBMode=hwcdc,CDCOnBoot=cdc,UploadSpeed=921600,CPUFreq=240,FlashMode=dio,FlashSize=16M,PartitionScheme=app3M_fat9M_16MB,PSRAM=opi}"
BUILD_DIR="${BUILD_DIR:-${ROOT_DIR}/.arduino-build/build-${MODE}}"
OUT_DIR="${OUT_DIR:-${ROOT_DIR}/.arduino-build/out-${MODE}}"
detect_port() {
local detected
detected="$(arduino-cli board list 2>/dev/null | awk '/\/dev\/tty(ACM|USB)/ {print $1; exit}')"
if [[ -n "${detected}" ]]; then
echo "${detected}"
return 0
fi
for candidate in /dev/ttyACM* /dev/ttyUSB*; do
if [[ -e "${candidate}" ]]; then
echo "${candidate}"
return 0
fi
done
return 1
}
case "${MODE}" in
hello) SKETCH_DIR="${DEMO_BASE}/examples/01_HelloWorld" ;;
widgets) SKETCH_DIR="${DEMO_BASE}/examples/05_LVGL_Widgets" ;;
@ -34,6 +52,13 @@ case "${MODE}" in
;;
esac
if [[ -z "${PORT}" ]]; then
if ! PORT="$(detect_port)"; then
echo "Failed to auto-detect ESP32 port. Set PORT=/dev/ttyACM0 ./burn.sh ${MODE}" >&2
exit 3
fi
fi
echo "== Mode: ${MODE}"
echo "== Sketch: ${SKETCH_DIR}"
echo "== Port: ${PORT}"
@ -41,17 +66,23 @@ echo "== FQBN: ${FQBN}"
mkdir -p "${BUILD_DIR}" "${OUT_DIR}"
arduino-cli compile \
--clean \
--fqbn "${FQBN}" \
--build-path "${BUILD_DIR}" \
--output-dir "${OUT_DIR}" \
--library "${DEMO_BASE}/libraries/GFX_Library_for_Arduino" \
--library "${DEMO_BASE}/libraries/SensorLib" \
--library "${DEMO_BASE}/libraries/XPowersLib" \
--library "${DEMO_BASE}/libraries/lvgl" \
--library "${DEMO_BASE}/libraries/Mylibrary" \
compile_args=(
--fqbn "${FQBN}"
--build-path "${BUILD_DIR}"
--output-dir "${OUT_DIR}"
--library "${DEMO_BASE}/libraries/GFX_Library_for_Arduino"
--library "${DEMO_BASE}/libraries/SensorLib"
--library "${DEMO_BASE}/libraries/XPowersLib"
--library "${DEMO_BASE}/libraries/lvgl"
--library "${DEMO_BASE}/libraries/Mylibrary"
"${SKETCH_DIR}"
)
echo "== Compile: fast incremental build"
if ! arduino-cli compile "${compile_args[@]}"; then
echo "== Compile: fast build failed, retrying clean build"
arduino-cli compile --clean "${compile_args[@]}"
fi
arduino-cli upload \
-p "${PORT}" \

View File

@ -27,6 +27,7 @@
#define SWIPE_THRESHOLD 48
#define TAP_CANCEL_THRESHOLD 18
#define MAX_SCAN_RESULTS 8
#define MAX_SAVED_WIFI_NETWORKS 8
#define WIFI_CONNECT_TIMEOUT_MS 12000
#define WIFI_RECONNECT_FAST_MS 10000
#define WIFI_RECONNECT_SLOW_MS 30000
@ -144,6 +145,8 @@ static uint32_t gLastHandledTouchSequence = 0;
static String gWifiSavedSsid;
static String gWifiSavedPassword;
static String gWifiSelectedSsid;
static String gKnownWifiSsids[MAX_SAVED_WIFI_NETWORKS];
static String gKnownWifiPasswords[MAX_SAVED_WIFI_NETWORKS];
static String gWifiStatusMessage = "No Wi-Fi configured";
static String gScanResults[MAX_SCAN_RESULTS];
static int gScanResultCount = 0;
@ -182,6 +185,10 @@ static void handleSwipe(SwipeDirection swipe);
static void loadPrefs();
static void saveWifiPrefs();
static void clearWifiPrefs();
static void clearSavedWifiList();
static int findKnownWifiIndex(const String &ssid);
static String savedPasswordForSsid(const String &ssid);
static void upsertKnownWifi(const String &ssid, const String &password);
static void saveServerPrefs();
static void saveAccountPrefs();
static void beginSavedWifi();
@ -334,10 +341,86 @@ static void showMessageAt(const String &text, lv_coord_t y) {
lv_obj_align(label, LV_ALIGN_TOP_MID, 0, y);
}
static void clearSavedWifiList() {
for (int i = 0; i < MAX_SAVED_WIFI_NETWORKS; ++i) {
gKnownWifiSsids[i] = "";
gKnownWifiPasswords[i] = "";
}
}
static int findKnownWifiIndex(const String &ssid) {
if (ssid.isEmpty()) {
return -1;
}
for (int i = 0; i < MAX_SAVED_WIFI_NETWORKS; ++i) {
if (gKnownWifiSsids[i] == ssid) {
return i;
}
}
return -1;
}
static String savedPasswordForSsid(const String &ssid) {
int index = findKnownWifiIndex(ssid);
return index >= 0 ? gKnownWifiPasswords[index] : "";
}
static String splitFixedWidth(const String &value, size_t chunkSize) {
if (chunkSize == 0 || value.isEmpty()) {
return value;
}
String out;
for (size_t i = 0; i < value.length(); ++i) {
if (i > 0 && (i % chunkSize) == 0) {
out += '\n';
}
out += value.charAt(i);
}
return out;
}
static void upsertKnownWifi(const String &ssid, const String &password) {
if (ssid.isEmpty()) {
return;
}
int existing = findKnownWifiIndex(ssid);
if (existing >= 0) {
gKnownWifiPasswords[existing] = password;
return;
}
for (int i = 0; i < MAX_SAVED_WIFI_NETWORKS; ++i) {
if (gKnownWifiSsids[i].isEmpty()) {
gKnownWifiSsids[i] = ssid;
gKnownWifiPasswords[i] = password;
return;
}
}
for (int i = 0; i < MAX_SAVED_WIFI_NETWORKS - 1; ++i) {
gKnownWifiSsids[i] = gKnownWifiSsids[i + 1];
gKnownWifiPasswords[i] = gKnownWifiPasswords[i + 1];
}
gKnownWifiSsids[MAX_SAVED_WIFI_NETWORKS - 1] = ssid;
gKnownWifiPasswords[MAX_SAVED_WIFI_NETWORKS - 1] = password;
}
static void loadPrefs() {
clearSavedWifiList();
gWifiSavedSsid = gPrefs.getString("wifi_ssid", "");
gWifiSavedPassword = gPrefs.getString("wifi_pass", "");
gWifiKnownGood = gPrefs.getBool("wifi_known_good", false);
for (int i = 0; i < MAX_SAVED_WIFI_NETWORKS; ++i) {
String ssidKey = String("wifi_ssid_") + i;
String passKey = String("wifi_pass_") + i;
gKnownWifiSsids[i] = gPrefs.getString(ssidKey.c_str(), "");
gKnownWifiPasswords[i] = gPrefs.getString(passKey.c_str(), "");
}
if (!gWifiSavedSsid.isEmpty() && findKnownWifiIndex(gWifiSavedSsid) < 0) {
upsertKnownWifi(gWifiSavedSsid, gWifiSavedPassword);
}
gSolanaRpcUrl = gPrefs.getString("solana_rpc", "https://api.devnet.solana.com");
gShineServerUrl = gPrefs.getString("shine_server", "https://shineup.me");
gLoginValue = gPrefs.getString("login", "");
@ -358,6 +441,17 @@ static void saveWifiPrefs() {
gPrefs.putString("wifi_ssid", gWifiSavedSsid);
gPrefs.putString("wifi_pass", gWifiSavedPassword);
gPrefs.putBool("wifi_known_good", gWifiKnownGood);
for (int i = 0; i < MAX_SAVED_WIFI_NETWORKS; ++i) {
String ssidKey = String("wifi_ssid_") + i;
String passKey = String("wifi_pass_") + i;
if (gKnownWifiSsids[i].isEmpty()) {
gPrefs.remove(ssidKey.c_str());
gPrefs.remove(passKey.c_str());
} else {
gPrefs.putString(ssidKey.c_str(), gKnownWifiSsids[i]);
gPrefs.putString(passKey.c_str(), gKnownWifiPasswords[i]);
}
}
}
static void saveServerPrefs() {
@ -382,6 +476,7 @@ static void clearWifiPrefs() {
gWifiSavedSsid = "";
gWifiSavedPassword = "";
gWifiSelectedSsid = "";
clearSavedWifiList();
gWifiKnownGood = false;
gWifiReconnectEnabled = false;
gWifiDisconnectedSinceMs = 0;
@ -418,7 +513,13 @@ static String wifiSavedLabel() {
if (gWifiSavedSsid.isEmpty()) {
return "Saved: none";
}
return String("Saved: ") + gWifiSavedSsid;
int knownCount = 0;
for (int i = 0; i < MAX_SAVED_WIFI_NETWORKS; ++i) {
if (!gKnownWifiSsids[i].isEmpty()) {
knownCount++;
}
}
return String("Saved: ") + gWifiSavedSsid + " (" + knownCount + ")";
}
static String wifiHomeSummary() {
@ -652,6 +753,7 @@ static bool connectWifiNow(const String &ssid, const String &password) {
if (WiFi.status() == WL_CONNECTED) {
gWifiSavedSsid = ssid;
gWifiSavedPassword = password;
upsertKnownWifi(ssid, password);
gWifiKnownGood = true;
gWifiReconnectEnabled = true;
gWifiDisconnectedSinceMs = 0;
@ -830,7 +932,7 @@ static void networkSelectCb(lv_event_t *event) {
SCREEN_WIFI,
"ENTER PASSWORD",
String("SSID: ") + gWifiSelectedSsid,
"",
savedPasswordForSsid(gWifiSelectedSsid),
true);
}
@ -1276,12 +1378,13 @@ static void drawSecretShowScreen() {
if (gSecretConfigured) {
showMessageAt("Current secret in base58:", 56);
lv_obj_t *label = lv_label_create(gRoot);
lv_label_set_text(label, gSecretBase58.c_str());
String secretLines = splitFixedWidth(gSecretBase58, 10);
lv_label_set_text(label, secretLines.c_str());
lv_obj_set_width(label, 420);
lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);
lv_obj_set_style_text_font(label, &lv_font_montserrat_16, 0);
lv_obj_set_style_text_font(label, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(label, lv_color_hex(0xD9E1EA), 0);
lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 112);
lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 102);
} else {
showMessageAt("Secret not set", 96);
}

View File

@ -1,2 +1,2 @@
client.version=1.2.150
server.version=1.2.142
client.version=1.2.151
server.version=1.2.143