Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions Firmware/HomeButtonsArduino/src/display/display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <SPIFFS.h>
#include <U8g2_for_Adafruit_GFX.h>
#include <qrcode.h>
#include <time.h>

#include "bitmaps.h"
#include "config.h"
Expand Down Expand Up @@ -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<time_t>(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();
}

Expand Down
19 changes: 17 additions & 2 deletions Firmware/HomeButtonsArduino/src/setup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<const char *> menu = {"wifi", "param", "info", "exit"};
wifi_manager.setMenu(menu);
wifi_manager.setDarkMode(true);
wifi_manager.setShowInfoUpdate(true);

Expand Down Expand Up @@ -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);
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,11 +121,14 @@ device starts an access point `HB-<random-id>` with password
`HB-<serial-number>`, 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.
Expand Down
15 changes: 12 additions & 3 deletions docs/counter-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 <token>`. 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 |
Expand All @@ -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:
Expand Down Expand Up @@ -386,6 +391,10 @@ curl -s -X POST https://your-n8n/webhook/<path> \
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.
Expand Down
Loading