diff --git a/README.md b/README.md index ed30c24..b1be6c8 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ client source code. | Path | Contents | | --- | --- | -| [`firmware/`](firmware/) | PlatformIO firmware: hardware drivers, print queue, protocol parser, BLE, TCP, Wi-Fi AP+STA, and embedded Web UI | +| [`firmware/`](firmware/) | PlatformIO firmware: hardware drivers, print queue, protocol parser, BLE, TCP, STA Wi-Fi with temporary setup AP, and embedded Web UI | | [`python/`](python/) | BLE test-page client and protocol tests | | [`docs/`](docs/) | Hardware notes, architecture, and [print protocol](docs/protocol.md) | | [`android/`](android/) · [`ios/`](ios/) · [`wxapp/`](wxapp/) | Client source code for other platforms | @@ -49,9 +49,12 @@ python3 -m unittest discover -s python -p 'test_*.py' Print data uses the project's compact `MP` frame protocol. The firmware accepts the same protocol over BLE, raw TCP port `9100`, and HTTP `POST /api/print`. -Wi-Fi runs as AP+STA. Connect to `Mini-Printer-XXXX` and open -`http://192.168.4.1/` for the offline setup and print page. The page can join another Wi-Fi network while keeping -the setup AP available, and can rasterize an image in the browser for printing. +Wi-Fi normally runs as STA only. On first boot, after a saved-network startup +failure, or after a triple click on the printer button, the temporary +`Mini-Printer-XXXX` setup AP becomes available. Open `http://192.168.4.1/` +(or use the captive portal) to select a network. The AP closes after successful +provisioning. The embedded page can rasterize images in the browser and print +through either the temporary setup AP or normal STA operation. The TCP endpoint is not IPP or AirPrint. See [`docs/protocol.md`](docs/protocol.md) for frame fields and status codes, and [`firmware/README.md`](firmware/README.md) diff --git a/docs/hal.md b/docs/hal.md index 564a019..83f8787 100644 --- a/docs/hal.md +++ b/docs/hal.md @@ -6,14 +6,13 @@ The pin map in `src/em_config.h` targets the original V4 board. Each file in | Driver | Pins and responsibility | | --- | --- | | `em_adc` | GPIO 4 enables ADC, GPIO 34 reads the battery divider, GPIO 36 reads the NTC divider | -| `em_button` | GPIO 5 input pull-up, debounce, single/double/long press events | | `em_indicator` | GPIO 18 LED; the V4 buzzer pin is shared, so buzzer mode requires a different board pin | | `em_motor` | GPIO 23/22/21/19 stepper coil phases | | `em_paper` | GPIO 35 paper detector and change interrupt; low means paper present | | `em_spi` | Print-head HSPI on GPIO 15 clock, GPIO 16 MISO, and GPIO 13 data; transfers one 48-byte raster line through the full-duplex SPI path. GPIO 12 is the latch and must not be selected as SPI MISO. | | `em_printer` | GPIO 12 latch, six strobe pins, GPIO 17 head boost gate | -`em_task` samples sensors and publishes status. `print_service` serializes all +`em_task` samples sensors and publishes status. GPIO 5 is configured as an input pull-up by the external `kbutton.c` Arduino adapter; button debounce and gesture recognition live in that reusable library rather than the hardware layer. `print_service` serializes all printing and feeding through one FreeRTOS task. Manual feed steps are issued by that task; stopping a feed needs no cross-task timer callback. The motor driver never changes the head boost gate. diff --git a/firmware/README.md b/firmware/README.md index 4ff376f..c7bd1ac 100644 --- a/firmware/README.md +++ b/firmware/README.md @@ -14,10 +14,9 @@ TCP :9100 ─────┼── MP protocol ── print service ── print HTTP /api/print┘ │ FreeRTOS queue -Wi-Fi AP+STA ── wifi_manager - ├── mDNS - ├── TCP :9100 - └── HTTP :80 +Wi-Fi STA ────── wifi_manager ── HTTP :80 / TCP :9100 / mDNS + │ + └── temporary AP+STA setup mode ``` - `hal/` owns GPIO, SPI, motor, sensors, and print-head timing. @@ -40,7 +39,7 @@ pio test -e native ## BLE -The default `esp32dev` build enables BLE, Wi-Fi AP+STA, HTTP, and TCP together. +The default `esp32dev` build enables BLE, Wi-Fi, HTTP, and TCP together. Normal operation uses STA only; AP+STA is enabled temporarily for provisioning and recovery. It starts a BLE-only controller and releases unused Bluetooth Classic controller memory before initializing the BLE stack. @@ -60,10 +59,22 @@ characteristic returns/notifications status frames. ## Wi-Fi -Wi-Fi runs in `WIFI_AP_STA` mode. The setup/recovery SoftAP remains available -even after the printer joins another network. +Normal operation uses `WIFI_STA` only. The setup SoftAP exists only while the +printer is being provisioned or recovered. -The AP SSID is: +Startup behavior is: + +```text +no saved network + -> setup AP + +saved network + -> STA connect for up to 30 seconds + -> success: online, STA only + -> failure: setup AP +``` + +The setup AP SSID is: ```text Mini-Printer-XXXX @@ -72,19 +83,35 @@ Mini-Printer-XXXX where `XXXX` is derived from the ESP32 ID. Development builds use an open AP unless `WIFI_AP_PASSWORD` is configured. +A triple click on GPIO 5 requests setup mode at any time. If saved credentials +exist, setup mode is a temporary five-minute recovery window. A first-boot +device with no credentials remains in setup until configured. After a successful +connection the HTTP response is sent first, then the AP closes after a short +delay and the printer returns to STA-only operation. + +While setup mode is active: + +- captive DNS points hostnames at the setup AP +- `/api/wifi/scan`, `/api/wifi/connect`, and `/api/wifi/forget` are enabled +- the embedded Web UI can still print through `POST /api/print` +- raw MP printing on TCP port `9100` remains available + +The AP is still temporary; successful provisioning or the setup timeout returns +the printer to STA-only operation. + The printer stores STA credentials as one NVS record only after a successful -connection. A failed save is reported to the browser. Saved credentials are -restored on restart; credentials from earlier firmware releases are still read. -Failed setup attempts are not persisted. +connection. A failed setup attempt is not persisted. During normal operation, +a transient STA disconnect relies on automatic reconnect and does **not** open +the setup AP. -The printer exposes: +Normal network services are: - HTTP on port `80` - MP protocol over raw TCP on port `9100` - mDNS as `mini-printer-xxxx.local` -Connect to `Mini-Printer-XXXX` and open `http://192.168.4.1/` in a browser. -The setup AP does not redirect other hostnames. +During setup, connect to `Mini-Printer-XXXX`. Captive DNS redirects generic +HTTP probes to the embedded page; `http://192.168.4.1/` is also available. The TCP service is not IPP, AirPrint, JetDirect, or ESC/POS. It carries the project's MP framing protocol. @@ -127,5 +154,6 @@ The current pin map targets the original V4 board configuration. Print-head timing and thermistor calibration are intentionally kept in the hardware layer so they can be validated and tuned independently of protocol/network changes. -The GPIO 5 button prints a built-in diagnostic page on a double click. Holding -it for about one second feeds paper, and releasing it stops the feed. +GPIO 5 uses the shared `kbutton.c` gesture state machine. A double click prints +a built-in diagnostic page, a triple click enters Wi-Fi setup mode, and holding +the button for about one second feeds paper until release. 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 = + - + diff --git a/firmware/src/em_config.h b/firmware/src/em_config.h index 26f3602..4907092 100644 --- a/firmware/src/em_config.h +++ b/firmware/src/em_config.h @@ -1,74 +1,77 @@ -#ifndef _EM_CONFIG_H_ -#define _EM_CONFIG_H_ - -#include +#ifndef _EM_CONFIG_H_ +#define _EM_CONFIG_H_ + +#include #ifndef BLE_ENABLED #define BLE_ENABLED 1 #endif - -#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" -#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" -#define BLE_NAME "Mini-Printer" - -// Wi-Fi runs in AP+STA mode. The AP is always available as a setup and -// recovery path while STA credentials are stored in NVS after a successful -// connection. -#define WIFI_ENABLED 1 -#define WIFI_AP_SSID "Mini-Printer" -#define WIFI_AP_PASSWORD "" -#define WIFI_HTTP_PORT 80 -#define WIFI_PRINT_PORT 9100 -#define WIFI_CLIENT_IDLE_TIMEOUT_MS 30000 -#define WIFI_CONNECT_TIMEOUT_MS 15000 -#define WIFI_PREFS_NAMESPACE "wifi" - -#define DEFAULT_PRINT_DENSITY 64 -#define PRINT_QUEUE_DEPTH 16 -#define PRINT_TRAILING_FEED_STEPS 230 - -// 按键引脚 -// 34 35 36 39 仅可以作为输入 INPUT, 不支持 INPUT_PULLUP -#define PIN_KEY 5 -#define PIN_PAPER 35 -#define PIN_BATTERY_ADC 34 -#define PIN_ADC_EN 4 -#define BATTERY_ADC_BIT 12 -#define PIN_TEMPERATRUE 36 -#define PIN_BEEP 18 -#define PIN_LED 18 - -#define PIN_MOTOR_AP 23 -#define PIN_MOTOR_AM 22 -#define PIN_MOTOR_BP 21 -#define PIN_MOTOR_BM 19 - + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" +#define BLE_NAME "Mini-Printer" + +// Wi-Fi normally runs as STA only. The setup AP is enabled only when there +// are no credentials, the saved network is unavailable at boot, or the user +// explicitly enters setup mode. +#define WIFI_ENABLED 1 +#define WIFI_AP_SSID "Mini-Printer" +#define WIFI_AP_PASSWORD "" +#define WIFI_HTTP_PORT 80 +#define WIFI_PRINT_PORT 9100 +#define WIFI_CLIENT_IDLE_TIMEOUT_MS 30000 +#define WIFI_CONNECT_TIMEOUT_MS 15000 +#define WIFI_STARTUP_CONNECT_TIMEOUT_MS 30000 +#define WIFI_SETUP_TIMEOUT_MS 300000 +#define WIFI_SETUP_CLOSE_DELAY_MS 1500 +#define WIFI_PREFS_NAMESPACE "wifi" + +#define DEFAULT_PRINT_DENSITY 64 +#define PRINT_QUEUE_DEPTH 16 +#define PRINT_TRAILING_FEED_STEPS 230 + +// 按键引脚 +// 34 35 36 39 仅可以作为输入 INPUT, 不支持 INPUT_PULLUP +#define PIN_KEY 5 +#define PIN_PAPER 35 +#define PIN_BATTERY_ADC 34 +#define PIN_ADC_EN 4 +#define BATTERY_ADC_BIT 12 +#define PIN_TEMPERATRUE 36 +#define PIN_BEEP 18 +#define PIN_LED 18 + +#define PIN_MOTOR_AP 23 +#define PIN_MOTOR_AM 22 +#define PIN_MOTOR_BP 21 +#define PIN_MOTOR_BM 19 + #define PIN_LAT 12 #define PIN_SCK 15 #define PIN_SPI_MISO 16 #define PIN_SDA 13 - -// 原厂 V4 -#define PIN_STB1 26 -#define PIN_STB2 27 -#define PIN_STB3 14 -#define PIN_STB4 32 -#define PIN_STB5 33 -#define PIN_STB6 25 - -// 打印头电源升压控制引脚 -#define PIN_VHEN 17 - + +// 原厂 V4 +#define PIN_STB1 26 +#define PIN_STB2 27 +#define PIN_STB3 14 +#define PIN_STB4 32 +#define PIN_STB5 33 +#define PIN_STB6 25 + +// 打印头电源升压控制引脚 +#define PIN_VHEN 17 + #define PRINT_TIME 2600 #define PRINT_END_TIME 200 #define PRINTER_BOOST_SETTLE_MS 50 #define MOTOR_WATI_TIME 4000 -#define LAT_TIME 1 - -#define PRINTER_MAX_TEMPERATURE_C 65 -#define PRINTER_TEMPERATURE_CHECK_LINES 16 -#define PRINTER_STALL_TIMEOUT_MS 20000 - -#define us_delay(us) delayMicroseconds(us) - -#endif +#define LAT_TIME 1 + +#define PRINTER_MAX_TEMPERATURE_C 65 +#define PRINTER_TEMPERATURE_CHECK_LINES 16 +#define PRINTER_STALL_TIMEOUT_MS 20000 + +#define us_delay(us) delayMicroseconds(us) + +#endif 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); +} diff --git a/firmware/src/hal/em_button.cpp b/firmware/src/hal/em_button.cpp deleted file mode 100644 index 4661c27..0000000 --- a/firmware/src/hal/em_button.cpp +++ /dev/null @@ -1,171 +0,0 @@ -#include "em_button.h" -#include "em_config.h" - -#include - -namespace { -constexpr uint8_t DEBOUNCE_TICKS = 2; // 40 ms -constexpr uint8_t DOUBLE_TICKS = 15; // 300 ms after the first release -constexpr uint8_t LONG_TICKS = 50; // 1 s - -enum Phase : uint8_t { - IDLE, - FIRST_PRESS, - WAIT_SECOND_PRESS, - SECOND_PRESS, - LONG_PRESS, -}; - -Button_t *head = nullptr; - -void emit(Button_t *button, Button_Event event) -{ - button->last_event = event; - if (button->callbacks[event] != nullptr) - button->callbacks[event](button); -} - -bool stable_level_changed(Button_t *button) -{ - const uint8_t level = digitalRead(button->pin); - if (level == button->stable_level) - { - button->debounce_ticks = 0; - return false; - } - - if (level != button->candidate_level) - { - button->candidate_level = level; - button->debounce_ticks = 1; - } - else if (++button->debounce_ticks >= DEBOUNCE_TICKS) - { - button->stable_level = level; - button->debounce_ticks = 0; - return true; - } - return false; -} - -void begin_long_press(Button_t *button) -{ - // A second press that becomes a long press is not a double click. - if (button->phase == SECOND_PRESS) - emit(button, BUTTON_SINGLE); - button->phase = LONG_PRESS; - emit(button, BUTTON_LONG); -} -} // namespace - -void Button_Create(Button_t *button, uint8_t pin, uint8_t active_level) -{ - if (button == nullptr) - return; - - Button_Delete(button); - memset(button, 0, sizeof(*button)); - button->pin = pin; - button->active_level = active_level; - pinMode(pin, active_level == LOW ? INPUT_PULLUP : INPUT_PULLDOWN); - button->stable_level = digitalRead(pin); - button->candidate_level = button->stable_level; - button->phase = IDLE; - button->last_event = BUTTON_EVENT_COUNT; - button->next = head; - head = button; -} - -void Button_Attach(Button_t *button, Button_Event event, Button_CallBack callback) -{ - if (button != nullptr && event < BUTTON_EVENT_COUNT) - button->callbacks[event] = callback; -} - -void Button_Delete(Button_t *button) -{ - for (Button_t **current = &head; *current != nullptr; current = &(*current)->next) - { - if (*current == button) - { - *current = button->next; - button->next = nullptr; - return; - } - } -} - -void Button_Cycle_Process(Button_t *button) -{ - if (button == nullptr) - return; - - const bool changed = stable_level_changed(button); - const bool pressed = button->stable_level == button->active_level; - - if (changed) - { - if (pressed) - { - if (button->phase == WAIT_SECOND_PRESS) - { - button->phase = SECOND_PRESS; - button->hold_ticks = 0; - } - else if (button->phase == IDLE) - { - button->phase = FIRST_PRESS; - button->hold_ticks = 0; - } - } - else if (button->phase == FIRST_PRESS) - { - button->phase = WAIT_SECOND_PRESS; - button->double_ticks = 0; - } - else if (button->phase == SECOND_PRESS) - { - button->phase = IDLE; - emit(button, BUTTON_DOUBLE); - } - else if (button->phase == LONG_PRESS) - { - button->phase = IDLE; - emit(button, BUTTON_LONG_RELEASE); - } - return; - } - - // Do not count time while an opposite level is being debounced. - if (button->debounce_ticks != 0) - return; - - if (pressed && (button->phase == FIRST_PRESS || button->phase == SECOND_PRESS)) - { - if (++button->hold_ticks >= LONG_TICKS) - begin_long_press(button); - } - else if (!pressed && button->phase == WAIT_SECOND_PRESS) - { - if (++button->double_ticks >= DOUBLE_TICKS) - { - button->phase = IDLE; - emit(button, BUTTON_SINGLE); - } - } -} - -void Button_Process() -{ - for (Button_t *button = head; button != nullptr; ) - { - Button_t *next = button->next; - Button_Cycle_Process(button); - button = next; - } -} - -Button_Event Get_Button_Event(const Button_t *button) -{ - return button == nullptr ? BUTTON_EVENT_COUNT : button->last_event; -} diff --git a/firmware/src/hal/em_button.h b/firmware/src/hal/em_button.h deleted file mode 100644 index 99c0fdd..0000000 --- a/firmware/src/hal/em_button.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef EM_BUTTON_H -#define EM_BUTTON_H - -#include - -// Button_Process is called every 20 ms by the button task. -enum Button_Event : uint8_t { - BUTTON_SINGLE = 0, - BUTTON_DOUBLE, - BUTTON_LONG, - BUTTON_LONG_RELEASE, - BUTTON_EVENT_COUNT, -}; - -typedef void (*Button_CallBack)(void *button); - -struct Button_t { - uint8_t pin; - uint8_t active_level; - uint8_t stable_level; - uint8_t candidate_level; - uint8_t debounce_ticks; - uint8_t phase; - uint8_t hold_ticks; - uint8_t double_ticks; - Button_Event last_event; - Button_CallBack callbacks[BUTTON_EVENT_COUNT]; - Button_t *next; -}; - -void Button_Create(Button_t *button, uint8_t pin, uint8_t active_level); -void Button_Attach(Button_t *button, Button_Event event, Button_CallBack callback); -void Button_Delete(Button_t *button); -void Button_Cycle_Process(Button_t *button); -void Button_Process(); -Button_Event Get_Button_Event(const Button_t *button); - -#endif diff --git a/firmware/src/network/web_server.cpp b/firmware/src/network/web_server.cpp index 7708925..692d1d4 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}