diff --git a/Firmware/HomeButtonsArduino/src/display/display.cpp b/Firmware/HomeButtonsArduino/src/display/display.cpp index 196bd57..7aaa584 100644 --- a/Firmware/HomeButtonsArduino/src/display/display.cpp +++ b/Firmware/HomeButtonsArduino/src/display/display.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "bitmaps.h" #include "config.h" @@ -557,6 +558,48 @@ void Display::draw_info() { u8g2.print(status); } + // Clock and build stamp. This screen is one button hold away from idle, + // where the device info screen is three steps into the settings menu, so + // the two things worth checking on a device that is behaving oddly live + // here: whether it knows the time, and what is actually flashed on it. + u8g2.setFont(u8g2_font_profont12_tr); + + if (device_state_.clock_valid()) { + const time_t local = + time(nullptr) + static_cast(device_state_.tz_offset()); + struct tm tm_local = {}; + gmtime_r(&local, &tm_local); + text = UIState::MessageType("%04d-%02d-%02d %02d:%02d", + tm_local.tm_year + 1900, tm_local.tm_mon + 1, + tm_local.tm_mday, tm_local.tm_hour, + tm_local.tm_min); + } else { + // No RTC and no NTP: the clock arrives with the first webhook response. + text = "clock not set"; + } + w = u8g2.getUTF8Width(text.c_str()); + u8g2.setCursor(WIDTH / 2 - w / 2, 272); + u8g2.print(text.c_str()); + + // One line while the two images agree, which is the normal case. A + // mismatch means a forgotten uploadfs and is worth the second line. + if (spiffs_build_ == BUILD_ID) { + text = UIState::MessageType("build %s", BUILD_ID); + w = u8g2.getUTF8Width(text.c_str()); + u8g2.setCursor(WIDTH / 2 - w / 2, 286); + u8g2.print(text.c_str()); + } else { + text = UIState::MessageType("fw %s", BUILD_ID); + w = u8g2.getUTF8Width(text.c_str()); + u8g2.setCursor(WIDTH / 2 - w / 2, 283); + u8g2.print(text.c_str()); + text = UIState::MessageType( + "fs %s", spiffs_build_.empty() ? "missing" : spiffs_build_.c_str()); + w = u8g2.getUTF8Width(text.c_str()); + u8g2.setCursor(WIDTH / 2 - w / 2, 294); + u8g2.print(text.c_str()); + } + disp->display(); } diff --git a/Firmware/HomeButtonsArduino/src/setup.cpp b/Firmware/HomeButtonsArduino/src/setup.cpp index 4722fa4..6e6f56d 100644 --- a/Firmware/HomeButtonsArduino/src/setup.cpp +++ b/Firmware/HomeButtonsArduino/src/setup.cpp @@ -279,7 +279,20 @@ void HBSetup::start_setup() { wifi_manager.setSaveParamsCallback( std::bind(&HBSetup::save_params_callback, this)); wifi_manager.setBreakAfterConfig(true); - wifi_manager.setParamsPage(true); + // Parameters render on the Wi-Fi page and on the Setup page both. + // + // setParamsPage(true) confines them to Setup, which is where Wi-Fi + // Country used to sit: the field that decides which channels a scan + // returns, on a different page from the scan results. WiFiManager gates + // rendering and saving on the same _paramsInWifi flag, so there is no way + // to place one parameter on the Wi-Fi page and leave the rest on Setup. + // + // What the flag does not gate is the Setup page itself: handleParam() + // renders unconditionally. So turning the flag on and putting "param" + // back in the menu by hand gives both pages, and either one saves. + wifi_manager.setParamsPage(false); + std::vector menu = {"wifi", "param", "info", "exit"}; + wifi_manager.setMenu(menu); wifi_manager.setDarkMode(true); wifi_manager.setShowInfoUpdate(true); @@ -319,12 +332,14 @@ void HBSetup::start_setup() { set_btn_label_params_from_device_state(app_.device_state_); #endif + // First, so that on the Wi-Fi page it lands directly under the network + // fields rather than below the button labels. + wifi_manager.addParameter(&wifi_country_param); wifi_manager.addParameter(&device_name_param); wifi_manager.addParameter(&endpoint_url_param); wifi_manager.addParameter(&auth_token_param); wifi_manager.addParameter(&awake_mode_param); wifi_manager.addParameter(&reset_spec_param); - wifi_manager.addParameter(&wifi_country_param); wifi_manager.addParameter(&static_ip_param); wifi_manager.addParameter(&gateway_param); wifi_manager.addParameter(&subnet_param); diff --git a/README.md b/README.md index 7473027..c1da1a6 100644 --- a/README.md +++ b/README.md @@ -121,11 +121,14 @@ device starts an access point `HB-` with password `HB-`, both unique per device and taken from the burnt eFuse. Portal fields are device name, **webhook URL**, **auth token**, **counter reset** schedule, **Wi-Fi country**, awake mode, static IP settings, and the -six button labels. +six button labels. They appear on the Wi-Fi page as well as on their own +Setup page, so either one saves them. Set the Wi-Fi country if your router sits on channel 12 or 13. The ESP-IDF default defers to whatever the access point advertises and reverts on -disconnect, so those channels can be missing from the scan list entirely. +disconnect, so those channels can be missing from the scan entirely. It is +listed first, directly under the network fields, because it decides what the +scan returns. > The remaining pages under `docs/` are inherited from upstream and describe > the MQTT firmware. They do not apply to this fork. diff --git a/docs/counter-guide.md b/docs/counter-guide.md index d16e442..4fe5a11 100644 --- a/docs/counter-guide.md +++ b/docs/counter-guide.md @@ -122,10 +122,10 @@ fields are empty — the device boots, counts locally, and silently logs |---|---| | Hold **any two buttons, 5 s** | Settings menu | | Then press **button 1** | Setup portal (Wi-Fi + all settings) | -| Then press **button 2** | Wi-Fi only — also carries the **Wi-Fi Country** field | +| Then press **button 2** | Wi-Fi only, carrying the **Wi-Fi Country** field | | Then press **button 3** | Restart | | Then press **button 4** | Cancel | -| In settings, hold **button 1, 2 s** | Device Info screen | +| In settings, hold **button 1, 2 s** | Device info screen: name, versions, both build stamps, IP, battery | | In settings, hold **button 3, 10 s** | Factory reset | The settings menu times out after 30 s. @@ -154,7 +154,7 @@ The portal opens at `http://192.168.4.1` and closes after 10 minutes. | **Device Name** | Cosmetic; shown on the settings screen | | **Webhook URL** | Full HTTPS URL of the n8n **production** webhook. Max 128 chars | | **Auth Token** | Sent as `Authorization: Bearer `. Max 128 chars. Masked in the page | -| **Wi-Fi Country** | ISO code, e.g. `PL`, `DE`, `GB`, `US`. Blank uses the ESP-IDF default. **Set this if your router uses channel 12 or 13** — see below | +| **Wi-Fi Country** | ISO code, e.g. `PL`, `DE`, `GB`, `US`. Blank uses the ESP-IDF default. **Set this if your router uses channel 12 or 13.** Shown on the Wi-Fi page too, under the network fields | | **Awake Mode** | `1` keeps the device from deep sleeping, which is what you want while watching a serial log. `0` for normal use. Drains the battery fast | | **Counter Reset** | When the counters clear themselves. `off`, `daily 03:00`, `weekly mon 03:00`, `monthly 1 03:00`. See below | | Static IP / Gateway / Subnet / DNS / DNS 2 | Optional — leave blank for DHCP. All three of IP, gateway and subnet must be set for static to apply | @@ -164,6 +164,11 @@ The portal opens at `http://192.168.4.1` and closes after 10 minutes. verifies the chain; a plain `http://` URL or an untrusted certificate will fail the POST. +**Wi-Fi Country** is on this page and on the Wi-Fi page, because it decides +which channels a scan returns and that is where you need it. Every field +below appears in both places and saving from either commits all of them, +so use whichever page you are already on. + ### Counter reset schedule One free-text field, parsed into four modes: @@ -386,6 +391,10 @@ curl -s -X POST https://your-n8n/webhook/ \ 4. Press three more times in quick succession. Only the first is slow. Presses 2-4 reuse the open TLS session and land in roughly 130 ms. 5. Press **button 5** to decrement, and confirm the count goes back down. + Hold any single button for 2 s to see the status screen: battery, the + local time the device believes it is, and the build stamp of what is + flashed. `clock not set` there means the receiver is not returning a + clock, and the scheduled reset will not run. 6. Press **button 1**. It is a title button, so it blinks twice and does nothing else. No POST, no change to the count. 7. After ~30 s of no input the device goes back to deep sleep.