From 2b98bdb4a43068f30fff7494e4e97863c6683adb Mon Sep 17 00:00:00 2001 From: Lsong Date: Tue, 29 Sep 2026 20:22:57 +0800 Subject: [PATCH 01/21] Define Wi-Fi provisioning states --- firmware/src/network/wifi_manager.h | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/firmware/src/network/wifi_manager.h b/firmware/src/network/wifi_manager.h index 16043e4..97151ee 100644 --- a/firmware/src/network/wifi_manager.h +++ b/firmware/src/network/wifi_manager.h @@ -3,7 +3,14 @@ #include +enum class wifi_state_e { + connecting, + online, + setup, +}; + typedef struct { + wifi_state_e state; bool connected; char ssid[33]; char saved_ssid[33]; @@ -18,9 +25,21 @@ enum class wifi_connect_result_e { }; bool wifi_manager_init(); -wifi_connect_result_e wifi_manager_connect(const char *ssid, const char *password, uint32_t timeout_ms); +void wifi_manager_process(); + +bool wifi_manager_start_setup(); +void wifi_manager_schedule_setup_stop(uint32_t delay_ms); + +wifi_connect_result_e wifi_manager_connect( + const char *ssid, + const char *password, + uint32_t timeout_ms); + bool wifi_manager_forget(); wifi_status_t wifi_manager_status(); +wifi_state_e wifi_manager_state(); +const char *wifi_manager_state_name(); +bool wifi_manager_setup_active(); const char *wifi_manager_ap_ssid(); const char *wifi_manager_hostname(); From d055f4e74de5ac3875c8b50ab35ef78e0732c7e0 Mon Sep 17 00:00:00 2001 From: Lsong Date: Tue, 29 Sep 2026 20:23:00 +0800 Subject: [PATCH 02/21] Implement STA-first Wi-Fi provisioning lifecycle --- firmware/src/network/wifi_manager.cpp | 415 +++++++++++++++++++++----- 1 file changed, 333 insertions(+), 82 deletions(-) diff --git a/firmware/src/network/wifi_manager.cpp b/firmware/src/network/wifi_manager.cpp index 300c3e6..a49add7 100644 --- a/firmware/src/network/wifi_manager.cpp +++ b/firmware/src/network/wifi_manager.cpp @@ -4,17 +4,29 @@ #if WIFI_ENABLED +#include #include #include #include +#include #include namespace { Preferences preferences; +DNSServer dns_server; + char ap_ssid[33] = {}; char hostname[33] = {}; char saved_ssid[33] = {}; + +std::atomic current_state{wifi_state_e::connecting}; +std::atomic setup_start_requested{false}; +std::atomic setup_stop_pending{false}; +std::atomic setup_stop_at{0}; + bool initialized = false; +bool mdns_started = false; +bool dns_started = false; constexpr char CREDENTIALS_KEY[] = "network"; constexpr uint8_t CREDENTIALS_VERSION = 1; @@ -48,11 +60,12 @@ bool read_saved_network(saved_network_t *network) preferences.getBytes(CREDENTIALS_KEY, network, sizeof(*network)) == sizeof(*network) && valid_network(*network); } + if (!loaded && preferences.isKey("ssid")) { - // Read credentials saved by earlier firmware releases. const String ssid = preferences.getString("ssid"); const String password = preferences.getString("password"); + if (ssid.length() > 0 && ssid.length() <= 32 && password.length() <= 64) { network->version = CREDENTIALS_VERSION; @@ -85,48 +98,59 @@ void make_names() suffix); } -void connect_saved_network() +bool wait_for_connection(uint32_t timeout_ms) { - saved_network_t network = {}; - if (!read_saved_network(&network)) + const uint32_t started = millis(); + + while (WiFi.status() != WL_CONNECTED) + { + if (static_cast(millis() - started) >= timeout_ms) + return false; + + vTaskDelay(pdMS_TO_TICKS(100)); + } + + return true; +} + +void start_mdns() +{ + if (mdns_started || WiFi.status() != WL_CONNECTED) + return; + + if (!MDNS.begin(hostname)) + { + Serial.println("Failed to start mDNS"); return; + } + MDNS.addService("http", "tcp", WIFI_HTTP_PORT); + MDNS.addService("miniprinter", "tcp", WIFI_PRINT_PORT); + mdns_started = true; +} + +bool begin_saved_network(const saved_network_t &network) +{ strlcpy(saved_ssid, network.ssid, sizeof(saved_ssid)); Serial.printf("Connecting to saved Wi-Fi: %s\n", network.ssid); WiFi.begin(network.ssid, network.password); + return true; } -void on_wifi_event(arduino_event_id_t event, arduino_event_info_t info) +void restore_saved_network() { - switch (event) - { - case ARDUINO_EVENT_WIFI_STA_GOT_IP: - Serial.printf("Wi-Fi STA ready: IP=%s, heap=%u, largest=%u\n", - WiFi.localIP().toString().c_str(), - ESP.getFreeHeap(), ESP.getMaxAllocHeap()); - break; - case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: - Serial.printf("Wi-Fi STA disconnected: reason=%u, heap=%u\n", - info.wifi_sta_disconnected.reason, ESP.getFreeHeap()); - break; - case ARDUINO_EVENT_WIFI_AP_STACONNECTED: - Serial.println("Wi-Fi AP client connected"); - break; - case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: - Serial.println("Wi-Fi AP client received an IP address"); - break; - case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: - Serial.println("Wi-Fi AP client disconnected"); - break; - default: - break; - } + saved_network_t network = {}; + if (!read_saved_network(&network)) + return; + + begin_saved_network(network); } bool save_credentials(const char *ssid, const char *password) { saved_network_t network = {}; network.version = CREDENTIALS_VERSION; + if (strlcpy(network.ssid, ssid, sizeof(network.ssid)) >= sizeof(network.ssid) || strlcpy(network.password, password == nullptr ? "" : password, sizeof(network.password)) >= sizeof(network.password)) return false; @@ -134,18 +158,141 @@ bool save_credentials(const char *ssid, const char *password) if (!preferences.begin(WIFI_PREFS_NAMESPACE, false)) return false; - const bool saved = preferences.putBytes(CREDENTIALS_KEY, &network, sizeof(network)) == sizeof(network); + const bool saved = + preferences.putBytes(CREDENTIALS_KEY, &network, sizeof(network)) == sizeof(network); + preferences.end(); + if (saved) strlcpy(saved_ssid, network.ssid, sizeof(saved_ssid)); + return saved; } -void restore_saved_network() +void stop_setup_now() { - saved_network_t network = {}; - if (read_saved_network(&network)) - WiFi.begin(network.ssid, network.password); + if (current_state.load() != wifi_state_e::setup) + return; + + setup_stop_pending.store(false); + + if (dns_started) + { + dns_server.stop(); + dns_started = false; + } + + WiFi.softAPdisconnect(true); + WiFi.mode(WIFI_STA); + + if (WiFi.status() == WL_CONNECTED) + { + current_state.store(wifi_state_e::online); + start_mdns(); + Serial.printf("Wi-Fi setup stopped; STA online at %s\n", + WiFi.localIP().toString().c_str()); + return; + } + + current_state.store(wifi_state_e::connecting); + restore_saved_network(); + Serial.println("Wi-Fi setup stopped; retrying saved network"); +} + +bool start_setup_now() +{ + if (current_state.load() == wifi_state_e::setup) + return true; + + if (!WiFi.mode(WIFI_AP_STA)) + { + Serial.println("Failed to enter Wi-Fi AP+STA setup mode"); + return false; + } + + bool ap_started = false; + + if (strlen(WIFI_AP_PASSWORD) >= 8) + ap_started = WiFi.softAP(ap_ssid, WIFI_AP_PASSWORD); + else + ap_started = WiFi.softAP(ap_ssid); + + if (!ap_started) + { + Serial.println("Failed to start Wi-Fi setup AP"); + return false; + } + + dns_server.setErrorReplyCode(DNSReplyCode::NoError); + dns_started = dns_server.start(53, "*", WiFi.softAPIP()); + + if (!dns_started) + Serial.println("Failed to start captive DNS"); + + current_state.store(wifi_state_e::setup); + + /* + * If credentials already exist, setup mode is a temporary recovery window. + * A first-boot device with no credentials stays in setup until configured. + */ + if (saved_ssid[0] != '\0') + { + setup_stop_at.store(millis() + WIFI_SETUP_TIMEOUT_MS); + setup_stop_pending.store(true); + } + else + { + setup_stop_pending.store(false); + } + + Serial.printf( + "Wi-Fi setup ready: %s, IP=%s\n", + ap_ssid, + WiFi.softAPIP().toString().c_str()); + + return true; +} + +void on_wifi_event(arduino_event_id_t event, arduino_event_info_t info) +{ + switch (event) + { + case ARDUINO_EVENT_WIFI_STA_GOT_IP: + if (current_state.load() != wifi_state_e::setup) + current_state.store(wifi_state_e::online); + + Serial.printf( + "Wi-Fi STA ready: IP=%s, heap=%u, largest=%u\n", + WiFi.localIP().toString().c_str(), + ESP.getFreeHeap(), + ESP.getMaxAllocHeap()); + break; + + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: + if (current_state.load() == wifi_state_e::online) + current_state.store(wifi_state_e::connecting); + + Serial.printf( + "Wi-Fi STA disconnected: reason=%u, heap=%u\n", + info.wifi_sta_disconnected.reason, + ESP.getFreeHeap()); + break; + + case ARDUINO_EVENT_WIFI_AP_STACONNECTED: + Serial.println("Wi-Fi setup client connected"); + break; + + case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: + Serial.println("Wi-Fi setup client received an IP address"); + break; + + case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: + Serial.println("Wi-Fi setup client disconnected"); + break; + + default: + break; + } } } // namespace @@ -159,18 +306,16 @@ bool wifi_manager_init() WiFi.onEvent(on_wifi_event); WiFi.setHostname(hostname); - if (!WiFi.mode(WIFI_AP_STA)) + if (!WiFi.mode(WIFI_STA)) { - Serial.println("Failed to initialize Wi-Fi AP+STA mode"); + Serial.println("Failed to initialize Wi-Fi STA mode"); return false; } - // ESP32 Wi-Fi and Bluetooth share the 2.4 GHz radio. The coexistence - // controller requires Wi-Fi modem sleep; WIFI_PS_NONE causes an abort - // as soon as both stacks are enabled. - // Arduino's WiFi.setSleep() returns false when the requested mode is - // already selected, which is the default on ESP32. Check the driver - // operation itself so an unchanged setting is not treated as a failure. + /* + * ESP32 Wi-Fi and Bluetooth share the 2.4 GHz radio. BLE coexistence + * requires Wi-Fi modem sleep; WIFI_PS_NONE aborts when both are enabled. + */ const esp_err_t sleep_result = esp_wifi_set_ps(WIFI_PS_MIN_MODEM); if (sleep_result != ESP_OK) { @@ -182,81 +327,128 @@ bool wifi_manager_init() WiFi.setAutoReconnect(true); - // Preferences is the source of truth. Clear the Arduino Wi-Fi driver's - // remembered STA credentials so a failed setup attempt cannot survive a - // reboot independently of our validated credentials. + /* + * Preferences is the source of truth. Do not allow Arduino's Wi-Fi driver + * to reconnect using credentials outside our validated record. + */ WiFi.disconnect(false, true); + vTaskDelay(pdMS_TO_TICKS(50)); - bool ap_started = false; - if (strlen(WIFI_AP_PASSWORD) >= 8) - ap_started = WiFi.softAP(ap_ssid, WIFI_AP_PASSWORD); - else - ap_started = WiFi.softAP(ap_ssid); + initialized = true; - if (!ap_started) - { - Serial.println("Failed to start Wi-Fi AP"); - return false; - } + saved_network_t network = {}; + if (!read_saved_network(&network)) + return start_setup_now(); - if (MDNS.begin(hostname)) - { - MDNS.addService("http", "tcp", WIFI_HTTP_PORT); - MDNS.addService("miniprinter", "tcp", WIFI_PRINT_PORT); - } - else + begin_saved_network(network); + current_state.store(wifi_state_e::connecting); + + if (wait_for_connection(WIFI_STARTUP_CONNECT_TIMEOUT_MS)) { - Serial.println("Failed to start mDNS"); + current_state.store(wifi_state_e::online); + start_mdns(); + + Serial.printf( + "Wi-Fi STA online: %s, IP=%s\n", + WiFi.SSID().c_str(), + WiFi.localIP().toString().c_str()); + + return true; } - Serial.printf( - "Wi-Fi AP ready: %s, IP=%s, host=%s.local\n", - ap_ssid, - WiFi.softAPIP().toString().c_str(), - hostname); + Serial.println("Saved Wi-Fi unavailable; entering setup mode"); + WiFi.disconnect(false, true); + return start_setup_now(); +} - connect_saved_network(); +void wifi_manager_process() +{ + if (!initialized) + return; - initialized = true; + if (setup_start_requested.exchange(false)) + start_setup_now(); + + if (current_state.load() != wifi_state_e::setup) + return; + + if (dns_started) + dns_server.processNextRequest(); + + if (!setup_stop_pending.load()) + return; + + const uint32_t deadline = setup_stop_at.load(); + if (static_cast(millis() - deadline) >= 0) + stop_setup_now(); +} + +bool wifi_manager_start_setup() +{ + if (!initialized) + return false; + + if (current_state.load() == wifi_state_e::setup) + return true; + + setup_start_requested.store(true); return true; } +void wifi_manager_schedule_setup_stop(uint32_t delay_ms) +{ + if (current_state.load() != wifi_state_e::setup) + return; + + setup_stop_at.store(millis() + delay_ms); + setup_stop_pending.store(true); +} + wifi_connect_result_e wifi_manager_connect( const char *ssid, const char *password, uint32_t timeout_ms) { - if (!initialized || ssid == nullptr || ssid[0] == '\0') + if (!initialized || + current_state.load() != wifi_state_e::setup || + ssid == nullptr || + ssid[0] == '\0') + { return wifi_connect_result_e::connection_failed; + } + + saved_network_t previous = {}; + const bool had_previous = read_saved_network(&previous); - // Force a real transition even if STA is already connected; otherwise - // WL_CONNECTED can briefly describe the previous network and make a new - // credential set look successful. WiFi.disconnect(false, true); vTaskDelay(pdMS_TO_TICKS(50)); WiFi.begin(ssid, password == nullptr ? "" : password); - const uint32_t started = millis(); - while (WiFi.status() != WL_CONNECTED) + if (!wait_for_connection(timeout_ms)) { - if (static_cast(millis() - started) >= timeout_ms) - { - WiFi.disconnect(false, true); - restore_saved_network(); - return wifi_connect_result_e::connection_failed; - } + WiFi.disconnect(false, true); - vTaskDelay(pdMS_TO_TICKS(100)); + if (had_previous) + begin_saved_network(previous); + + return wifi_connect_result_e::connection_failed; } if (!save_credentials(ssid, password)) { Serial.println("Wi-Fi connected, but credentials could not be saved"); + + WiFi.disconnect(false, true); + if (had_previous) + begin_saved_network(previous); + return wifi_connect_result_e::save_failed; } + start_mdns(); + Serial.printf( - "Wi-Fi STA connected: %s, IP=%s\n", + "Wi-Fi STA connected during setup: %s, IP=%s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); @@ -265,15 +457,20 @@ wifi_connect_result_e wifi_manager_connect( bool wifi_manager_forget() { + if (!initialized || current_state.load() != wifi_state_e::setup) + return false; + if (!preferences.begin(WIFI_PREFS_NAMESPACE, false)) return false; const bool cleared = preferences.clear(); preferences.end(); + if (!cleared) return false; saved_ssid[0] = '\0'; + setup_stop_pending.store(false); WiFi.disconnect(false, true); return true; } @@ -281,6 +478,7 @@ bool wifi_manager_forget() wifi_status_t wifi_manager_status() { wifi_status_t status = {}; + status.state = current_state.load(); strlcpy(status.saved_ssid, saved_ssid, sizeof(status.saved_ssid)); status.connected = WiFi.status() == WL_CONNECTED; @@ -294,6 +492,31 @@ wifi_status_t wifi_manager_status() return status; } +wifi_state_e wifi_manager_state() +{ + return current_state.load(); +} + +const char *wifi_manager_state_name() +{ + switch (current_state.load()) + { + case wifi_state_e::connecting: + return "connecting"; + case wifi_state_e::online: + return "online"; + case wifi_state_e::setup: + return "setup"; + default: + return "unknown"; + } +} + +bool wifi_manager_setup_active() +{ + return current_state.load() == wifi_state_e::setup; +} + const char *wifi_manager_ap_ssid() { return ap_ssid; @@ -311,6 +534,19 @@ bool wifi_manager_init() return false; } +void wifi_manager_process() +{ +} + +bool wifi_manager_start_setup() +{ + return false; +} + +void wifi_manager_schedule_setup_stop(uint32_t) +{ +} + wifi_connect_result_e wifi_manager_connect(const char *, const char *, uint32_t) { return wifi_connect_result_e::connection_failed; @@ -326,6 +562,21 @@ wifi_status_t wifi_manager_status() return {}; } +wifi_state_e wifi_manager_state() +{ + return wifi_state_e::connecting; +} + +const char *wifi_manager_state_name() +{ + return "disabled"; +} + +bool wifi_manager_setup_active() +{ + return false; +} + const char *wifi_manager_ap_ssid() { return ""; From 98ddc8be60d5e84ec8053793599158df0154d065 Mon Sep 17 00:00:00 2001 From: Lsong Date: Tue, 29 Sep 2026 20:23:17 +0800 Subject: [PATCH 03/21] Configure temporary Wi-Fi setup mode From 6f84ab64538981b640c3e7b74f8c35e005e97955 Mon Sep 17 00:00:00 2001 From: Lsong Date: Tue, 29 Sep 2026 20:23:19 +0800 Subject: [PATCH 04/21] Use kbutton.c for button gestures --- firmware/platformio.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/firmware/platformio.ini b/firmware/platformio.ini index 9604802..1861a8b 100644 --- a/firmware/platformio.ini +++ b/firmware/platformio.ini @@ -8,6 +8,8 @@ framework = arduino monitor_speed = 115200 build_flags = -D BLE_ENABLED=1 build_src_filter = +<*> +lib_deps = + https://github.com/lsongdev/kbutton.c.git#f0665ecca96674aa7ee033407734f0f76b386a40 ; BLE + Wi-Fi no longer fits the 1.25 MiB default app slot. ; min_spiffs keeps OTA support while giving each app partition more room. @@ -20,4 +22,3 @@ test_build_src = yes build_flags = -Itest/mocks build_src_filter = + - + From 483659482fbc127cd6ae4d6f62dbf9535d3a1f8b Mon Sep 17 00:00:00 2001 From: Lsong Date: Tue, 29 Sep 2026 20:23:48 +0800 Subject: [PATCH 05/21] Use KButton gestures for Wi-Fi setup recovery --- firmware/src/em_task.cpp | 446 +++++++++++++++++++++------------------ 1 file changed, 246 insertions(+), 200 deletions(-) diff --git a/firmware/src/em_task.cpp b/firmware/src/em_task.cpp index c053367..dde4d1c 100644 --- a/firmware/src/em_task.cpp +++ b/firmware/src/em_task.cpp @@ -1,213 +1,259 @@ -#include "em_task.h" - -#include "core/print_service.h" -#include "core/test_page.h" +#include "em_task.h" + +#include "core/print_service.h" +#include "core/test_page.h" #include "em_config.h" -#include "em_device.h" -#include "hal/em_adc.h" -#include "hal/em_button.h" -#include "hal/em_indicator.h" -#include "hal/em_paper.h" -#include "hal/em_printer.h" -#include "network/web_server.h" -#include "network/wifi_manager.h" +#include "em_device.h" +#include "hal/em_adc.h" +#include "hal/em_indicator.h" +#include "hal/em_paper.h" +#include "hal/em_printer.h" +#include "network/web_server.h" +#include "network/wifi_manager.h" #if BLE_ENABLED -#include "transport/ble_transport.h" +#include "transport/ble_transport.h" #endif -#include "transport/tcp_transport.h" - -#include -#include - -Button_t Button; -static std::atomic test_page_task_running{false}; - -void read_paper() -{ - set_device_paper_status(paper_present() ? PAPER_STATUS_NORMAL : PAPER_STATUS_LACK); -} - -void read_temperature() -{ - float celsius = 0.0f; - if (!adc_read_temperature_c(&celsius)) - { - set_device_temperature(255); - return; - } - const int rounded = static_cast(lroundf(celsius)); - set_device_temperature(static_cast(rounded < 0 ? 0 : rounded > 254 ? 254 : rounded)); -} - -void read_all_sensors() -{ - int adc_mv = 0; - if (adc_read_battery_mv(&adc_mv)) - { - const int battery_mv = adc_mv * 2; - const int percent = (battery_mv - 3300) * 100 / 900; - set_device_battery(static_cast(percent < 0 ? 0 : percent > 100 ? 100 : percent)); - } - else - set_device_battery(0); - read_temperature(); - read_paper(); -} - -void test_page_task(void *) -{ - if (!print_test_page()) - { - Serial.println("Test page could not be printed"); - indicator_beep(INDICATOR_WARN); - indicator_led(INDICATOR_WARN); - } - - test_page_task_running.store(false); - vTaskDelete(nullptr); -} - -void Btn_Double_CallBack(void *) -{ - if (test_page_task_running.exchange(true)) - return; - - if (xTaskCreate(test_page_task, "TestPage", 4096, nullptr, 1, nullptr) != pdPASS) - { - test_page_task_running.store(false); - indicator_beep(INDICATOR_WARN); - indicator_led(INDICATOR_WARN); - } -} - -void Btn_Long_CallBack(void *) -{ - if (!print_service_feed_start(100)) - { - indicator_beep(INDICATOR_WARN); - indicator_led(INDICATOR_WARN); - } -} - -void Btn_Long_Release_CallBack(void *) -{ - print_service_feed_stop(100); -} - -void init_key() -{ - Button_Create(&Button, PIN_KEY, LOW); - - Button_Attach(&Button, BUTTON_DOUBLE, Btn_Double_CallBack); - Button_Attach(&Button, BUTTON_LONG, Btn_Long_CallBack); - Button_Attach(&Button, BUTTON_LONG_RELEASE, Btn_Long_Release_CallBack); -} - -void task_report(void *) -{ - TickType_t last_sensor = xTaskGetTickCount(); +#include "transport/tcp_transport.h" + +#include +#include +#include + +namespace { +KButton button(PIN_KEY); +std::atomic test_page_task_running{false}; + +void read_paper() +{ + set_device_paper_status( + paper_present() ? PAPER_STATUS_NORMAL : PAPER_STATUS_LACK); +} + +void read_temperature() +{ + float celsius = 0.0f; + if (!adc_read_temperature_c(&celsius)) + { + set_device_temperature(255); + return; + } + + const int rounded = static_cast(lroundf(celsius)); + set_device_temperature(static_cast( + rounded < 0 ? 0 : rounded > 254 ? 254 : rounded)); +} + +void read_all_sensors() +{ + int adc_mv = 0; + + if (adc_read_battery_mv(&adc_mv)) + { + const int battery_mv = adc_mv * 2; + const int percent = (battery_mv - 3300) * 100 / 900; + set_device_battery(static_cast( + percent < 0 ? 0 : percent > 100 ? 100 : percent)); + } + else + { + set_device_battery(0); + } + + read_temperature(); + read_paper(); +} + +void test_page_task(void *) +{ + if (!print_test_page()) + { + Serial.println("Test page could not be printed"); + indicator_beep(INDICATOR_WARN); + indicator_led(INDICATOR_WARN); + } + + test_page_task_running.store(false); + vTaskDelete(nullptr); +} + +void start_test_page() +{ + if (test_page_task_running.exchange(true)) + return; + + if (xTaskCreate( + test_page_task, + "TestPage", + 4096, + nullptr, + 1, + nullptr) != pdPASS) + { + test_page_task_running.store(false); + indicator_beep(INDICATOR_WARN); + indicator_led(INDICATOR_WARN); + } +} + +void button_click(uint8_t count) +{ + if (count == 2) + { + start_test_page(); + return; + } + + if (count == 3) + { + if (!wifi_manager_start_setup()) + { + indicator_beep(INDICATOR_WARN); + indicator_led(INDICATOR_WARN); + return; + } + + Serial.println("Wi-Fi setup requested by button"); + indicator_beep(INDICATOR_CONNECT); + indicator_led(INDICATOR_CONNECT); + } +} + +void button_long_press() +{ + if (!print_service_feed_start(100)) + { + indicator_beep(INDICATOR_WARN); + indicator_led(INDICATOR_WARN); + } +} + +void button_long_release() +{ + print_service_feed_stop(100); +} + +void init_key() +{ + // Preserve the original printer button timings. + button.setDebounceMs(40); + button.setClickMs(300); + button.setLongPressMs(1000); + + button.onClicks(button_click); + button.onLongPress(button_long_press); + button.onLongRelease(button_long_release); + button.begin(); +} + +void task_report(void *) +{ + TickType_t last_sensor = xTaskGetTickCount(); #if BLE_ENABLED - TickType_t last_status = 0; + TickType_t last_status = 0; #endif - - for (;;) - { - const TickType_t now = xTaskGetTickCount(); - - if (now - last_sensor >= pdMS_TO_TICKS(10000)) - { - const device_state_t state = get_device_state(); - - // The printer task owns ADC temperature sampling while printing. - if (state.printer_state != PRINTER_STATUS_PRINTING) - read_all_sensors(); - else - read_paper(); - - last_sensor = now; - } - - if (paper_take_change()) - { - read_paper(); - const device_state_t state = get_device_state(); - - if (state.paper_state == PAPER_STATUS_LACK && - state.printer_state == PRINTER_STATUS_FEEDING) - { - print_service_feed_stop(0); - } - + + for (;;) + { + const TickType_t now = xTaskGetTickCount(); + + wifi_manager_process(); + + if (now - last_sensor >= pdMS_TO_TICKS(10000)) + { + const device_state_t state = get_device_state(); + + // The printer task owns ADC temperature sampling while printing. + if (state.printer_state != PRINTER_STATUS_PRINTING) + read_all_sensors(); + else + read_paper(); + + last_sensor = now; + } + + if (paper_take_change()) + { + read_paper(); + const device_state_t state = get_device_state(); + + if (state.paper_state == PAPER_STATUS_LACK && + state.printer_state == PRINTER_STATUS_FEEDING) + { + print_service_feed_stop(0); + } + #if BLE_ENABLED - ble_transport_report_status(); + ble_transport_report_status(); #endif - } - + } + #if BLE_ENABLED - if (now - last_status >= pdMS_TO_TICKS(1000)) - { - ble_transport_report_status(); - last_status = now; - } + if (now - last_status >= pdMS_TO_TICKS(1000)) + { + ble_transport_report_status(); + last_status = now; + } #endif - - vTaskDelay(pdMS_TO_TICKS(50)); - } -} - -void task_button(void *) -{ - for (;;) - { - Button_Process(); - vTaskDelay(pdMS_TO_TICKS(20)); - } -} - -void init_task() -{ - Serial.begin(115200); - Serial.println("mini-printer firmware"); - - init_device_state(); - paper_init(); - indicator_init(); - adc_init(); - read_all_sensors(); - printer_init(); - - if (!print_service_init()) - { - Serial.println("failed to initialize print service"); - return; - } - - init_key(); + + vTaskDelay(pdMS_TO_TICKS(50)); + } +} + +void task_button(void *) +{ + for (;;) + { + button.tick(); + vTaskDelay(pdMS_TO_TICKS(20)); + } +} +} // namespace + +void init_task() +{ + Serial.begin(115200); + Serial.println("mini-printer firmware"); + + init_device_state(); + paper_init(); + indicator_init(); + adc_init(); + read_all_sensors(); + printer_init(); + + if (!print_service_init()) + { + Serial.println("failed to initialize print service"); + return; + } + + init_key(); + #if BLE_ENABLED - init_ble_transport(); + init_ble_transport(); #else Serial.println("BLE disabled at build time"); #endif - - if (wifi_manager_init()) - { - init_tcp_transport(); - init_web_server(); - } - - xTaskCreate( - task_report, - "Report", - 4096, - nullptr, - 1, - nullptr); - - xTaskCreate( - task_button, - "Button", - 4096, - nullptr, - 1, - nullptr); -} + + if (wifi_manager_init()) + { + init_tcp_transport(); + init_web_server(); + } + + xTaskCreate( + task_report, + "Report", + 4096, + nullptr, + 1, + nullptr); + + xTaskCreate( + task_button, + "Button", + 4096, + nullptr, + 1, + nullptr); +} From a95b1e7617a45f66d3a3a5fc344dff6a75a1d8b6 Mon Sep 17 00:00:00 2001 From: Lsong Date: Tue, 29 Sep 2026 20:23:58 +0800 Subject: [PATCH 06/21] Disable raw TCP printing during Wi-Fi setup --- firmware/src/transport/tcp_transport.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/firmware/src/transport/tcp_transport.cpp b/firmware/src/transport/tcp_transport.cpp index de2270e..e833662 100644 --- a/firmware/src/transport/tcp_transport.cpp +++ b/firmware/src/transport/tcp_transport.cpp @@ -5,6 +5,7 @@ #if WIFI_ENABLED #include "core/print_service.h" +#include "network/wifi_manager.h" #include "protocol/print_protocol.h" #include "transport_common.h" @@ -83,6 +84,12 @@ void tcp_task(void *) continue; } + if (wifi_manager_setup_active()) + { + client.stop(); + continue; + } + client.setNoDelay(true); tcp_context_t context = { @@ -100,7 +107,9 @@ void tcp_task(void *) uint8_t buffer[128]; uint32_t last_activity = millis(); - while (client.connected() && accepted) + while (client.connected() && + accepted && + !wifi_manager_setup_active()) { const int available = client.available(); if (available <= 0) From 974b5baa239ff1dedbbc791bfb33e3ae1ced0b14 Mon Sep 17 00:00:00 2001 From: Lsong Date: Tue, 29 Sep 2026 20:24:45 +0800 Subject: [PATCH 07/21] Scope Wi-Fi setup APIs and printing by mode --- firmware/src/network/web_server.cpp | 74 +++++++++++++++++++++++++---- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/firmware/src/network/web_server.cpp b/firmware/src/network/web_server.cpp index 7708925..c358330 100644 --- a/firmware/src/network/web_server.cpp +++ b/firmware/src/network/web_server.cpp @@ -82,6 +82,7 @@ small{display:block;margin-top:12px;color:#5b6570}