2025-03-04 23:09:43 +11:00
|
|
|
#include "UITask.h"
|
|
|
|
|
#include <helpers/TxtDataHelpers.h>
|
2025-08-16 20:04:54 +10:00
|
|
|
#include "../MyMesh.h"
|
2026-05-12 14:16:37 +02:00
|
|
|
#include "../MsgExpand.h"
|
2025-08-08 20:01:31 +10:00
|
|
|
#include "target.h"
|
2025-10-27 17:58:29 +01:00
|
|
|
#ifdef WIFI_SSID
|
|
|
|
|
#include <WiFi.h>
|
|
|
|
|
#endif
|
2025-03-04 23:09:43 +11:00
|
|
|
|
2025-09-03 18:17:37 +02:00
|
|
|
#ifndef AUTO_OFF_MILLIS
|
|
|
|
|
#define AUTO_OFF_MILLIS 15000 // 15 seconds
|
|
|
|
|
#endif
|
2025-06-07 15:57:22 -07:00
|
|
|
#define BOOT_SCREEN_MILLIS 3000 // 3 seconds
|
2025-03-04 23:09:43 +11:00
|
|
|
|
2025-04-24 21:54:37 -04:00
|
|
|
#ifdef PIN_STATUS_LED
|
|
|
|
|
#define LED_ON_MILLIS 20
|
|
|
|
|
#define LED_ON_MSG_MILLIS 200
|
|
|
|
|
#define LED_CYCLE_MILLIS 4000
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
#define LONG_PRESS_MILLIS 1200
|
|
|
|
|
|
2025-08-16 18:13:50 +02:00
|
|
|
#ifndef UI_RECENT_LIST_SIZE
|
|
|
|
|
#define UI_RECENT_LIST_SIZE 4
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-10-16 17:33:22 +11:00
|
|
|
#if UI_HAS_JOYSTICK
|
|
|
|
|
#define PRESS_LABEL "press Enter"
|
|
|
|
|
#else
|
|
|
|
|
#define PRESS_LABEL "long press"
|
|
|
|
|
#endif
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
|
|
|
#include "icons.h"
|
|
|
|
|
|
|
|
|
|
class SplashScreen : public UIScreen {
|
|
|
|
|
UITask* _task;
|
|
|
|
|
unsigned long dismiss_after;
|
|
|
|
|
char _version_info[12];
|
2026-05-16 00:33:13 +02:00
|
|
|
char _plus_ver[12];
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
SplashScreen(UITask* task) : _task(task) {
|
2026-05-16 00:33:13 +02:00
|
|
|
// strip off dash and commit hash: v1.2.3-abcdef -> v1.2.3
|
2025-08-08 20:01:31 +10:00
|
|
|
const char *ver = FIRMWARE_VERSION;
|
|
|
|
|
const char *dash = strchr(ver, '-');
|
|
|
|
|
|
|
|
|
|
int len = dash ? dash - ver : strlen(ver);
|
|
|
|
|
if (len >= sizeof(_version_info)) len = sizeof(_version_info) - 1;
|
|
|
|
|
memcpy(_version_info, ver, len);
|
|
|
|
|
_version_info[len] = 0;
|
|
|
|
|
|
2026-05-16 00:33:13 +02:00
|
|
|
// extract plus version: v1.15-plus.1.4-SHA -> "1.4"
|
|
|
|
|
_plus_ver[0] = '\0';
|
|
|
|
|
const char *plus = strstr(ver, "plus.");
|
|
|
|
|
if (plus) {
|
|
|
|
|
plus += 5; // skip "plus."
|
|
|
|
|
const char *end = strchr(plus, '-');
|
|
|
|
|
int plen = end ? end - plus : strlen(plus);
|
|
|
|
|
if (plen >= (int)sizeof(_plus_ver)) plen = sizeof(_plus_ver) - 1;
|
|
|
|
|
memcpy(_plus_ver, plus, plen);
|
|
|
|
|
_plus_ver[plen] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
dismiss_after = millis() + BOOT_SCREEN_MILLIS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int render(DisplayDriver& display) override {
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.setTextSize(1);
|
|
|
|
|
const int lh = display.getLineHeight();
|
|
|
|
|
const int step = display.lineStep();
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
// meshcore logo
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-22 23:53:16 +02:00
|
|
|
int logoWidth = 128;
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int logo_y = 3;
|
|
|
|
|
display.drawXbm((display.width() - logoWidth) / 2, logo_y, meshcore_logo, logoWidth, 13);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
// version info at sz2
|
|
|
|
|
int ver_y = logo_y + 13 + 2;
|
2025-08-08 20:01:31 +10:00
|
|
|
display.setTextSize(2);
|
2026-05-22 18:34:20 +02:00
|
|
|
int lh2 = display.getLineHeight();
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.drawTextCentered(display.width()/2, ver_y, _version_info);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
2026-05-22 18:34:20 +02:00
|
|
|
// build date at sz1, below sz2 version
|
|
|
|
|
int date_y = ver_y + lh2 + 2;
|
2025-08-08 20:01:31 +10:00
|
|
|
display.setTextSize(1);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.drawTextCentered(display.width()/2, date_y, FIRMWARE_BUILD_DATE);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
2026-05-10 22:58:46 +02:00
|
|
|
#ifdef FIRMWARE_PLUS_BUILD
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int plus_y = date_y + step;
|
|
|
|
|
display.fillRect(0, plus_y - 1, display.width(), lh + 2);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
display.setColor(DisplayDriver::DARK);
|
2026-05-16 00:33:13 +02:00
|
|
|
char plus_label[24];
|
|
|
|
|
if (_plus_ver[0])
|
|
|
|
|
snprintf(plus_label, sizeof(plus_label), "Plus %s for Wio", _plus_ver);
|
|
|
|
|
else
|
|
|
|
|
snprintf(plus_label, sizeof(plus_label), "Plus for Wio");
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.drawTextCentered(display.width()/2, plus_y, plus_label);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-10 22:58:46 +02:00
|
|
|
#endif
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
return 1000;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void poll() override {
|
|
|
|
|
if (millis() >= dismiss_after) {
|
|
|
|
|
_task->gotoHomeScreen();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-11 16:52:24 +02:00
|
|
|
static const int QUICK_MSGS_MAX = 10;
|
|
|
|
|
|
2026-05-12 09:51:30 +02:00
|
|
|
// Bit positions for NodePrefs::home_pages_mask.
|
|
|
|
|
// Bit=1 means page is shown. 0 in the field means ALL visible (default/unset).
|
|
|
|
|
static const uint16_t HP_CLOCK = 1 << 0;
|
|
|
|
|
static const uint16_t HP_RECENT = 1 << 1;
|
|
|
|
|
static const uint16_t HP_RADIO = 1 << 2;
|
|
|
|
|
static const uint16_t HP_BLUETOOTH = 1 << 3;
|
|
|
|
|
static const uint16_t HP_ADVERT = 1 << 4;
|
|
|
|
|
static const uint16_t HP_GPS = 1 << 5;
|
|
|
|
|
static const uint16_t HP_SENSORS = 1 << 6;
|
|
|
|
|
static const uint16_t HP_TOOLS = 1 << 7;
|
|
|
|
|
static const uint16_t HP_SHUTDOWN = 1 << 8;
|
|
|
|
|
static const uint16_t HP_ALL = 0x01FF;
|
|
|
|
|
|
2026-05-13 18:22:41 +02:00
|
|
|
#include "KeyboardWidget.h"
|
2026-05-13 23:54:26 +02:00
|
|
|
#include "FullscreenMsgView.h"
|
2026-05-14 00:12:07 +02:00
|
|
|
#include "SensorPlaceholders.h"
|
2026-05-17 00:12:40 +02:00
|
|
|
#include "SettingsScreen.h"
|
|
|
|
|
#include "QuickMsgScreen.h"
|
2026-05-10 18:45:35 +02:00
|
|
|
|
2026-05-12 14:45:02 +02:00
|
|
|
// ── Custom screens (separate files to ease upstream merges) ───────────────────
|
|
|
|
|
#include "RingtoneEditorScreen.h"
|
|
|
|
|
#include "BotScreen.h"
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
#include "NearbyScreen.h"
|
|
|
|
|
#include "DashboardConfigScreen.h"
|
2026-05-14 16:08:33 +02:00
|
|
|
#include "AutoAdvertScreen.h"
|
2026-05-12 14:45:02 +02:00
|
|
|
#include "ToolsScreen.h"
|
Add ringtone editor, buzzer volume, unread fixes and buzzer notification fixes
- RingtoneEditorScreen: 32-note step sequencer accessible via new Tools page on home screen. U/D=pitch, ENTER=octave cycle, L/R=cursor, MENU=options (play, duration, BPM, insert/delete, save). Notes stored packed in NodePrefs and persisted via DataStore.
- ToolsScreen: new "Tools" home page entry launching the editor
- Buzzer volume: 0-4 bar control in Settings > Sound, persisted to prefs
- Buzzer notifications: fixed notify() firing inside wrong serial-guard branch and before addChannelMsg set the channel index
- Unread counter: fixed newest-first index direction; watermark formula now correct
- OLED brightness: wider range via pre-charge register 0xD9 combined with contrast
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:40:37 +02:00
|
|
|
|
|
|
|
|
// ── HomeScreen ────────────────────────────────────────────────────────────────
|
2025-08-08 20:01:31 +10:00
|
|
|
class HomeScreen : public UIScreen {
|
|
|
|
|
enum HomePage {
|
2026-05-10 15:18:35 +02:00
|
|
|
CLOCK,
|
2025-08-08 20:01:31 +10:00
|
|
|
RECENT,
|
|
|
|
|
RADIO,
|
|
|
|
|
BLUETOOTH,
|
|
|
|
|
ADVERT,
|
2025-09-28 09:43:28 +02:00
|
|
|
#if ENV_INCLUDE_GPS == 1
|
2025-09-23 10:39:43 +02:00
|
|
|
GPS,
|
|
|
|
|
#endif
|
2025-09-09 16:32:41 +02:00
|
|
|
#if UI_SENSORS_PAGE == 1
|
2025-09-05 15:20:52 +02:00
|
|
|
SENSORS,
|
2025-09-09 16:32:41 +02:00
|
|
|
#endif
|
2026-05-10 15:18:35 +02:00
|
|
|
SETTINGS,
|
Add ringtone editor, buzzer volume, unread fixes and buzzer notification fixes
- RingtoneEditorScreen: 32-note step sequencer accessible via new Tools page on home screen. U/D=pitch, ENTER=octave cycle, L/R=cursor, MENU=options (play, duration, BPM, insert/delete, save). Notes stored packed in NodePrefs and persisted via DataStore.
- ToolsScreen: new "Tools" home page entry launching the editor
- Buzzer volume: 0-4 bar control in Settings > Sound, persisted to prefs
- Buzzer notifications: fixed notify() firing inside wrong serial-guard branch and before addChannelMsg set the channel index
- Unread counter: fixed newest-first index direction; watermark formula now correct
- OLED brightness: wider range via pre-charge register 0xD9 combined with contrast
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:40:37 +02:00
|
|
|
TOOLS,
|
2026-05-10 18:45:35 +02:00
|
|
|
QUICK_MSG,
|
2025-08-08 20:01:31 +10:00
|
|
|
SHUTDOWN,
|
|
|
|
|
Count // keep as last
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
UITask* _task;
|
|
|
|
|
mesh::RTCClock* _rtc;
|
|
|
|
|
SensorManager* _sensors;
|
|
|
|
|
NodePrefs* _node_prefs;
|
|
|
|
|
uint8_t _page;
|
|
|
|
|
bool _shutdown_init;
|
2025-08-16 18:13:50 +02:00
|
|
|
AdvertPath recent[UI_RECENT_LIST_SIZE];
|
2025-08-08 20:01:31 +10:00
|
|
|
|
2026-05-12 09:51:30 +02:00
|
|
|
int pageBit(int page) const {
|
|
|
|
|
if (page == CLOCK) return 0;
|
|
|
|
|
if (page == RECENT) return 1;
|
|
|
|
|
if (page == RADIO) return 2;
|
|
|
|
|
if (page == BLUETOOTH) return 3;
|
|
|
|
|
if (page == ADVERT) return 4;
|
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
|
|
|
|
if (page == GPS) return 5;
|
|
|
|
|
#endif
|
|
|
|
|
#if UI_SENSORS_PAGE == 1
|
|
|
|
|
if (page == SENSORS) return 6;
|
|
|
|
|
#endif
|
|
|
|
|
if (page == TOOLS) return 7;
|
|
|
|
|
if (page == SHUTDOWN) return 8;
|
|
|
|
|
return -1; // SETTINGS, QUICK_MSG always visible
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 23:33:06 +02:00
|
|
|
// Maps page_order bit-index (0-10) back to the HomePage enum value for this build.
|
|
|
|
|
// Returns -1 if the page is not compiled in.
|
|
|
|
|
int bitToPage(int bit) const {
|
|
|
|
|
switch (bit) {
|
|
|
|
|
case 0: return CLOCK;
|
|
|
|
|
case 1: return RECENT;
|
|
|
|
|
case 2: return RADIO;
|
|
|
|
|
case 3: return BLUETOOTH;
|
|
|
|
|
case 4: return ADVERT;
|
|
|
|
|
case 5:
|
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
|
|
|
|
return GPS;
|
|
|
|
|
#else
|
|
|
|
|
return -1;
|
|
|
|
|
#endif
|
|
|
|
|
case 6:
|
|
|
|
|
#if UI_SENSORS_PAGE == 1
|
|
|
|
|
return SENSORS;
|
|
|
|
|
#else
|
|
|
|
|
return -1;
|
|
|
|
|
#endif
|
|
|
|
|
case 7: return TOOLS;
|
|
|
|
|
case 8: return SHUTDOWN;
|
|
|
|
|
case 9: return SETTINGS;
|
|
|
|
|
case 10: return QUICK_MSG;
|
|
|
|
|
default: return -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 09:51:30 +02:00
|
|
|
bool isPageVisible(int page) const {
|
|
|
|
|
int bit = pageBit(page);
|
|
|
|
|
if (bit < 0) return true;
|
2026-05-13 10:25:55 +02:00
|
|
|
uint16_t mask = (_node_prefs && _node_prefs->home_pages_mask) ? _node_prefs->home_pages_mask : HP_ALL;
|
2026-05-12 09:51:30 +02:00
|
|
|
return (mask >> bit) & 1;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-22 23:33:06 +02:00
|
|
|
// Build ordered list of all visible pages, respecting page_order when set.
|
|
|
|
|
// Returns count; out[] receives HomePage enum values.
|
|
|
|
|
int buildVisibleOrder(int* out) const {
|
|
|
|
|
int n = 0;
|
|
|
|
|
bool custom = _node_prefs && _node_prefs->page_order[0] >= 1 && _node_prefs->page_order[0] <= 11;
|
|
|
|
|
if (custom) {
|
|
|
|
|
for (int i = 0; i < 11; i++) {
|
|
|
|
|
uint8_t v = _node_prefs->page_order[i];
|
|
|
|
|
if (v < 1 || v > 11) break;
|
|
|
|
|
int pg = bitToPage(v - 1);
|
|
|
|
|
if (pg >= 0 && pg < (int)Count && isPageVisible(pg)) out[n++] = pg;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
for (int pg = 0; pg < (int)Count; pg++)
|
|
|
|
|
if (isPageVisible(pg)) out[n++] = pg;
|
2026-05-12 09:51:30 +02:00
|
|
|
}
|
2026-05-22 23:33:06 +02:00
|
|
|
return n;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int navPage(int from, int dir) const {
|
|
|
|
|
int order[11]; int n = buildVisibleOrder(order);
|
|
|
|
|
if (n == 0) return from;
|
|
|
|
|
int cur = 0;
|
|
|
|
|
for (int i = 0; i < n; i++) if (order[i] == from) { cur = i; break; }
|
|
|
|
|
return order[((cur + dir) % n + n) % n];
|
2026-05-12 09:51:30 +02:00
|
|
|
}
|
2025-09-16 17:17:15 -07:00
|
|
|
|
2026-05-14 18:13:18 +02:00
|
|
|
int renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) {
|
2026-01-28 14:23:36 -07:00
|
|
|
#ifndef BATT_MIN_MILLIVOLTS
|
2026-05-10 15:18:35 +02:00
|
|
|
#define BATT_MIN_MILLIVOLTS 3200
|
2026-01-28 14:23:36 -07:00
|
|
|
#endif
|
2026-05-10 19:47:12 +02:00
|
|
|
// LiPo discharge curve: voltage (mV) → raw capacity (%)
|
|
|
|
|
static const struct { uint16_t mv; uint8_t pct; } CURVE[] = {
|
|
|
|
|
{3200, 0}, {3300, 3}, {3400, 8}, {3500, 15},
|
|
|
|
|
{3600, 25}, {3650, 33}, {3700, 45}, {3750, 58},
|
|
|
|
|
{3800, 68}, {3900, 77}, {4000, 86}, {4100, 93}, {4200, 100}
|
|
|
|
|
};
|
|
|
|
|
static const int CURVE_LEN = sizeof(CURVE) / sizeof(CURVE[0]);
|
|
|
|
|
|
|
|
|
|
auto curveAt = [&](int mv) -> int {
|
|
|
|
|
if (mv <= (int)CURVE[0].mv) return CURVE[0].pct;
|
|
|
|
|
if (mv >= (int)CURVE[CURVE_LEN-1].mv) return CURVE[CURVE_LEN-1].pct;
|
|
|
|
|
for (int i = 1; i < CURVE_LEN; i++) {
|
|
|
|
|
if (mv <= (int)CURVE[i].mv) {
|
|
|
|
|
int span_mv = CURVE[i].mv - CURVE[i-1].mv;
|
|
|
|
|
int span_pct = CURVE[i].pct - CURVE[i-1].pct;
|
|
|
|
|
return CURVE[i-1].pct + (mv - (int)CURVE[i-1].mv) * span_pct / span_mv;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return 100;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
int low_mv = (_node_prefs && _node_prefs->low_batt_mv > 0)
|
|
|
|
|
? (int)_node_prefs->low_batt_mv : BATT_MIN_MILLIVOLTS;
|
|
|
|
|
int raw_pct = curveAt((int)batteryMilliVolts);
|
|
|
|
|
int low_pct = curveAt(low_mv);
|
|
|
|
|
// rescale so low_mv = 0% and 4200mV = 100%
|
|
|
|
|
int pct = (low_pct >= 100) ? 0
|
|
|
|
|
: (raw_pct - low_pct) * 100 / (100 - low_pct);
|
2026-05-10 15:18:35 +02:00
|
|
|
if (pct < 0) pct = 0;
|
|
|
|
|
if (pct > 100) pct = 100;
|
2025-08-08 20:01:31 +10:00
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
uint8_t mode = (_node_prefs && _node_prefs->batt_display_mode < 3)
|
|
|
|
|
? _node_prefs->batt_display_mode : 0;
|
2025-08-08 20:01:31 +10:00
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
display.setTextSize(1);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
2026-05-22 23:13:27 +02:00
|
|
|
const int lh = display.getLineHeight();
|
|
|
|
|
const int cw = display.getCharWidth();
|
|
|
|
|
const int ind = cw + 2; // single-char indicator width
|
2026-05-23 14:45:24 +02:00
|
|
|
const int ind_h = display.isLemonFont() ? lh - 2 : lh;
|
|
|
|
|
const int ind_gap = display.isLandscape() ? 3 : 1; // gap between indicator boxes
|
2026-05-22 18:34:20 +02:00
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
int battLeftX;
|
|
|
|
|
if (mode == 1) { // percent
|
|
|
|
|
char buf[6];
|
|
|
|
|
sprintf(buf, "%d%%", pct);
|
|
|
|
|
battLeftX = display.width() - display.getTextWidth(buf) - 1;
|
|
|
|
|
display.setCursor(battLeftX, 0);
|
|
|
|
|
display.print(buf);
|
|
|
|
|
} else if (mode == 2) { // voltage
|
|
|
|
|
char buf[8];
|
|
|
|
|
sprintf(buf, "%u.%02uV", batteryMilliVolts / 1000, (batteryMilliVolts % 1000) / 10);
|
|
|
|
|
battLeftX = display.width() - display.getTextWidth(buf) - 1;
|
|
|
|
|
display.setCursor(battLeftX, 0);
|
|
|
|
|
display.print(buf);
|
2026-05-22 18:34:20 +02:00
|
|
|
} else { // icon — scales with lh
|
|
|
|
|
const int iconH = lh;
|
2026-05-22 23:13:27 +02:00
|
|
|
const int iconW = lh * 2;
|
2026-05-23 14:45:24 +02:00
|
|
|
const int bm = display.isLandscape() ? 3 : 2; // inner margin: 3px on landscape e-ink, 2px on OLED/portrait
|
2026-05-22 18:34:20 +02:00
|
|
|
battLeftX = display.width() - iconW - 3;
|
|
|
|
|
display.drawRect(battLeftX, 0, iconW, iconH);
|
|
|
|
|
display.fillRect(battLeftX + iconW, iconH / 4, 2, iconH / 2);
|
2026-05-22 23:13:27 +02:00
|
|
|
int fillW = (pct * (iconW - 2 * bm)) / 100;
|
|
|
|
|
display.fillRect(battLeftX + bm, bm, fillW, iconH - 2 * bm);
|
2026-05-10 15:18:35 +02:00
|
|
|
}
|
2026-02-11 09:51:28 +01:00
|
|
|
|
|
|
|
|
#ifdef PIN_BUZZER
|
|
|
|
|
if (_task->isBuzzerQuiet()) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-22 23:13:27 +02:00
|
|
|
int mx = battLeftX - ind - ind_gap;
|
2026-05-23 14:45:24 +02:00
|
|
|
display.fillRect(mx, 0, ind, ind_h);
|
2026-05-22 18:34:20 +02:00
|
|
|
display.setColor(DisplayDriver::DARK);
|
|
|
|
|
display.setCursor(mx + 1, 0);
|
|
|
|
|
display.print("M");
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
battLeftX = mx;
|
2026-02-11 09:51:28 +01:00
|
|
|
}
|
|
|
|
|
#endif
|
2026-05-11 16:52:24 +02:00
|
|
|
|
|
|
|
|
// BT connection indicator (left of muted/battery icons)
|
2026-05-14 18:13:18 +02:00
|
|
|
int leftmostX = battLeftX;
|
2026-05-11 16:52:24 +02:00
|
|
|
if (_task->isSerialEnabled()) {
|
2026-05-22 23:13:27 +02:00
|
|
|
int btX = battLeftX - ind - ind_gap;
|
2026-05-11 16:52:24 +02:00
|
|
|
if (_task->hasConnection()) {
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-23 14:45:24 +02:00
|
|
|
display.fillRect(btX, 0, ind, ind_h);
|
2026-05-11 16:52:24 +02:00
|
|
|
display.setColor(DisplayDriver::DARK);
|
2026-05-22 18:34:20 +02:00
|
|
|
display.setCursor(btX + 1, 0);
|
2026-05-11 16:52:24 +02:00
|
|
|
display.print("B");
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
} else {
|
2026-05-22 18:34:20 +02:00
|
|
|
display.setCursor(btX + 1, 0);
|
2026-05-11 16:52:24 +02:00
|
|
|
display.print("b");
|
|
|
|
|
}
|
2026-05-22 23:13:27 +02:00
|
|
|
leftmostX = btX - ind_gap;
|
2026-05-14 18:13:18 +02:00
|
|
|
|
2026-05-23 10:06:22 +02:00
|
|
|
// "A" indicator — left of BT; blinks on OLED, always shown on e-ink
|
2026-05-14 18:13:18 +02:00
|
|
|
if (_node_prefs && _node_prefs->advert_auto_interval_sec > 0) {
|
2026-05-22 18:34:20 +02:00
|
|
|
int aX = leftmostX - ind;
|
2026-05-23 10:06:22 +02:00
|
|
|
#ifdef EINK_DISPLAY_MODEL
|
|
|
|
|
bool show_a = true;
|
|
|
|
|
#else
|
|
|
|
|
bool show_a = (millis() % 4000) < 2000;
|
|
|
|
|
#endif
|
|
|
|
|
if (show_a) {
|
2026-05-14 18:13:18 +02:00
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-23 14:45:24 +02:00
|
|
|
display.fillRect(aX, 0, ind, ind_h);
|
2026-05-14 18:13:18 +02:00
|
|
|
display.setColor(DisplayDriver::DARK);
|
2026-05-22 18:34:20 +02:00
|
|
|
display.setCursor(aX + 1, 0);
|
2026-05-14 18:13:18 +02:00
|
|
|
display.print("A");
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
}
|
|
|
|
|
leftmostX = aX - 1;
|
|
|
|
|
}
|
2026-05-11 16:52:24 +02:00
|
|
|
}
|
2026-05-14 18:13:18 +02:00
|
|
|
return leftmostX;
|
2025-08-08 20:01:31 +10:00
|
|
|
}
|
|
|
|
|
|
2025-09-09 16:32:41 +02:00
|
|
|
CayenneLPP sensors_lpp;
|
|
|
|
|
int sensors_nb = 0;
|
|
|
|
|
bool sensors_scroll = false;
|
|
|
|
|
int sensors_scroll_offset = 0;
|
2025-09-05 15:20:52 +02:00
|
|
|
int next_sensors_refresh = 0;
|
2025-10-22 20:01:15 +02:00
|
|
|
|
2025-09-05 15:20:52 +02:00
|
|
|
void refresh_sensors() {
|
|
|
|
|
if (millis() > next_sensors_refresh) {
|
2025-09-09 16:32:41 +02:00
|
|
|
sensors_lpp.reset();
|
|
|
|
|
sensors_nb = 0;
|
|
|
|
|
sensors_lpp.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
|
|
|
|
sensors.querySensors(0xFF, sensors_lpp);
|
|
|
|
|
LPPReader reader (sensors_lpp.getBuffer(), sensors_lpp.getSize());
|
|
|
|
|
uint8_t channel, type;
|
|
|
|
|
while(reader.readHeader(channel, type)) {
|
|
|
|
|
reader.skipData(type);
|
|
|
|
|
sensors_nb ++;
|
|
|
|
|
}
|
|
|
|
|
sensors_scroll = sensors_nb > UI_RECENT_LIST_SIZE;
|
2025-09-05 15:20:52 +02:00
|
|
|
#if AUTO_OFF_MILLIS > 0
|
|
|
|
|
next_sensors_refresh = millis() + 5000; // refresh sensor values every 5 sec
|
|
|
|
|
#else
|
|
|
|
|
next_sensors_refresh = millis() + 60000; // refresh sensor values every 1 min
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
public:
|
|
|
|
|
HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs)
|
2025-09-05 15:20:52 +02:00
|
|
|
: _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0),
|
2025-09-09 16:32:41 +02:00
|
|
|
_shutdown_init(false), sensors_lpp(200) { }
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
|
|
|
void poll() override {
|
|
|
|
|
if (_shutdown_init && !_task->isButtonPressed()) { // must wait for USR button to be released
|
|
|
|
|
_task->shutdown();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int render(DisplayDriver& display) override {
|
|
|
|
|
char tmp[80];
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.setTextSize(1);
|
|
|
|
|
const int lh = display.getLineHeight(); // line height at sz1
|
|
|
|
|
const int step = display.lineStep(); // lh + 2
|
|
|
|
|
const int dots_y = lh + 4; // page-dot row: just below header
|
2026-05-22 23:13:27 +02:00
|
|
|
const int content_y = dots_y + 6; // first content row (6px gap keeps dots visible)
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
// node name + battery — hidden on CLOCK page (full screen used for dashboard)
|
|
|
|
|
if (_page != CLOCK) {
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
char filtered_name[sizeof(_node_prefs->node_name)];
|
|
|
|
|
display.translateUTF8ToBlocks(filtered_name, _node_prefs->node_name, sizeof(filtered_name));
|
2026-05-14 18:13:18 +02:00
|
|
|
int rightEdge = renderBatteryIndicator(display, _task->getBattMilliVolts());
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
display.drawTextEllipsized(0, 0, rightEdge - 2, filtered_name);
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
|
2026-05-12 09:51:30 +02:00
|
|
|
// ensure current page is visible (e.g. after settings change)
|
2026-05-12 09:57:21 +02:00
|
|
|
if (!isPageVisible(_page)) _page = navPage(_page, +1);
|
2026-05-12 09:51:30 +02:00
|
|
|
|
2026-05-14 13:28:52 +02:00
|
|
|
// curr page indicator — hidden on CLOCK page (full screen used for dashboard)
|
|
|
|
|
if (_page != CLOCK) {
|
2026-05-22 23:33:06 +02:00
|
|
|
int order[11]; int n = buildVisibleOrder(order);
|
|
|
|
|
int curr_vis = 0;
|
|
|
|
|
for (int i = 0; i < n; i++) if (order[i] == _page) { curr_vis = i; break; }
|
|
|
|
|
int x = display.width() / 2 - 5 * (n - 1);
|
|
|
|
|
for (int i = 0; i < n; i++) {
|
2026-05-23 14:45:24 +02:00
|
|
|
int ds = display.isLandscape() ? 2 : 1;
|
2026-05-22 23:46:35 +02:00
|
|
|
if (i == curr_vis) display.fillRect(x-ds, dots_y-ds, 2*ds+1, 2*ds+1);
|
|
|
|
|
else display.fillRect(x-ds+1, dots_y-ds+1, 2*ds-1, 2*ds-1);
|
2026-05-22 23:33:06 +02:00
|
|
|
x += 10;
|
2025-08-08 20:01:31 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
if (_page == HomePage::CLOCK) {
|
|
|
|
|
uint32_t unix_ts = _rtc->getCurrentTime();
|
|
|
|
|
if (unix_ts < 1000000000UL) {
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
display.setTextSize(1);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int mid_y = display.height() / 2 - step;
|
|
|
|
|
display.drawTextCentered(display.width() / 2, mid_y, "! No time sync");
|
|
|
|
|
display.drawTextCentered(display.width() / 2, mid_y + step, "Enable GPS or");
|
|
|
|
|
display.drawTextCentered(display.width() / 2, mid_y + step * 2, "connect app");
|
2026-05-10 15:18:35 +02:00
|
|
|
} else {
|
|
|
|
|
int8_t tz = _node_prefs ? _node_prefs->tz_offset_hours : 0;
|
|
|
|
|
unix_ts += (int32_t)tz * 3600;
|
|
|
|
|
time_t t = (time_t)unix_ts;
|
|
|
|
|
struct tm* ti = gmtime(&t);
|
|
|
|
|
|
|
|
|
|
char buf[24];
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-10 15:18:35 +02:00
|
|
|
display.setTextSize(2);
|
2026-05-23 14:45:24 +02:00
|
|
|
#ifdef EINK_DISPLAY_MODEL
|
|
|
|
|
bool show_sec = false;
|
|
|
|
|
#else
|
2026-05-12 20:12:54 +02:00
|
|
|
bool show_sec = !_node_prefs || !_node_prefs->clock_hide_seconds;
|
2026-05-23 14:45:24 +02:00
|
|
|
#endif
|
2026-05-21 21:04:43 +02:00
|
|
|
bool h12 = _node_prefs && _node_prefs->clock_12h;
|
|
|
|
|
if (h12) {
|
|
|
|
|
int h = ti->tm_hour % 12; if (h == 0) h = 12;
|
|
|
|
|
const char* ap = ti->tm_hour < 12 ? "AM" : "PM";
|
|
|
|
|
if (show_sec) sprintf(buf, "%d:%02d:%02d%s", h, ti->tm_min, ti->tm_sec, ap);
|
|
|
|
|
else sprintf(buf, "%d:%02d %s", h, ti->tm_min, ap);
|
|
|
|
|
} else {
|
|
|
|
|
if (show_sec) sprintf(buf, "%02d:%02d:%02d", ti->tm_hour, ti->tm_min, ti->tm_sec);
|
|
|
|
|
else sprintf(buf, "%02d:%02d", ti->tm_hour, ti->tm_min);
|
|
|
|
|
}
|
2026-05-22 18:34:20 +02:00
|
|
|
int lh2 = display.getLineHeight(); // sz2 height: 16 (OLED) or 32 (landscape)
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
display.drawTextCentered(display.width() / 2, 0, buf);
|
2026-05-10 15:18:35 +02:00
|
|
|
|
|
|
|
|
display.setTextSize(1);
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
static const char* wd[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
|
|
|
|
|
static const char* mo[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
|
2026-05-10 15:18:35 +02:00
|
|
|
sprintf(buf, "%s %d %s %d", wd[ti->tm_wday], ti->tm_mday, mo[ti->tm_mon], 1900 + ti->tm_year);
|
2026-05-22 18:34:20 +02:00
|
|
|
int date_y = lh2 + 2;
|
|
|
|
|
display.drawTextCentered(display.width() / 2, date_y, buf);
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
2026-05-22 18:34:20 +02:00
|
|
|
int sep_y = date_y + lh + 1;
|
2026-05-22 23:13:27 +02:00
|
|
|
int dash0 = sep_y + display.sepH() + 2;
|
|
|
|
|
display.fillRect(0, sep_y, display.width(), display.sepH());
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
|
|
|
|
|
// dashboard data fields
|
|
|
|
|
if (_node_prefs) {
|
|
|
|
|
refresh_sensors();
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
const int FIELD_Y[3] = { dash0, dash0 + step, dash0 + step * 2 };
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
for (int fi = 0; fi < 3; fi++) {
|
|
|
|
|
uint8_t field = _node_prefs->dashboard_fields[fi];
|
|
|
|
|
if (field == DASH_NONE) continue;
|
|
|
|
|
|
|
|
|
|
char label[10], val[20];
|
|
|
|
|
label[0] = '\0';
|
|
|
|
|
val[0] = '\0';
|
|
|
|
|
|
|
|
|
|
if (field == DASH_BATT) {
|
|
|
|
|
strcpy(label, "Batt");
|
|
|
|
|
uint16_t mv = _task->getBattMilliVolts();
|
|
|
|
|
if (mv > 0) snprintf(val, sizeof(val), "%u.%02uV", mv/1000, (mv%1000)/10);
|
|
|
|
|
else strcpy(val, "--");
|
|
|
|
|
} else if (field == DASH_GPS) {
|
|
|
|
|
strcpy(label, "GPS");
|
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
|
|
|
|
LocationProvider* loc = sensors.getLocationProvider();
|
|
|
|
|
if (loc && loc->isValid())
|
|
|
|
|
snprintf(val, sizeof(val), "%.3f %.3f",
|
|
|
|
|
loc->getLatitude()/1000000.0f, loc->getLongitude()/1000000.0f);
|
|
|
|
|
else
|
|
|
|
|
strcpy(val, "no fix");
|
|
|
|
|
#else
|
|
|
|
|
strcpy(val, "--");
|
|
|
|
|
#endif
|
|
|
|
|
} else if (field == DASH_NODES) {
|
|
|
|
|
strcpy(label, "Nodes");
|
|
|
|
|
snprintf(val, sizeof(val), "%d", the_mesh.getNumContacts());
|
2026-05-14 18:20:49 +02:00
|
|
|
} else if (field == DASH_MSGS) {
|
|
|
|
|
strcpy(label, "Msgs");
|
|
|
|
|
int unread = _task->getDMUnreadTotal() + _task->getChannelUnreadCount() + _task->getRoomUnreadCount();
|
|
|
|
|
snprintf(val, sizeof(val), "%d", unread);
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
} else {
|
|
|
|
|
uint8_t lpp_type = 0;
|
|
|
|
|
switch (field) {
|
|
|
|
|
case DASH_TEMP: strcpy(label, "Temp"); lpp_type = LPP_TEMPERATURE; break;
|
|
|
|
|
case DASH_HUM: strcpy(label, "Hum"); lpp_type = LPP_RELATIVE_HUMIDITY; break;
|
|
|
|
|
case DASH_PRES: strcpy(label, "Pres"); lpp_type = LPP_BAROMETRIC_PRESSURE; break;
|
|
|
|
|
case DASH_ALT: strcpy(label, "Alt"); lpp_type = LPP_ALTITUDE; break;
|
|
|
|
|
case DASH_LUX: strcpy(label, "Lux"); lpp_type = LPP_LUMINOSITY; break;
|
|
|
|
|
case DASH_CO2: strcpy(label, "CO2"); lpp_type = LPP_CONCENTRATION; break;
|
|
|
|
|
}
|
|
|
|
|
if (lpp_type) {
|
|
|
|
|
LPPReader r(sensors_lpp.getBuffer(), sensors_lpp.getSize());
|
|
|
|
|
uint8_t ch, type;
|
|
|
|
|
while (r.readHeader(ch, type)) {
|
|
|
|
|
if (type == lpp_type) {
|
|
|
|
|
float v;
|
|
|
|
|
switch (lpp_type) {
|
|
|
|
|
case LPP_TEMPERATURE: r.readTemperature(v); snprintf(val, sizeof(val), "%.1f\xf8""C", v); break;
|
|
|
|
|
case LPP_RELATIVE_HUMIDITY: r.readRelativeHumidity(v); snprintf(val, sizeof(val), "%.0f%%", v); break;
|
|
|
|
|
case LPP_BAROMETRIC_PRESSURE: r.readPressure(v); snprintf(val, sizeof(val), "%.0fhPa", v); break;
|
|
|
|
|
case LPP_ALTITUDE: r.readAltitude(v); snprintf(val, sizeof(val), "%.0fm", v); break;
|
|
|
|
|
case LPP_LUMINOSITY: r.readLuminosity(v); snprintf(val, sizeof(val), "%.0flux", v); break;
|
|
|
|
|
case LPP_CONCENTRATION: r.readConcentration(v); snprintf(val, sizeof(val), "%.0fppm", v); break;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
r.skipData(type);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!val[0]) strcpy(val, "--");
|
|
|
|
|
}
|
2026-05-10 15:18:35 +02:00
|
|
|
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
if (val[0] && label[0]) {
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
display.setCursor(0, FIELD_Y[fi]);
|
|
|
|
|
display.print(label);
|
|
|
|
|
int vw = display.getTextWidth(val);
|
|
|
|
|
display.setCursor(display.width() - vw - 1, FIELD_Y[fi]);
|
|
|
|
|
display.print(val);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-10 15:18:35 +02:00
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
} else if (_page == HomePage::RECENT) {
|
2025-08-16 18:13:50 +02:00
|
|
|
the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE);
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int y = content_y;
|
|
|
|
|
for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += step) {
|
2025-08-08 20:01:31 +10:00
|
|
|
auto a = &recent[i];
|
|
|
|
|
if (a->name[0] == 0) continue; // empty slot
|
|
|
|
|
int secs = _rtc->getCurrentTime() - a->recv_timestamp;
|
|
|
|
|
if (secs < 60) {
|
|
|
|
|
sprintf(tmp, "%ds", secs);
|
|
|
|
|
} else if (secs < 60*60) {
|
|
|
|
|
sprintf(tmp, "%dm", secs / 60);
|
|
|
|
|
} else {
|
|
|
|
|
sprintf(tmp, "%dh", secs / (60*60));
|
|
|
|
|
}
|
2025-09-16 17:17:15 -07:00
|
|
|
|
|
|
|
|
int timestamp_width = display.getTextWidth(tmp);
|
|
|
|
|
int max_name_width = display.width() - timestamp_width - 1;
|
|
|
|
|
|
|
|
|
|
char filtered_recent_name[sizeof(a->name)];
|
|
|
|
|
display.translateUTF8ToBlocks(filtered_recent_name, a->name, sizeof(filtered_recent_name));
|
|
|
|
|
display.drawTextEllipsized(0, y, max_name_width, filtered_recent_name);
|
|
|
|
|
display.setCursor(display.width() - timestamp_width - 1, y);
|
2025-08-08 20:01:31 +10:00
|
|
|
display.print(tmp);
|
|
|
|
|
}
|
|
|
|
|
} else if (_page == HomePage::RADIO) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2025-08-08 20:01:31 +10:00
|
|
|
// freq / sf
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.setCursor(0, content_y);
|
2025-08-08 20:01:31 +10:00
|
|
|
sprintf(tmp, "FQ: %06.3f SF: %d", _node_prefs->freq, _node_prefs->sf);
|
|
|
|
|
display.print(tmp);
|
|
|
|
|
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.setCursor(0, content_y + step);
|
2025-08-08 20:01:31 +10:00
|
|
|
sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr);
|
|
|
|
|
display.print(tmp);
|
|
|
|
|
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
// tx power, noise floor
|
|
|
|
|
display.setCursor(0, content_y + step * 2);
|
2025-08-08 20:01:31 +10:00
|
|
|
sprintf(tmp, "TX: %ddBm", _node_prefs->tx_power_dbm);
|
|
|
|
|
display.print(tmp);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.setCursor(0, content_y + step * 3);
|
2025-08-08 20:01:31 +10:00
|
|
|
sprintf(tmp, "Noise floor: %d", radio_driver.getNoiseFloor());
|
|
|
|
|
display.print(tmp);
|
|
|
|
|
} else if (_page == HomePage::BLUETOOTH) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2025-08-08 20:01:31 +10:00
|
|
|
display.setTextSize(1);
|
2026-05-23 14:45:24 +02:00
|
|
|
display.drawXbm((display.width() - 32) / 2, content_y,
|
|
|
|
|
_task->isSerialEnabled() ? bluetooth_on : bluetooth_off, 32, 32);
|
|
|
|
|
const int text_y = content_y + 32 + 3;
|
|
|
|
|
const bool waiting_for_pair = _task->isSerialEnabled() && !_task->hasConnection() && the_mesh.getBLEPin() != 0;
|
|
|
|
|
if (waiting_for_pair && !display.isLandscape()) {
|
|
|
|
|
char pin_buf[16];
|
|
|
|
|
snprintf(pin_buf, sizeof(pin_buf), "PIN: %d", the_mesh.getBLEPin());
|
|
|
|
|
display.drawTextCentered(display.width() / 2, text_y, pin_buf);
|
|
|
|
|
} else if (waiting_for_pair) {
|
2026-05-11 16:52:24 +02:00
|
|
|
char pin_buf[16];
|
|
|
|
|
snprintf(pin_buf, sizeof(pin_buf), "PIN: %d", the_mesh.getBLEPin());
|
2026-05-23 14:45:24 +02:00
|
|
|
display.drawTextCentered(display.width() / 2, text_y, pin_buf);
|
|
|
|
|
display.drawTextCentered(display.width() / 2, text_y + step, "toggle: " PRESS_LABEL);
|
|
|
|
|
} else {
|
|
|
|
|
display.drawTextCentered(display.width() / 2, text_y, "toggle: " PRESS_LABEL);
|
2026-05-11 16:52:24 +02:00
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
} else if (_page == HomePage::ADVERT) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.drawXbm((display.width() - 32) / 2, content_y, advert_icon, 32, 32);
|
|
|
|
|
display.drawTextCentered(display.width() / 2, content_y + 32 + 3, "advert: " PRESS_LABEL);
|
2025-09-28 09:43:28 +02:00
|
|
|
#if ENV_INCLUDE_GPS == 1
|
2025-09-23 10:39:43 +02:00
|
|
|
} else if (_page == HomePage::GPS) {
|
|
|
|
|
LocationProvider* nmea = sensors.getLocationProvider();
|
2025-11-28 11:11:13 +01:00
|
|
|
char buf[50];
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int y = content_y;
|
2025-11-28 11:11:13 +01:00
|
|
|
bool gps_state = _task->getGPSState();
|
|
|
|
|
#ifdef PIN_GPS_SWITCH
|
|
|
|
|
bool hw_gps_state = digitalRead(PIN_GPS_SWITCH);
|
|
|
|
|
if (gps_state != hw_gps_state) {
|
|
|
|
|
strcpy(buf, gps_state ? "gps off(hw)" : "gps off(sw)");
|
|
|
|
|
} else {
|
|
|
|
|
strcpy(buf, gps_state ? "gps on" : "gps off");
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
strcpy(buf, gps_state ? "gps on" : "gps off");
|
|
|
|
|
#endif
|
|
|
|
|
display.drawTextLeftAlign(0, y, buf);
|
2025-09-23 10:39:43 +02:00
|
|
|
if (nmea == NULL) {
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
y += step;
|
2025-09-30 09:21:12 +02:00
|
|
|
display.drawTextLeftAlign(0, y, "Can't access GPS");
|
2025-09-23 10:39:43 +02:00
|
|
|
} else {
|
|
|
|
|
strcpy(buf, nmea->isValid()?"fix":"no fix");
|
2025-09-30 09:21:12 +02:00
|
|
|
display.drawTextRightAlign(display.width()-1, y, buf);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
y += step;
|
2025-09-30 09:21:12 +02:00
|
|
|
display.drawTextLeftAlign(0, y, "sat");
|
2025-09-23 10:39:43 +02:00
|
|
|
sprintf(buf, "%d", nmea->satellitesCount());
|
2025-09-30 09:21:12 +02:00
|
|
|
display.drawTextRightAlign(display.width()-1, y, buf);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
y += step;
|
2025-09-30 09:21:12 +02:00
|
|
|
display.drawTextLeftAlign(0, y, "pos");
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
sprintf(buf, "%.4f %.4f",
|
2025-09-23 10:39:43 +02:00
|
|
|
nmea->getLatitude()/1000000., nmea->getLongitude()/1000000.);
|
2025-09-30 09:21:12 +02:00
|
|
|
display.drawTextRightAlign(display.width()-1, y, buf);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
y += step;
|
2025-09-30 09:21:12 +02:00
|
|
|
display.drawTextLeftAlign(0, y, "alt");
|
2025-09-23 10:39:43 +02:00
|
|
|
sprintf(buf, "%.2f", nmea->getAltitude()/1000.);
|
2025-09-30 09:21:12 +02:00
|
|
|
display.drawTextRightAlign(display.width()-1, y, buf);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
y += step;
|
2025-09-23 10:39:43 +02:00
|
|
|
}
|
|
|
|
|
#endif
|
2025-09-09 16:32:41 +02:00
|
|
|
#if UI_SENSORS_PAGE == 1
|
2025-09-05 15:20:52 +02:00
|
|
|
} else if (_page == HomePage::SENSORS) {
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int y = content_y;
|
2025-09-05 15:20:52 +02:00
|
|
|
refresh_sensors();
|
2025-09-09 16:32:41 +02:00
|
|
|
|
2026-05-11 21:33:49 +02:00
|
|
|
uint8_t avail_types[16];
|
|
|
|
|
int avail_count = _sensors ? _sensors->getAvailableLPPTypes(avail_types, 16) : 0;
|
|
|
|
|
bool need_scroll = avail_count > UI_RECENT_LIST_SIZE;
|
|
|
|
|
int offset = need_scroll ? (sensors_scroll_offset % avail_count) : 0;
|
|
|
|
|
int show_n = need_scroll ? UI_RECENT_LIST_SIZE : avail_count;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < show_n; i++) {
|
|
|
|
|
uint8_t target = avail_types[(offset + i) % avail_count];
|
|
|
|
|
|
|
|
|
|
// scan LPP buffer for this type
|
|
|
|
|
LPPReader r(sensors_lpp.getBuffer(), sensors_lpp.getSize());
|
|
|
|
|
uint8_t ch, type;
|
|
|
|
|
bool found = false;
|
|
|
|
|
char buf[22] = "--";
|
|
|
|
|
while (r.readHeader(ch, type)) {
|
|
|
|
|
if (type == target) {
|
|
|
|
|
float v, v2, v3;
|
|
|
|
|
switch (type) {
|
|
|
|
|
case LPP_GPS:
|
|
|
|
|
r.readGPS(v, v2, v3);
|
|
|
|
|
if (v != 0 || v2 != 0) snprintf(buf, sizeof(buf), "%.4f %.4f", v, v2);
|
|
|
|
|
break;
|
|
|
|
|
case LPP_VOLTAGE: r.readVoltage(v); snprintf(buf, sizeof(buf), "%.2fV", v); break;
|
|
|
|
|
case LPP_CURRENT: r.readCurrent(v); snprintf(buf, sizeof(buf), "%.3fA", v); break;
|
|
|
|
|
case LPP_POWER: r.readPower(v); snprintf(buf, sizeof(buf), "%.1fW", v); break;
|
|
|
|
|
case LPP_TEMPERATURE:r.readTemperature(v); snprintf(buf, sizeof(buf), "%.1f\xf8""C", v); break;
|
|
|
|
|
case LPP_RELATIVE_HUMIDITY: r.readRelativeHumidity(v); snprintf(buf, sizeof(buf), "%.0f%%", v); break;
|
|
|
|
|
case LPP_BAROMETRIC_PRESSURE: r.readPressure(v); snprintf(buf, sizeof(buf), "%.1fhPa", v); break;
|
|
|
|
|
case LPP_ALTITUDE: r.readAltitude(v); snprintf(buf, sizeof(buf), "%.0fm", v); break;
|
|
|
|
|
case LPP_LUMINOSITY: r.readLuminosity(v); snprintf(buf, sizeof(buf), "%.0flux", v); break;
|
|
|
|
|
case LPP_PERCENTAGE: r.readPercentage(v); snprintf(buf, sizeof(buf), "%.0f%%", v); break;
|
|
|
|
|
case LPP_DISTANCE: r.readDistance(v); snprintf(buf, sizeof(buf), "%.2fm", v); break;
|
|
|
|
|
case LPP_CONCENTRATION: r.readConcentration(v); snprintf(buf, sizeof(buf), "%.0fppm", v); break;
|
|
|
|
|
default: r.skipData(type); continue;
|
|
|
|
|
}
|
|
|
|
|
found = true;
|
2025-09-09 16:32:41 +02:00
|
|
|
break;
|
2026-05-11 21:33:49 +02:00
|
|
|
}
|
|
|
|
|
r.skipData(type);
|
2025-09-05 15:20:52 +02:00
|
|
|
}
|
2026-05-11 21:33:49 +02:00
|
|
|
(void)found;
|
|
|
|
|
|
|
|
|
|
static const struct { uint8_t type; const char* name; } TYPE_NAMES[] = {
|
|
|
|
|
{ LPP_VOLTAGE, "voltage" },
|
|
|
|
|
{ LPP_GPS, "gps" },
|
|
|
|
|
{ LPP_TEMPERATURE, "temp" },
|
|
|
|
|
{ LPP_RELATIVE_HUMIDITY, "humidity" },
|
|
|
|
|
{ LPP_BAROMETRIC_PRESSURE,"pressure" },
|
|
|
|
|
{ LPP_ALTITUDE, "altitude" },
|
|
|
|
|
{ LPP_CURRENT, "current" },
|
|
|
|
|
{ LPP_POWER, "power" },
|
|
|
|
|
{ LPP_LUMINOSITY, "light" },
|
|
|
|
|
{ LPP_PERCENTAGE, "moisture" },
|
|
|
|
|
{ LPP_DISTANCE, "distance" },
|
|
|
|
|
{ LPP_CONCENTRATION, "CO2" },
|
|
|
|
|
};
|
|
|
|
|
const char* name = "sensor";
|
|
|
|
|
for (auto& tn : TYPE_NAMES) { if (tn.type == target) { name = tn.name; break; } }
|
|
|
|
|
|
2025-09-05 15:20:52 +02:00
|
|
|
display.setCursor(0, y);
|
2025-09-09 16:32:41 +02:00
|
|
|
display.print(name);
|
2026-05-11 21:33:49 +02:00
|
|
|
display.setCursor(display.width() - display.getTextWidth(buf) - 1, y);
|
2025-09-05 15:20:52 +02:00
|
|
|
display.print(buf);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
y += step;
|
2025-09-05 15:20:52 +02:00
|
|
|
}
|
2026-05-11 21:33:49 +02:00
|
|
|
if (need_scroll) sensors_scroll_offset = (sensors_scroll_offset + 1) % avail_count;
|
2025-09-09 16:32:41 +02:00
|
|
|
else sensors_scroll_offset = 0;
|
|
|
|
|
#endif
|
2026-05-10 15:18:35 +02:00
|
|
|
} else if (_page == HomePage::SETTINGS) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-10 15:18:35 +02:00
|
|
|
display.setTextSize(1);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.drawTextCentered(display.width() / 2, content_y, "Settings");
|
|
|
|
|
display.drawTextCentered(display.width() / 2, content_y + step * 2, PRESS_LABEL " to open");
|
Add ringtone editor, buzzer volume, unread fixes and buzzer notification fixes
- RingtoneEditorScreen: 32-note step sequencer accessible via new Tools page on home screen. U/D=pitch, ENTER=octave cycle, L/R=cursor, MENU=options (play, duration, BPM, insert/delete, save). Notes stored packed in NodePrefs and persisted via DataStore.
- ToolsScreen: new "Tools" home page entry launching the editor
- Buzzer volume: 0-4 bar control in Settings > Sound, persisted to prefs
- Buzzer notifications: fixed notify() firing inside wrong serial-guard branch and before addChannelMsg set the channel index
- Unread counter: fixed newest-first index direction; watermark formula now correct
- OLED brightness: wider range via pre-charge register 0xD9 combined with contrast
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:40:37 +02:00
|
|
|
} else if (_page == HomePage::TOOLS) {
|
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
|
display.setTextSize(1);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.drawTextCentered(display.width() / 2, content_y, "Tools");
|
|
|
|
|
display.drawTextCentered(display.width() / 2, content_y + step * 2, PRESS_LABEL " to open");
|
2026-05-10 18:45:35 +02:00
|
|
|
} else if (_page == HomePage::QUICK_MSG) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2026-05-10 18:45:35 +02:00
|
|
|
display.setTextSize(1);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.drawTextCentered(display.width() / 2, content_y, "Messages");
|
2026-05-13 10:25:55 +02:00
|
|
|
int total_unread = _task->getDMUnreadTotal() + _task->getChannelUnreadCount() + _task->getRoomUnreadCount();
|
2026-05-11 19:11:19 +02:00
|
|
|
if (total_unread > 0) {
|
|
|
|
|
char badge[20];
|
|
|
|
|
snprintf(badge, sizeof(badge), "%d unread", total_unread);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.drawTextCentered(display.width() / 2, content_y + step, badge);
|
2026-05-11 19:11:19 +02:00
|
|
|
}
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.drawTextCentered(display.width() / 2, content_y + step * 2, PRESS_LABEL " to open");
|
2025-08-08 20:01:31 +10:00
|
|
|
} else if (_page == HomePage::SHUTDOWN) {
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
2025-08-08 20:01:31 +10:00
|
|
|
display.setTextSize(1);
|
|
|
|
|
if (_shutdown_init) {
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.drawTextCentered(display.width() / 2, content_y + step, "hibernating...");
|
2025-08-08 20:01:31 +10:00
|
|
|
} else {
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
display.drawXbm((display.width() - 32) / 2, content_y, power_icon, 32, 32);
|
2026-05-23 14:45:24 +02:00
|
|
|
const int text_y = content_y + 32 + 3;
|
|
|
|
|
const int lh1 = display.getLineHeight();
|
|
|
|
|
if (text_y + lh1 <= display.height()) {
|
|
|
|
|
char hib_hint[32];
|
|
|
|
|
snprintf(hib_hint, sizeof(hib_hint), "hibernate: %s", PRESS_LABEL);
|
|
|
|
|
if (display.getTextWidth(hib_hint) < display.width()) {
|
|
|
|
|
display.drawTextCentered(display.width() / 2, text_y, hib_hint);
|
|
|
|
|
} else {
|
|
|
|
|
display.drawTextCentered(display.width() / 2, text_y, "hibernate:");
|
|
|
|
|
if (text_y + step + lh1 <= display.height())
|
|
|
|
|
display.drawTextCentered(display.width() / 2, text_y + step, PRESS_LABEL);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-14 17:25:27 +02:00
|
|
|
bool auto_adv = _node_prefs && _node_prefs->advert_auto_interval_sec > 0;
|
2026-05-22 10:47:19 +02:00
|
|
|
#ifdef EINK_DISPLAY_MODEL
|
2026-05-23 10:06:22 +02:00
|
|
|
if (_page == HomePage::CLOCK) return 30000; // no seconds on e-ink; refresh every 30s
|
2026-05-22 10:47:19 +02:00
|
|
|
return 30000; // e-ink: limit base polling; new messages still force immediate refresh via notify()
|
|
|
|
|
#else
|
2026-05-12 20:12:54 +02:00
|
|
|
if (_page == HomePage::CLOCK) {
|
|
|
|
|
bool show_sec = !_node_prefs || !_node_prefs->clock_hide_seconds;
|
2026-05-14 18:13:18 +02:00
|
|
|
return auto_adv ? 1000 : (show_sec ? 1000 : 60000);
|
2026-05-12 20:12:54 +02:00
|
|
|
}
|
2026-05-14 18:13:18 +02:00
|
|
|
return auto_adv ? 1000 : 5000;
|
2026-05-22 10:47:19 +02:00
|
|
|
#endif
|
2025-08-08 20:01:31 +10:00
|
|
|
}
|
2025-03-10 22:42:52 +01:00
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
bool handleInput(char c) override {
|
2025-09-03 16:28:58 +10:00
|
|
|
if (c == KEY_LEFT || c == KEY_PREV) {
|
2026-05-12 09:51:30 +02:00
|
|
|
_page = navPage(_page, -1);
|
2025-08-08 20:01:31 +10:00
|
|
|
return true;
|
|
|
|
|
}
|
2025-09-03 16:28:58 +10:00
|
|
|
if (c == KEY_NEXT || c == KEY_RIGHT) {
|
2026-05-12 09:51:30 +02:00
|
|
|
_page = navPage(_page, +1);
|
2025-08-08 20:01:31 +10:00
|
|
|
if (_page == HomePage::RECENT) {
|
|
|
|
|
_task->showAlert("Recent adverts", 800);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (c == KEY_ENTER && _page == HomePage::BLUETOOTH) {
|
|
|
|
|
if (_task->isSerialEnabled()) { // toggle Bluetooth on/off
|
|
|
|
|
_task->disableSerial();
|
|
|
|
|
} else {
|
|
|
|
|
_task->enableSerial();
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
if (c == KEY_ENTER && _page == HomePage::ADVERT) {
|
2025-09-17 08:53:50 +08:00
|
|
|
_task->notify(UIEventType::ack);
|
2025-08-08 20:01:31 +10:00
|
|
|
if (the_mesh.advert()) {
|
|
|
|
|
_task->showAlert("Advert sent!", 1000);
|
|
|
|
|
} else {
|
|
|
|
|
_task->showAlert("Advert failed..", 1000);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-09-28 09:43:28 +02:00
|
|
|
#if ENV_INCLUDE_GPS == 1
|
2025-09-23 10:39:43 +02:00
|
|
|
if (c == KEY_ENTER && _page == HomePage::GPS) {
|
|
|
|
|
_task->toggleGPS();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
2025-09-09 16:32:41 +02:00
|
|
|
#if UI_SENSORS_PAGE == 1
|
2025-09-05 15:32:02 +02:00
|
|
|
if (c == KEY_ENTER && _page == HomePage::SENSORS) {
|
|
|
|
|
_task->toggleGPS();
|
2025-09-05 15:35:04 +02:00
|
|
|
next_sensors_refresh=0;
|
2025-09-05 15:32:02 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
2025-09-09 16:32:41 +02:00
|
|
|
#endif
|
2026-05-10 15:18:35 +02:00
|
|
|
if (c == KEY_ENTER && _page == HomePage::SETTINGS) {
|
|
|
|
|
_task->gotoSettingsScreen();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
Add ringtone editor, buzzer volume, unread fixes and buzzer notification fixes
- RingtoneEditorScreen: 32-note step sequencer accessible via new Tools page on home screen. U/D=pitch, ENTER=octave cycle, L/R=cursor, MENU=options (play, duration, BPM, insert/delete, save). Notes stored packed in NodePrefs and persisted via DataStore.
- ToolsScreen: new "Tools" home page entry launching the editor
- Buzzer volume: 0-4 bar control in Settings > Sound, persisted to prefs
- Buzzer notifications: fixed notify() firing inside wrong serial-guard branch and before addChannelMsg set the channel index
- Unread counter: fixed newest-first index direction; watermark formula now correct
- OLED brightness: wider range via pre-charge register 0xD9 combined with contrast
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:40:37 +02:00
|
|
|
if (c == KEY_ENTER && _page == HomePage::TOOLS) {
|
|
|
|
|
_task->gotoToolsScreen();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2026-05-10 18:45:35 +02:00
|
|
|
if (c == KEY_ENTER && _page == HomePage::QUICK_MSG) {
|
|
|
|
|
_task->gotoQuickMsgScreen();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) {
|
|
|
|
|
_shutdown_init = true; // need to wait for button to be released
|
|
|
|
|
return true;
|
|
|
|
|
}
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
if (c == KEY_CONTEXT_MENU && _page == HomePage::CLOCK) {
|
|
|
|
|
_task->gotoDashboardConfig();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-03-04 23:09:43 +11:00
|
|
|
|
2025-06-19 17:26:58 +02:00
|
|
|
void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs) {
|
2025-03-16 13:42:36 +11:00
|
|
|
_display = display;
|
2025-06-19 17:26:58 +02:00
|
|
|
_sensors = sensors;
|
2026-05-10 15:18:35 +02:00
|
|
|
_node_prefs = node_prefs;
|
|
|
|
|
uint32_t aoff = autoOffMillis();
|
|
|
|
|
_auto_off = millis() + (aoff > 0 ? aoff : AUTO_OFF_MILLIS);
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
|
|
|
#if defined(PIN_USER_BTN)
|
|
|
|
|
user_btn.begin();
|
|
|
|
|
#endif
|
2025-09-01 17:11:55 +10:00
|
|
|
#if defined(PIN_USER_BTN_ANA)
|
|
|
|
|
analog_btn.begin();
|
|
|
|
|
#endif
|
2025-08-08 20:01:31 +10:00
|
|
|
|
2025-03-10 17:11:55 +01:00
|
|
|
if (_display != NULL) {
|
|
|
|
|
_display->turnOn();
|
|
|
|
|
}
|
2025-04-08 22:58:17 +12:00
|
|
|
|
2025-05-20 11:52:55 +10:00
|
|
|
#ifdef PIN_BUZZER
|
2025-11-20 18:55:39 -08:00
|
|
|
buzzer.quiet(_node_prefs->buzzer_quiet);
|
2026-05-12 09:17:37 +02:00
|
|
|
buzzer.setVolume(_node_prefs->buzzer_volume);
|
2026-05-10 15:18:35 +02:00
|
|
|
buzzer.begin();
|
2025-05-20 11:52:55 +10:00
|
|
|
#endif
|
2025-05-27 19:10:56 -07:00
|
|
|
|
2025-09-07 15:16:15 +08:00
|
|
|
#ifdef PIN_VIBRATION
|
|
|
|
|
vibration.begin();
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-05-11 16:52:24 +02:00
|
|
|
// Set default quick message if slot 0 is empty (first boot)
|
|
|
|
|
if (_node_prefs && _node_prefs->custom_msgs[0][0] == '\0') {
|
|
|
|
|
strncpy(_node_prefs->custom_msgs[0], "OK", sizeof(_node_prefs->custom_msgs[0]) - 1);
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-04 21:33:48 +10:00
|
|
|
ui_started_at = millis();
|
2025-08-08 20:01:31 +10:00
|
|
|
_alert_expiry = 0;
|
2026-05-10 15:18:35 +02:00
|
|
|
_batt_mv = AbstractUITask::getBattMilliVolts(); // seed EMA with first reading
|
2025-08-08 20:01:31 +10:00
|
|
|
|
|
|
|
|
splash = new SplashScreen(this);
|
|
|
|
|
home = new HomeScreen(this, &rtc_clock, sensors, node_prefs);
|
2026-05-10 15:18:35 +02:00
|
|
|
settings = new SettingsScreen(this);
|
2026-05-10 18:45:35 +02:00
|
|
|
quick_msg = new QuickMsgScreen(this);
|
Add ringtone editor, buzzer volume, unread fixes and buzzer notification fixes
- RingtoneEditorScreen: 32-note step sequencer accessible via new Tools page on home screen. U/D=pitch, ENTER=octave cycle, L/R=cursor, MENU=options (play, duration, BPM, insert/delete, save). Notes stored packed in NodePrefs and persisted via DataStore.
- ToolsScreen: new "Tools" home page entry launching the editor
- Buzzer volume: 0-4 bar control in Settings > Sound, persisted to prefs
- Buzzer notifications: fixed notify() firing inside wrong serial-guard branch and before addChannelMsg set the channel index
- Unread counter: fixed newest-first index direction; watermark formula now correct
- OLED brightness: wider range via pre-charge register 0xD9 combined with contrast
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:40:37 +02:00
|
|
|
tools_screen = new ToolsScreen(this);
|
|
|
|
|
ringtone_edit = new RingtoneEditorScreen(this, node_prefs);
|
2026-05-12 11:47:33 +02:00
|
|
|
bot_screen = new BotScreen(this, node_prefs);
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
nearby_screen = new NearbyScreen(this);
|
|
|
|
|
dashboard_config = new DashboardConfigScreen(this, node_prefs);
|
2026-05-14 16:08:33 +02:00
|
|
|
auto_advert_screen = new AutoAdvertScreen(this, node_prefs);
|
2026-05-10 15:18:35 +02:00
|
|
|
applyBrightness();
|
2026-05-20 09:29:38 +02:00
|
|
|
applyFont();
|
2026-05-20 21:53:40 +02:00
|
|
|
applyRotation();
|
2026-05-22 23:50:21 +02:00
|
|
|
setCurrScreen(splash);
|
2025-08-08 20:01:31 +10:00
|
|
|
}
|
|
|
|
|
|
2026-05-10 22:12:29 +02:00
|
|
|
void UITask::gotoSettingsScreen() {
|
|
|
|
|
((SettingsScreen*)settings)->markClean();
|
|
|
|
|
setCurrScreen(settings);
|
|
|
|
|
}
|
|
|
|
|
|
Add ringtone editor, buzzer volume, unread fixes and buzzer notification fixes
- RingtoneEditorScreen: 32-note step sequencer accessible via new Tools page on home screen. U/D=pitch, ENTER=octave cycle, L/R=cursor, MENU=options (play, duration, BPM, insert/delete, save). Notes stored packed in NodePrefs and persisted via DataStore.
- ToolsScreen: new "Tools" home page entry launching the editor
- Buzzer volume: 0-4 bar control in Settings > Sound, persisted to prefs
- Buzzer notifications: fixed notify() firing inside wrong serial-guard branch and before addChannelMsg set the channel index
- Unread counter: fixed newest-first index direction; watermark formula now correct
- OLED brightness: wider range via pre-charge register 0xD9 combined with contrast
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:40:37 +02:00
|
|
|
void UITask::gotoToolsScreen() {
|
|
|
|
|
setCurrScreen(tools_screen);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 11:43:38 +02:00
|
|
|
void UITask::gotoRingtoneEditor(int slot) {
|
|
|
|
|
((RingtoneEditorScreen*)ringtone_edit)->enter(slot);
|
Add ringtone editor, buzzer volume, unread fixes and buzzer notification fixes
- RingtoneEditorScreen: 32-note step sequencer accessible via new Tools page on home screen. U/D=pitch, ENTER=octave cycle, L/R=cursor, MENU=options (play, duration, BPM, insert/delete, save). Notes stored packed in NodePrefs and persisted via DataStore.
- ToolsScreen: new "Tools" home page entry launching the editor
- Buzzer volume: 0-4 bar control in Settings > Sound, persisted to prefs
- Buzzer notifications: fixed notify() firing inside wrong serial-guard branch and before addChannelMsg set the channel index
- Unread counter: fixed newest-first index direction; watermark formula now correct
- OLED brightness: wider range via pre-charge register 0xD9 combined with contrast
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:40:37 +02:00
|
|
|
setCurrScreen(ringtone_edit);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 11:47:33 +02:00
|
|
|
void UITask::gotoBotScreen() {
|
|
|
|
|
((BotScreen*)bot_screen)->enter();
|
|
|
|
|
setCurrScreen(bot_screen);
|
|
|
|
|
}
|
|
|
|
|
|
feat: NearbyScreen, configurable clock dashboard, memory optimizations
- Add NearbyScreen: GPS contact list with haversine distance sort,
type filter (ALL/Chat/Rpt/Room/Snsr), detail view with ping (RTT via
ACK poll), node scan via advert(), context menu
- Add DashboardConfigScreen: configure up to 3 data fields on the clock
page (Battery, Temp, Humidity, Pressure, GPS, Altitude, Lux, CO2,
Contacts); entered via long-press ENTER on clock page
- Rework HP_CLOCK layout: full-screen clock (size 2 at y=0), date at
y=19, separator at y=28, three data field rows at y=31/41/51;
hide node name/battery indicator on clock page
- Persist dashboard_fields[3] in NodePrefs and DataStore
- Add isAckPending() to MyMesh for NearbyScreen ping detection
- Wire NearbyScreen into ToolsScreen and UITask
- Reduce NearbyScreen entry buffer from MAX_CONTACTS(100) to 32,
saving ~3.3 KB RAM
- Switch haversine/bearing math from double to float (sinf/cosf/atan2f),
eliminating double libm and saving ~5-10 KB flash
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 11:28:36 +02:00
|
|
|
void UITask::gotoNearbyScreen() {
|
|
|
|
|
((NearbyScreen*)nearby_screen)->enter();
|
|
|
|
|
setCurrScreen(nearby_screen);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void UITask::gotoDashboardConfig() {
|
|
|
|
|
((DashboardConfigScreen*)dashboard_config)->enter();
|
|
|
|
|
setCurrScreen(dashboard_config);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-14 16:08:33 +02:00
|
|
|
void UITask::gotoAutoAdvertScreen() {
|
|
|
|
|
((AutoAdvertScreen*)auto_advert_screen)->enter();
|
|
|
|
|
setCurrScreen(auto_advert_screen);
|
|
|
|
|
}
|
|
|
|
|
|
Add ringtone editor, buzzer volume, unread fixes and buzzer notification fixes
- RingtoneEditorScreen: 32-note step sequencer accessible via new Tools page on home screen. U/D=pitch, ENTER=octave cycle, L/R=cursor, MENU=options (play, duration, BPM, insert/delete, save). Notes stored packed in NodePrefs and persisted via DataStore.
- ToolsScreen: new "Tools" home page entry launching the editor
- Buzzer volume: 0-4 bar control in Settings > Sound, persisted to prefs
- Buzzer notifications: fixed notify() firing inside wrong serial-guard branch and before addChannelMsg set the channel index
- Unread counter: fixed newest-first index direction; watermark formula now correct
- OLED brightness: wider range via pre-charge register 0xD9 combined with contrast
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 09:40:37 +02:00
|
|
|
void UITask::playMelody(const char* melody) {
|
|
|
|
|
#ifdef PIN_BUZZER
|
|
|
|
|
buzzer.playForced(melody);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void UITask::stopMelody() {
|
|
|
|
|
#ifdef PIN_BUZZER
|
|
|
|
|
buzzer.stop();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool UITask::isMelodyPlaying() {
|
|
|
|
|
#ifdef PIN_BUZZER
|
|
|
|
|
return buzzer.isPlaying();
|
|
|
|
|
#else
|
|
|
|
|
return false;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 18:45:35 +02:00
|
|
|
void UITask::gotoQuickMsgScreen() {
|
|
|
|
|
((QuickMsgScreen*)quick_msg)->reset();
|
|
|
|
|
setCurrScreen(quick_msg);
|
|
|
|
|
}
|
|
|
|
|
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
void UITask::addChannelMsg(uint8_t channel_idx, const char* text) {
|
2026-05-11 18:56:26 +02:00
|
|
|
_last_notif_ch_idx = (int)channel_idx;
|
WioTrackerL1: redesign companion UI (channels, keyboard, msg preview)
- QuickMsg → Message screen: channel history, on-screen keyboard, custom message first
- Channel history: RAM ring buffer (32 entries, 80 chars), fullscreen view with word-wrap scroll
- After channel send: stay in history, own message shown as "Me: ..."
- MsgPreview: one message per screen with up/down nav, ENTER opens fullscreen scroll
- Monochromatic display: all color usage corrected to DARK/LIGHT only
- Top bar: battery icon, BT muted indicator, splash "Plus for Wio"
- KB_MAX_LEN: 64 → 100 chars; channel text buffer: 52 → 80 chars
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:56:30 +02:00
|
|
|
((QuickMsgScreen*)quick_msg)->addChannelMsg(channel_idx, text);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-11 19:11:19 +02:00
|
|
|
int UITask::getChannelUnreadCount() const {
|
|
|
|
|
return ((QuickMsgScreen*)quick_msg)->getTotalChannelUnread();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-13 10:25:55 +02:00
|
|
|
void UITask::addDMMsg(const uint8_t* pub_key, bool outgoing, const char* text) {
|
|
|
|
|
((QuickMsgScreen*)quick_msg)->addDMMsg(pub_key, outgoing, text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int UITask::getDMUnreadTotal() const {
|
|
|
|
|
int total = 0;
|
|
|
|
|
for (int i = 0; i < DM_UNREAD_TABLE_SIZE; i++)
|
|
|
|
|
total += _dm_unread_table[i].count;
|
|
|
|
|
return total;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
void UITask::showAlert(const char* text, int duration_millis) {
|
2026-05-12 08:19:27 +02:00
|
|
|
snprintf(_alert, sizeof(_alert), "%s", text);
|
2025-08-08 20:01:31 +10:00
|
|
|
_alert_expiry = millis() + duration_millis;
|
2025-05-20 11:52:55 +10:00
|
|
|
}
|
|
|
|
|
|
2026-05-15 11:43:38 +02:00
|
|
|
static void buildMelodyFromPrefs(const NodePrefs* p, int slot, char* buf, int size) {
|
|
|
|
|
static const uint16_t BPM_OPTS[] = { 60, 90, 120, 150, 180 };
|
|
|
|
|
static const uint8_t DUR_VALS[] = { 4, 8, 16, 32 };
|
|
|
|
|
static const char PITCHES[] = { 'p', 'c', 'd', 'e', 'f', 'g', 'a', 'b' };
|
|
|
|
|
const uint8_t* notes = (slot == 2) ? p->ringtone2_notes : p->ringtone_notes;
|
|
|
|
|
uint8_t len = (slot == 2) ? p->ringtone2_len : p->ringtone_len;
|
|
|
|
|
uint8_t bpm_i = (slot == 2) ? p->ringtone2_bpm_idx : p->ringtone_bpm_idx;
|
|
|
|
|
if (len == 0) { buf[0] = '\0'; return; }
|
|
|
|
|
uint16_t bpm = BPM_OPTS[bpm_i < 5 ? bpm_i : 2];
|
|
|
|
|
int pos = snprintf(buf, size, "Ring:d=8,o=5,b=%u:", bpm);
|
|
|
|
|
for (int i = 0; i < len && pos < size - 8; i++) {
|
|
|
|
|
if (i > 0 && pos < size - 1) buf[pos++] = ',';
|
|
|
|
|
uint8_t pitch = notes[i] & 0x07;
|
|
|
|
|
uint8_t octave = ((notes[i] >> 3) & 0x03) + 4;
|
|
|
|
|
uint8_t dur_val = DUR_VALS[(notes[i] >> 5) & 0x03];
|
|
|
|
|
if (pitch == 0) pos += snprintf(buf + pos, size - pos, "%dp", dur_val);
|
|
|
|
|
else pos += snprintf(buf + pos, size - pos, "%d%c%d", dur_val, PITCHES[pitch], octave);
|
|
|
|
|
}
|
|
|
|
|
if (pos < size) buf[pos] = '\0';
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-17 08:53:50 +08:00
|
|
|
void UITask::notify(UIEventType t) {
|
2025-05-20 11:52:55 +10:00
|
|
|
#if defined(PIN_BUZZER)
|
2025-09-17 08:53:50 +08:00
|
|
|
switch(t){
|
2026-05-13 10:48:42 +02:00
|
|
|
case UIEventType::contactMessage: {
|
|
|
|
|
bool play = false;
|
|
|
|
|
bool force = false;
|
|
|
|
|
if (_last_notif_dm_valid && _node_prefs) {
|
|
|
|
|
uint8_t state = 0;
|
|
|
|
|
for (int i = 0; i < NodePrefs::DM_NOTIF_TABLE_MAX; i++) {
|
|
|
|
|
if (_node_prefs->dm_notif[i].state &&
|
|
|
|
|
memcmp(_node_prefs->dm_notif[i].prefix, _last_notif_dm_prefix, 4) == 0) {
|
|
|
|
|
state = _node_prefs->dm_notif[i].state; break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (state == 2) { play = true; force = true; } // force-on
|
|
|
|
|
else if (state == 1) { /* muted */ }
|
|
|
|
|
else { play = !buzzer.isQuiet(); } // default: follow global
|
|
|
|
|
} else {
|
|
|
|
|
play = !buzzer.isQuiet();
|
|
|
|
|
}
|
|
|
|
|
_last_notif_dm_valid = false;
|
|
|
|
|
if (play) {
|
2026-05-15 11:43:38 +02:00
|
|
|
int slot = _node_prefs ? (int)_node_prefs->notif_melody_dm : 0;
|
|
|
|
|
if (_node_prefs) {
|
|
|
|
|
for (int i = 0; i < NodePrefs::DM_MELODY_TABLE_MAX; i++)
|
|
|
|
|
if (_node_prefs->dm_melody[i].slot &&
|
|
|
|
|
memcmp(_node_prefs->dm_melody[i].prefix, _last_notif_dm_prefix, 4) == 0)
|
|
|
|
|
{ slot = _node_prefs->dm_melody[i].slot; break; }
|
|
|
|
|
}
|
|
|
|
|
bool custom_played = false;
|
|
|
|
|
if (slot > 0 && _node_prefs) {
|
|
|
|
|
buildMelodyFromPrefs(_node_prefs, slot, _notif_mel_buf, sizeof(_notif_mel_buf));
|
|
|
|
|
if (_notif_mel_buf[0]) {
|
|
|
|
|
if (force) buzzer.playForced(_notif_mel_buf); else buzzer.play(_notif_mel_buf);
|
|
|
|
|
custom_played = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!custom_played) {
|
|
|
|
|
if (force) buzzer.playForced("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7");
|
|
|
|
|
else buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7");
|
|
|
|
|
}
|
2026-05-13 10:48:42 +02:00
|
|
|
}
|
2025-05-20 19:09:49 +12:00
|
|
|
break;
|
2026-05-13 10:48:42 +02:00
|
|
|
}
|
2026-05-11 18:56:26 +02:00
|
|
|
case UIEventType::channelMessage: {
|
2026-05-11 21:17:31 +02:00
|
|
|
bool play = false;
|
|
|
|
|
bool force = false;
|
2026-05-15 15:34:10 +02:00
|
|
|
if (_last_notif_ch_idx >= 0 && _last_notif_ch_idx < 64 && _node_prefs) {
|
2026-05-11 18:56:26 +02:00
|
|
|
uint64_t mask = 1ULL << _last_notif_ch_idx;
|
|
|
|
|
if (_node_prefs->ch_notif_override & mask) {
|
2026-05-15 11:43:38 +02:00
|
|
|
if (!(_node_prefs->ch_notif_muted & mask)) { play = true; force = true; }
|
2026-05-11 18:56:26 +02:00
|
|
|
} else {
|
2026-05-15 11:43:38 +02:00
|
|
|
play = !buzzer.isQuiet();
|
2026-05-11 18:56:26 +02:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
play = !buzzer.isQuiet();
|
|
|
|
|
}
|
2026-05-11 21:17:31 +02:00
|
|
|
if (play) {
|
2026-05-15 11:43:38 +02:00
|
|
|
int slot = _node_prefs ? (int)_node_prefs->notif_melody_ch : 0;
|
2026-05-15 15:34:10 +02:00
|
|
|
if (_last_notif_ch_idx >= 0 && _last_notif_ch_idx < 64 && _node_prefs) {
|
2026-05-15 11:43:38 +02:00
|
|
|
uint64_t mask = 1ULL << _last_notif_ch_idx;
|
|
|
|
|
if (_node_prefs->ch_notif_melody_set & mask)
|
|
|
|
|
slot = (_node_prefs->ch_notif_melody_2 & mask) ? 2 : 1;
|
|
|
|
|
}
|
|
|
|
|
bool custom_played = false;
|
|
|
|
|
if (slot > 0 && _node_prefs) {
|
|
|
|
|
buildMelodyFromPrefs(_node_prefs, slot, _notif_mel_buf, sizeof(_notif_mel_buf));
|
|
|
|
|
if (_notif_mel_buf[0]) {
|
|
|
|
|
if (force) buzzer.playForced(_notif_mel_buf); else buzzer.play(_notif_mel_buf);
|
|
|
|
|
custom_played = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!custom_played) {
|
|
|
|
|
if (force) buzzer.playForced("kerplop:d=16,o=6,b=120:32g#,32c#");
|
|
|
|
|
else buzzer.play("kerplop:d=16,o=6,b=120:32g#,32c#");
|
|
|
|
|
}
|
2026-05-11 21:17:31 +02:00
|
|
|
}
|
2026-05-15 11:43:38 +02:00
|
|
|
_last_notif_ch_idx = -1;
|
2025-05-23 17:58:13 +12:00
|
|
|
break;
|
2026-05-11 18:56:26 +02:00
|
|
|
}
|
2025-05-30 22:55:53 -07:00
|
|
|
case UIEventType::ack:
|
2025-05-30 22:58:30 -07:00
|
|
|
buzzer.play("ack:d=32,o=8,b=120:c");
|
2025-05-30 22:55:53 -07:00
|
|
|
break;
|
2025-05-20 19:33:21 +12:00
|
|
|
case UIEventType::roomMessage:
|
|
|
|
|
case UIEventType::newContactMessage:
|
|
|
|
|
case UIEventType::none:
|
2025-05-20 19:09:49 +12:00
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-05-20 11:52:55 +10:00
|
|
|
#endif
|
2025-03-10 17:11:55 +01:00
|
|
|
|
2025-09-07 15:16:15 +08:00
|
|
|
#ifdef PIN_VIBRATION
|
2025-09-17 08:53:50 +08:00
|
|
|
// Trigger vibration for all UI events except none
|
|
|
|
|
if (t != UIEventType::none) {
|
|
|
|
|
vibration.trigger();
|
|
|
|
|
}
|
2025-09-07 15:16:15 +08:00
|
|
|
#endif
|
2025-03-10 17:11:55 +01:00
|
|
|
}
|
|
|
|
|
|
2025-09-07 15:16:15 +08:00
|
|
|
|
2025-03-10 17:11:55 +01:00
|
|
|
void UITask::msgRead(int msgcount) {
|
|
|
|
|
_msgcount = msgcount;
|
|
|
|
|
if (msgcount == 0) {
|
2026-05-11 19:27:16 +02:00
|
|
|
_room_unread = 0;
|
2026-05-13 10:25:55 +02:00
|
|
|
memset(_dm_unread_table, 0, sizeof(_dm_unread_table));
|
2026-05-13 10:36:37 +02:00
|
|
|
((QuickMsgScreen*)quick_msg)->clearAllChannelUnread();
|
2025-03-10 17:11:55 +01:00
|
|
|
}
|
2025-03-04 23:09:43 +11:00
|
|
|
}
|
|
|
|
|
|
2026-05-13 10:25:55 +02:00
|
|
|
void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount, uint8_t contact_type, const uint8_t* pub_key) {
|
2025-03-10 17:11:55 +01:00
|
|
|
_msgcount = msgcount;
|
2026-05-11 19:34:07 +02:00
|
|
|
if (contact_type == ADV_TYPE_ROOM && _room_unread < _msgcount) _room_unread++;
|
2026-05-13 10:25:55 +02:00
|
|
|
if (contact_type == ADV_TYPE_CHAT && pub_key != nullptr) {
|
2026-05-13 10:48:42 +02:00
|
|
|
memcpy(_last_notif_dm_prefix, pub_key, 4);
|
|
|
|
|
_last_notif_dm_valid = true;
|
2026-05-13 10:25:55 +02:00
|
|
|
int slot = -1, empty_slot = -1;
|
|
|
|
|
for (int i = 0; i < DM_UNREAD_TABLE_SIZE; i++) {
|
|
|
|
|
if (_dm_unread_table[i].count > 0 && memcmp(_dm_unread_table[i].prefix, pub_key, 4) == 0) { slot = i; break; }
|
|
|
|
|
if (empty_slot < 0 && _dm_unread_table[i].count == 0) empty_slot = i;
|
|
|
|
|
}
|
|
|
|
|
if (slot >= 0) {
|
|
|
|
|
if (_dm_unread_table[slot].count < 99) _dm_unread_table[slot].count++;
|
|
|
|
|
} else if (empty_slot >= 0) {
|
|
|
|
|
memcpy(_dm_unread_table[empty_slot].prefix, pub_key, 4);
|
|
|
|
|
_dm_unread_table[empty_slot].count = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-10 17:11:55 +01:00
|
|
|
|
2026-05-11 17:19:03 +02:00
|
|
|
char alert_buf[80];
|
|
|
|
|
snprintf(alert_buf, sizeof(alert_buf), "Msg: %.20s", from_name);
|
|
|
|
|
showAlert(alert_buf, 3000);
|
2025-03-04 23:09:43 +11:00
|
|
|
|
2026-05-17 09:55:51 +02:00
|
|
|
if (_display != NULL && !_locked) {
|
2025-11-03 21:25:31 +01:00
|
|
|
if (!_display->isOn() && !hasConnection()) {
|
|
|
|
|
_display->turnOn();
|
|
|
|
|
}
|
|
|
|
|
if (_display->isOn()) {
|
2026-05-11 17:19:03 +02:00
|
|
|
uint32_t aoff = autoOffMillis();
|
|
|
|
|
if (aoff > 0) _auto_off = millis() + aoff;
|
|
|
|
|
_next_refresh = 100;
|
2025-11-03 21:25:31 +01:00
|
|
|
}
|
2025-03-10 17:11:55 +01:00
|
|
|
}
|
2025-03-04 23:09:43 +11:00
|
|
|
}
|
|
|
|
|
|
2025-03-10 17:11:55 +01:00
|
|
|
void UITask::userLedHandler() {
|
|
|
|
|
#ifdef PIN_STATUS_LED
|
|
|
|
|
int cur_time = millis();
|
2025-09-03 17:22:11 +02:00
|
|
|
if (cur_time > next_led_change) {
|
|
|
|
|
if (led_state == 0) {
|
|
|
|
|
led_state = 1;
|
2025-03-10 17:11:55 +01:00
|
|
|
if (_msgcount > 0) {
|
2025-09-03 17:22:11 +02:00
|
|
|
last_led_increment = LED_ON_MSG_MILLIS;
|
2025-03-10 17:11:55 +01:00
|
|
|
} else {
|
2025-09-03 17:22:11 +02:00
|
|
|
last_led_increment = LED_ON_MILLIS;
|
2025-03-10 17:11:55 +01:00
|
|
|
}
|
2025-09-03 17:22:11 +02:00
|
|
|
next_led_change = cur_time + last_led_increment;
|
2025-03-10 17:11:55 +01:00
|
|
|
} else {
|
2025-09-03 17:22:11 +02:00
|
|
|
led_state = 0;
|
|
|
|
|
next_led_change = cur_time + LED_CYCLE_MILLIS - last_led_increment;
|
2025-03-10 17:11:55 +01:00
|
|
|
}
|
2025-11-20 10:58:14 +01:00
|
|
|
digitalWrite(PIN_STATUS_LED, led_state == LED_STATE_ON);
|
2025-03-10 17:11:55 +01:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
void UITask::setCurrScreen(UIScreen* c) {
|
|
|
|
|
curr = c;
|
2025-09-03 17:22:11 +02:00
|
|
|
_next_refresh = 100;
|
2025-08-08 20:01:31 +10:00
|
|
|
}
|
|
|
|
|
|
2025-09-04 21:45:42 +02:00
|
|
|
/*
|
|
|
|
|
hardware-agnostic pre-shutdown activity should be done here
|
2025-05-27 11:07:51 +12:00
|
|
|
*/
|
|
|
|
|
void UITask::shutdown(bool restart){
|
2026-05-10 18:23:25 +02:00
|
|
|
the_mesh.saveRTCTime();
|
2025-05-27 11:07:51 +12:00
|
|
|
|
|
|
|
|
#ifdef PIN_BUZZER
|
|
|
|
|
/* note: we have a choice here -
|
|
|
|
|
we can do a blocking buzzer.loop() with non-deterministic consequences
|
|
|
|
|
or we can set a flag and delay the shutdown for a couple of seconds
|
|
|
|
|
while a non-blocking buzzer.loop() plays out in UITask::loop()
|
|
|
|
|
*/
|
|
|
|
|
buzzer.shutdown();
|
|
|
|
|
uint32_t buzzer_timer = millis(); // fail-safe shutdown
|
2026-05-15 15:34:10 +02:00
|
|
|
while (buzzer.isPlaying() && (millis() - buzzer_timer) < 2500)
|
2025-05-27 11:07:51 +12:00
|
|
|
buzzer.loop();
|
|
|
|
|
|
|
|
|
|
#endif // PIN_BUZZER
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
if (restart) {
|
2025-05-27 11:07:51 +12:00
|
|
|
_board->reboot();
|
2025-08-08 20:01:31 +10:00
|
|
|
} else {
|
|
|
|
|
_display->turnOff();
|
2025-11-22 02:06:44 +01:00
|
|
|
radio_driver.powerOff();
|
2025-05-27 11:07:51 +12:00
|
|
|
_board->powerOff();
|
2025-08-08 20:01:31 +10:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool UITask::isButtonPressed() const {
|
|
|
|
|
#ifdef PIN_USER_BTN
|
|
|
|
|
return user_btn.isPressed();
|
|
|
|
|
#else
|
|
|
|
|
return false;
|
|
|
|
|
#endif
|
2025-05-27 11:07:51 +12:00
|
|
|
}
|
|
|
|
|
|
2026-05-17 18:32:49 +02:00
|
|
|
static void formatDashVal(uint8_t field, char* val, int val_len, uint16_t batt_mv,
|
|
|
|
|
CayenneLPP* lpp = nullptr) {
|
2026-05-17 10:04:28 +02:00
|
|
|
val[0] = '\0';
|
|
|
|
|
switch (field) {
|
|
|
|
|
case DASH_NONE: return;
|
|
|
|
|
case DASH_BATT:
|
|
|
|
|
if (batt_mv > 0) snprintf(val, val_len, "%u.%02uV", batt_mv/1000, (batt_mv%1000)/10);
|
|
|
|
|
else strcpy(val, "--");
|
|
|
|
|
return;
|
|
|
|
|
case DASH_NODES:
|
|
|
|
|
snprintf(val, val_len, "%d nodes", the_mesh.getNumContacts());
|
|
|
|
|
return;
|
|
|
|
|
#if ENV_INCLUDE_GPS == 1
|
|
|
|
|
case DASH_GPS: {
|
|
|
|
|
LocationProvider* loc = sensors.getLocationProvider();
|
|
|
|
|
if (loc && loc->isValid())
|
|
|
|
|
snprintf(val, val_len, "%.2f %.2f",
|
|
|
|
|
loc->getLatitude()/1000000.0f, loc->getLongitude()/1000000.0f);
|
|
|
|
|
else strcpy(val, "no fix");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
default: break;
|
|
|
|
|
}
|
2026-05-17 18:32:49 +02:00
|
|
|
// LPP sensor fields
|
2026-05-17 10:04:28 +02:00
|
|
|
uint8_t lpp_type = 0;
|
|
|
|
|
switch (field) {
|
|
|
|
|
case DASH_TEMP: lpp_type = LPP_TEMPERATURE; break;
|
|
|
|
|
case DASH_HUM: lpp_type = LPP_RELATIVE_HUMIDITY; break;
|
|
|
|
|
case DASH_PRES: lpp_type = LPP_BAROMETRIC_PRESSURE; break;
|
|
|
|
|
case DASH_ALT: lpp_type = LPP_ALTITUDE; break;
|
|
|
|
|
case DASH_LUX: lpp_type = LPP_LUMINOSITY; break;
|
|
|
|
|
case DASH_CO2: lpp_type = LPP_CONCENTRATION; break;
|
|
|
|
|
}
|
|
|
|
|
if (lpp_type) {
|
2026-05-17 18:32:49 +02:00
|
|
|
CayenneLPP local_lpp(200);
|
|
|
|
|
if (!lpp) { local_lpp.reset(); sensors.querySensors(0xFF, local_lpp); lpp = &local_lpp; }
|
|
|
|
|
LPPReader r(lpp->getBuffer(), lpp->getSize());
|
2026-05-17 10:04:28 +02:00
|
|
|
uint8_t ch, type;
|
|
|
|
|
while (r.readHeader(ch, type)) {
|
|
|
|
|
if (type == lpp_type) {
|
|
|
|
|
float v;
|
|
|
|
|
switch (lpp_type) {
|
|
|
|
|
case LPP_TEMPERATURE: r.readTemperature(v); snprintf(val, val_len, "%.1f\xf8""C", v); return;
|
|
|
|
|
case LPP_RELATIVE_HUMIDITY: r.readRelativeHumidity(v); snprintf(val, val_len, "%.0f%%", v); return;
|
|
|
|
|
case LPP_BAROMETRIC_PRESSURE: r.readPressure(v); snprintf(val, val_len, "%.0fhPa", v); return;
|
|
|
|
|
case LPP_ALTITUDE: r.readAltitude(v); snprintf(val, val_len, "%.0fm", v); return;
|
|
|
|
|
case LPP_LUMINOSITY: r.readLuminosity(v); snprintf(val, val_len, "%.0flux", v); return;
|
|
|
|
|
case LPP_CONCENTRATION: r.readConcentration(v); snprintf(val, val_len, "%.0fppm", v); return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
r.skipData(type);
|
|
|
|
|
}
|
|
|
|
|
strcpy(val, "--");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-10 17:11:55 +01:00
|
|
|
void UITask::loop() {
|
2025-08-08 20:01:31 +10:00
|
|
|
char c = 0;
|
2025-10-16 17:33:22 +11:00
|
|
|
#if UI_HAS_JOYSTICK
|
2026-05-23 15:20:40 +02:00
|
|
|
uint8_t joy_rot = _node_prefs ? _node_prefs->joystick_rotation : JOYSTICK_ROTATION;
|
2025-08-08 20:01:31 +10:00
|
|
|
int ev = user_btn.check();
|
|
|
|
|
if (ev == BUTTON_EVENT_CLICK) {
|
2026-05-17 10:09:54 +02:00
|
|
|
if (back_btn.isPressed()) {
|
|
|
|
|
// Enter clicked while Back is held — lock/unlock sequence
|
2026-05-17 11:17:59 +02:00
|
|
|
if (_display && !_display->isOn()) {
|
|
|
|
|
_display->turnOn(); // turn on display so hints are visible
|
|
|
|
|
}
|
|
|
|
|
_lock_wake_until = millis() + 5000; // keep display on during sequence
|
2026-05-17 10:09:54 +02:00
|
|
|
if (millis() - _lock_seq_ms > 3000) _lock_seq_count = 0; // timeout reset
|
|
|
|
|
_lock_seq_count++;
|
|
|
|
|
_lock_seq_ms = millis();
|
2026-05-17 11:17:59 +02:00
|
|
|
_next_refresh = 0; // update hint immediately on each press
|
2026-05-17 10:09:54 +02:00
|
|
|
if (_lock_seq_count >= 3) {
|
|
|
|
|
_lock_seq_count = 0;
|
2026-05-17 11:17:59 +02:00
|
|
|
_lock_seq_used = true; // suppress Back release click
|
2026-05-17 10:09:54 +02:00
|
|
|
_locked = !_locked;
|
|
|
|
|
if (_locked) {
|
|
|
|
|
_lock_wake_until = millis() + 2000;
|
|
|
|
|
} else {
|
2026-05-17 11:17:59 +02:00
|
|
|
if (_display && !_display->isOn()) _display->turnOn();
|
2026-05-17 10:09:54 +02:00
|
|
|
uint32_t aoff = autoOffMillis();
|
|
|
|
|
if (aoff > 0) _auto_off = millis() + aoff;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// eat the Enter — don't pass to curr
|
|
|
|
|
} else {
|
|
|
|
|
c = checkDisplayOn(KEY_ENTER);
|
|
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
2025-10-16 17:33:22 +11:00
|
|
|
c = handleLongPress(KEY_ENTER); // REVISIT: could be mapped to different key code
|
2025-08-08 20:01:31 +10:00
|
|
|
}
|
2026-05-10 15:18:35 +02:00
|
|
|
#if UI_HAS_JOYSTICK_UPDOWN
|
|
|
|
|
ev = joystick_up.check();
|
|
|
|
|
if (ev == BUTTON_EVENT_CLICK) {
|
2026-05-23 15:20:40 +02:00
|
|
|
c = checkDisplayOn(rotateJoystickKey(KEY_UP, joy_rot));
|
2026-05-10 15:18:35 +02:00
|
|
|
}
|
|
|
|
|
ev = joystick_down.check();
|
|
|
|
|
if (ev == BUTTON_EVENT_CLICK) {
|
2026-05-23 15:20:40 +02:00
|
|
|
c = checkDisplayOn(rotateJoystickKey(KEY_DOWN, joy_rot));
|
2026-05-10 15:18:35 +02:00
|
|
|
}
|
|
|
|
|
#endif
|
2025-08-17 19:01:28 +10:00
|
|
|
ev = joystick_left.check();
|
|
|
|
|
if (ev == BUTTON_EVENT_CLICK) {
|
2026-05-23 15:20:40 +02:00
|
|
|
c = checkDisplayOn(rotateJoystickKey(KEY_LEFT, joy_rot));
|
2025-08-17 19:01:28 +10:00
|
|
|
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
2026-05-23 15:20:40 +02:00
|
|
|
c = handleLongPress(rotateJoystickKey(KEY_LEFT, joy_rot));
|
2025-08-17 19:01:28 +10:00
|
|
|
}
|
|
|
|
|
ev = joystick_right.check();
|
|
|
|
|
if (ev == BUTTON_EVENT_CLICK) {
|
2026-05-23 15:20:40 +02:00
|
|
|
c = checkDisplayOn(rotateJoystickKey(KEY_RIGHT, joy_rot));
|
2025-08-17 19:01:28 +10:00
|
|
|
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
2026-05-23 15:20:40 +02:00
|
|
|
c = handleLongPress(rotateJoystickKey(KEY_RIGHT, joy_rot));
|
2025-08-17 19:01:28 +10:00
|
|
|
}
|
2026-05-17 12:17:02 +02:00
|
|
|
if (_lock_seq_used && millis() - _lock_seq_ms > 5000) {
|
|
|
|
|
_lock_seq_used = false; // safety reset if Back release event was missed
|
|
|
|
|
}
|
2025-10-18 13:37:18 +02:00
|
|
|
ev = back_btn.check();
|
2026-05-10 15:18:35 +02:00
|
|
|
if (ev == BUTTON_EVENT_CLICK) {
|
2026-05-17 11:17:59 +02:00
|
|
|
if (_lock_seq_count > 0 || _lock_seq_used) {
|
|
|
|
|
// Back released mid-sequence or after completing it — cancel/suppress
|
2026-05-17 09:55:51 +02:00
|
|
|
_lock_seq_count = 0;
|
2026-05-17 11:17:59 +02:00
|
|
|
_lock_seq_used = false;
|
2026-05-17 10:09:54 +02:00
|
|
|
} else {
|
|
|
|
|
c = checkDisplayOn(KEY_CANCEL);
|
2026-05-17 09:55:51 +02:00
|
|
|
}
|
2026-05-10 15:18:35 +02:00
|
|
|
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
2026-05-17 11:17:59 +02:00
|
|
|
if (!_locked) c = handleTripleClick(KEY_SELECT);
|
2025-10-18 13:37:18 +02:00
|
|
|
}
|
2025-10-16 17:33:22 +11:00
|
|
|
#elif defined(PIN_USER_BTN)
|
|
|
|
|
int ev = user_btn.check();
|
|
|
|
|
if (ev == BUTTON_EVENT_CLICK) {
|
|
|
|
|
c = checkDisplayOn(KEY_NEXT);
|
|
|
|
|
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
|
|
|
|
c = handleLongPress(KEY_ENTER);
|
|
|
|
|
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
|
|
|
|
c = handleDoubleClick(KEY_PREV);
|
|
|
|
|
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
|
|
|
|
c = handleTripleClick(KEY_SELECT);
|
|
|
|
|
}
|
2025-08-17 19:01:28 +10:00
|
|
|
#endif
|
2025-09-01 17:11:55 +10:00
|
|
|
#if defined(PIN_USER_BTN_ANA)
|
2025-10-31 13:04:59 +00:00
|
|
|
if (abs(millis() - _analogue_pin_read_millis) > 10) {
|
|
|
|
|
ev = analog_btn.check();
|
|
|
|
|
if (ev == BUTTON_EVENT_CLICK) {
|
|
|
|
|
c = checkDisplayOn(KEY_NEXT);
|
|
|
|
|
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
|
|
|
|
c = handleLongPress(KEY_ENTER);
|
|
|
|
|
} else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
|
|
|
|
c = handleDoubleClick(KEY_PREV);
|
|
|
|
|
} else if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
|
|
|
|
|
c = handleTripleClick(KEY_SELECT);
|
|
|
|
|
}
|
|
|
|
|
_analogue_pin_read_millis = millis();
|
2025-09-01 17:11:55 +10:00
|
|
|
}
|
|
|
|
|
#endif
|
2025-11-28 10:33:19 +01:00
|
|
|
#if defined(BACKLIGHT_BTN)
|
2025-09-03 17:22:11 +02:00
|
|
|
if (millis() > next_backlight_btn_check) {
|
2025-09-02 11:43:48 +02:00
|
|
|
bool touch_state = digitalRead(PIN_BUTTON2);
|
2025-11-28 10:33:19 +01:00
|
|
|
#if defined(DISP_BACKLIGHT)
|
2025-09-02 11:43:48 +02:00
|
|
|
digitalWrite(DISP_BACKLIGHT, !touch_state);
|
2025-11-28 10:33:19 +01:00
|
|
|
#elif defined(EXP_PIN_BACKLIGHT)
|
|
|
|
|
expander.digitalWrite(EXP_PIN_BACKLIGHT, !touch_state);
|
|
|
|
|
#endif
|
2025-09-03 17:22:11 +02:00
|
|
|
next_backlight_btn_check = millis() + 300;
|
2025-09-02 11:43:48 +02:00
|
|
|
}
|
|
|
|
|
#endif
|
2025-08-08 20:01:31 +10:00
|
|
|
|
2026-05-17 09:55:51 +02:00
|
|
|
if (c != 0) {
|
2026-05-17 10:09:54 +02:00
|
|
|
if (!_locked && curr) {
|
2026-05-17 09:55:51 +02:00
|
|
|
curr->handleInput(c);
|
|
|
|
|
{ uint32_t aoff = autoOffMillis(); if (aoff > 0) _auto_off = millis() + aoff; } // extend auto-off timer
|
|
|
|
|
_next_refresh = 100; // trigger refresh
|
|
|
|
|
} else if (_locked) {
|
2026-05-17 14:30:53 +02:00
|
|
|
// Locked: eat all keys — wake window is set only when display first turns on
|
2026-05-17 09:55:51 +02:00
|
|
|
_next_refresh = 0;
|
|
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
}
|
|
|
|
|
|
2025-03-10 17:11:55 +01:00
|
|
|
userLedHandler();
|
2025-03-04 23:09:43 +11:00
|
|
|
|
2025-05-20 11:52:55 +10:00
|
|
|
#ifdef PIN_BUZZER
|
2026-05-12 20:12:54 +02:00
|
|
|
if (_node_prefs && _node_prefs->buzzer_auto) {
|
|
|
|
|
bool should_quiet = hasConnection();
|
|
|
|
|
if (buzzer.isQuiet() != should_quiet) {
|
|
|
|
|
buzzer.quiet(should_quiet);
|
|
|
|
|
_next_refresh = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-05-20 11:52:55 +10:00
|
|
|
if (buzzer.isPlaying()) buzzer.loop();
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
if (curr) curr->poll();
|
|
|
|
|
|
2025-03-10 17:11:55 +01:00
|
|
|
if (_display != NULL && _display->isOn()) {
|
2026-05-17 09:55:51 +02:00
|
|
|
if (_locked && millis() > _lock_wake_until) {
|
|
|
|
|
_display->turnOff();
|
|
|
|
|
} else if (_locked && millis() >= _next_refresh) {
|
|
|
|
|
_display->startFrame();
|
|
|
|
|
// Lock screen: clock + unlock hint popup
|
|
|
|
|
uint32_t unix_ts = rtc_clock.getCurrentTime();
|
|
|
|
|
_display->setColor(DisplayDriver::LIGHT);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
_display->setTextSize(1);
|
|
|
|
|
const int lk_lh = _display->getLineHeight();
|
|
|
|
|
const int lk_step = _display->lineStep();
|
2026-05-17 09:55:51 +02:00
|
|
|
if (unix_ts < 1000000000UL) {
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
_display->drawTextCentered(_display->width() / 2, _display->height() / 2 - lk_step, "No time sync");
|
2026-05-17 09:55:51 +02:00
|
|
|
} else {
|
|
|
|
|
int8_t tz = _node_prefs ? _node_prefs->tz_offset_hours : 0;
|
|
|
|
|
unix_ts += (int32_t)tz * 3600;
|
|
|
|
|
time_t t = (time_t)unix_ts;
|
|
|
|
|
struct tm* ti = gmtime(&t);
|
|
|
|
|
char buf[12];
|
|
|
|
|
_display->setTextSize(2);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
const int lh2 = _display->getLineHeight(); // sz2 line height
|
|
|
|
|
const int clk_y = 2;
|
2026-05-21 21:04:43 +02:00
|
|
|
if (_node_prefs && _node_prefs->clock_12h) {
|
|
|
|
|
int h = ti->tm_hour % 12; if (h == 0) h = 12;
|
|
|
|
|
sprintf(buf, "%d:%02d %s", h, ti->tm_min, ti->tm_hour < 12 ? "AM" : "PM");
|
|
|
|
|
} else {
|
|
|
|
|
sprintf(buf, "%02d:%02d", ti->tm_hour, ti->tm_min);
|
|
|
|
|
}
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
_display->drawTextCentered(_display->width() / 2, clk_y, buf);
|
2026-05-17 09:55:51 +02:00
|
|
|
_display->setTextSize(1);
|
|
|
|
|
static const char* wd[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
|
|
|
|
|
static const char* mo[] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
|
|
|
|
|
sprintf(buf, "%s %d %s", wd[ti->tm_wday], ti->tm_mday, mo[ti->tm_mon]);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int date_y = clk_y + lh2 + 2;
|
|
|
|
|
_display->drawTextCentered(_display->width() / 2, date_y, buf);
|
2026-05-17 10:04:28 +02:00
|
|
|
|
|
|
|
|
// Two sensor values side by side (dashboard_fields[0] and [1])
|
|
|
|
|
if (_node_prefs) {
|
|
|
|
|
char v0[20] = "", v1[20] = "";
|
2026-05-17 18:32:49 +02:00
|
|
|
CayenneLPP shared_lpp(200);
|
|
|
|
|
CayenneLPP* lpp_ptr = nullptr;
|
|
|
|
|
uint8_t f0 = _node_prefs->dashboard_fields[0], f1 = _node_prefs->dashboard_fields[1];
|
|
|
|
|
auto isLPP = [](uint8_t f) {
|
|
|
|
|
return f==DASH_TEMP||f==DASH_HUM||f==DASH_PRES||f==DASH_ALT||f==DASH_LUX||f==DASH_CO2;
|
|
|
|
|
};
|
|
|
|
|
if (isLPP(f0) || isLPP(f1)) {
|
|
|
|
|
shared_lpp.reset(); sensors.querySensors(0xFF, shared_lpp); lpp_ptr = &shared_lpp;
|
|
|
|
|
}
|
|
|
|
|
formatDashVal(f0, v0, sizeof(v0), _batt_mv, lpp_ptr);
|
|
|
|
|
formatDashVal(f1, v1, sizeof(v1), _batt_mv, lpp_ptr);
|
2026-05-17 10:04:28 +02:00
|
|
|
if (v0[0] || v1[0]) {
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int sv_y = date_y + lk_step;
|
2026-05-17 10:04:28 +02:00
|
|
|
_display->setColor(DisplayDriver::LIGHT);
|
|
|
|
|
if (v0[0] && v1[0]) {
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
_display->setCursor(0, sv_y);
|
2026-05-17 10:04:28 +02:00
|
|
|
_display->print(v0);
|
|
|
|
|
int vw = _display->getTextWidth(v1);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
_display->setCursor(_display->width() - vw, sv_y);
|
2026-05-17 10:04:28 +02:00
|
|
|
_display->print(v1);
|
|
|
|
|
} else {
|
|
|
|
|
const char* sv = v0[0] ? v0 : v1;
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
_display->drawTextCentered(_display->width() / 2, sv_y, sv);
|
2026-05-17 10:04:28 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-17 09:55:51 +02:00
|
|
|
}
|
|
|
|
|
// Hint popup at bottom (like alert style)
|
|
|
|
|
_display->setTextSize(1);
|
2026-05-17 10:09:54 +02:00
|
|
|
const char* hint = _lock_seq_count == 0 ? "Hold Back + 3xEnter" :
|
|
|
|
|
_lock_seq_count == 1 ? "Enter x2 more..." : "Enter x1 more...";
|
2026-05-17 09:55:51 +02:00
|
|
|
int p = 3;
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int hy = _display->height() - lk_lh - p * 2;
|
2026-05-17 09:55:51 +02:00
|
|
|
int hw = _display->getTextWidth(hint);
|
|
|
|
|
int hx = (_display->width() - hw) / 2;
|
|
|
|
|
_display->setColor(DisplayDriver::LIGHT);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
_display->fillRect(hx - p, hy - p, hw + p*2, lk_lh + p*2);
|
2026-05-17 09:55:51 +02:00
|
|
|
_display->setColor(DisplayDriver::DARK);
|
|
|
|
|
_display->setCursor(hx, hy);
|
|
|
|
|
_display->print(hint);
|
|
|
|
|
_display->endFrame();
|
2026-05-22 10:47:19 +02:00
|
|
|
#ifdef EINK_DISPLAY_MODEL
|
2026-05-23 10:06:22 +02:00
|
|
|
_next_refresh = millis() + 30000;
|
2026-05-22 10:47:19 +02:00
|
|
|
#else
|
2026-05-17 09:55:51 +02:00
|
|
|
_next_refresh = millis() + 1000;
|
2026-05-22 10:47:19 +02:00
|
|
|
#endif
|
2026-05-17 09:55:51 +02:00
|
|
|
} else if (!_locked && millis() >= _next_refresh && curr) {
|
2025-03-04 23:09:43 +11:00
|
|
|
_display->startFrame();
|
2025-08-08 20:01:31 +10:00
|
|
|
int delay_millis = curr->render(*_display);
|
2026-05-11 17:19:03 +02:00
|
|
|
if (millis() < _alert_expiry && curr == home) { // render alert only on home screen
|
2025-08-08 20:01:31 +10:00
|
|
|
_display->setTextSize(1);
|
2026-05-22 23:13:27 +02:00
|
|
|
int lh = _display->getLineHeight();
|
|
|
|
|
int pad = 3;
|
|
|
|
|
int box_h = lh + pad * 2;
|
|
|
|
|
int box_w = _display->width() - 8;
|
|
|
|
|
int box_x = 4;
|
|
|
|
|
int box_y = (_display->height() - box_h) / 2;
|
2025-08-08 20:01:31 +10:00
|
|
|
_display->setColor(DisplayDriver::DARK);
|
2026-05-22 23:13:27 +02:00
|
|
|
_display->fillRect(box_x, box_y, box_w, box_h);
|
|
|
|
|
_display->setColor(DisplayDriver::LIGHT);
|
|
|
|
|
_display->drawRect(box_x, box_y, box_w, box_h);
|
|
|
|
|
_display->drawTextCentered(_display->width() / 2, box_y + pad, _alert);
|
2025-08-08 20:01:31 +10:00
|
|
|
_next_refresh = _alert_expiry; // will need refresh when alert is dismissed
|
|
|
|
|
} else {
|
|
|
|
|
_next_refresh = millis() + delay_millis;
|
|
|
|
|
}
|
2025-03-04 23:09:43 +11:00
|
|
|
_display->endFrame();
|
|
|
|
|
}
|
2025-09-03 18:17:37 +02:00
|
|
|
#if AUTO_OFF_MILLIS > 0
|
2026-05-17 09:55:51 +02:00
|
|
|
if (!_locked && autoOffMillis() > 0 && millis() > _auto_off) {
|
2025-03-04 23:09:43 +11:00
|
|
|
_display->turnOff();
|
2026-05-10 18:01:59 +02:00
|
|
|
#ifdef PIN_LED
|
|
|
|
|
digitalWrite(PIN_LED, LOW); // turn off status LED with display to save power
|
|
|
|
|
#endif
|
2026-05-17 09:55:51 +02:00
|
|
|
if (_node_prefs && _node_prefs->auto_lock) {
|
|
|
|
|
_locked = true;
|
|
|
|
|
_lock_wake_until = 0;
|
|
|
|
|
}
|
2025-03-04 23:09:43 +11:00
|
|
|
}
|
2025-09-03 18:17:37 +02:00
|
|
|
#endif
|
2025-03-04 23:09:43 +11:00
|
|
|
}
|
2025-05-27 19:10:56 -07:00
|
|
|
|
2025-09-07 15:16:15 +08:00
|
|
|
#ifdef PIN_VIBRATION
|
|
|
|
|
vibration.loop();
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
if (millis() > next_batt_chck) {
|
2026-05-10 15:18:35 +02:00
|
|
|
uint16_t raw = AbstractUITask::getBattMilliVolts();
|
|
|
|
|
if (raw > 0) {
|
|
|
|
|
// EMA filter: alpha=0.2 (80% old, 20% new) — smooths ADC noise from uneven load
|
|
|
|
|
_batt_mv = (_batt_mv == 0) ? raw : (uint16_t)((_batt_mv * 4u + raw) / 5u);
|
|
|
|
|
}
|
|
|
|
|
uint16_t low_mv = _node_prefs ? _node_prefs->low_batt_mv : 0;
|
|
|
|
|
if (low_mv > 0 && _batt_mv > 0 && _batt_mv < low_mv) {
|
2025-08-30 23:09:01 +12:00
|
|
|
if (_display != NULL) {
|
|
|
|
|
_display->startFrame();
|
2026-05-16 17:47:22 +02:00
|
|
|
_display->setTextSize(1);
|
2026-05-10 15:18:35 +02:00
|
|
|
_display->setColor(DisplayDriver::LIGHT);
|
refactor: replace all hardcoded pixel positions with layout helpers
Add lineStep(), headerH(), listStart(), listVisible(), valCol() to
DisplayDriver so layout derives from getLineHeight() instead of fixed
OLED constants. Refactor all screens (SettingsScreen, NearbyScreen,
FullscreenMsgView, QuickMsgScreen, BotScreen, DashboardConfigScreen,
AutoAdvertScreen, RingtoneEditorScreen) and all HomeScreen pages plus
the lock screen and splash screen in UITask.cpp.
On OLED (lh=8) positions are unchanged; on landscape e-ink (lh=16) all
text and widgets now scale correctly to the 250×122 display.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 18:05:09 +02:00
|
|
|
int mid = _display->height() / 2;
|
|
|
|
|
int step = _display->lineStep();
|
|
|
|
|
_display->drawTextCentered(_display->width() / 2, mid - step, "Low Battery");
|
|
|
|
|
_display->drawTextCentered(_display->width() / 2, mid, "Shutting down");
|
2025-08-30 23:09:01 +12:00
|
|
|
_display->endFrame();
|
2026-05-10 15:18:35 +02:00
|
|
|
delay(2000);
|
2025-08-30 23:09:01 +12:00
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
shutdown();
|
2025-05-27 19:10:56 -07:00
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
next_batt_chck = millis() + 8000;
|
2025-05-27 19:10:56 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
char UITask::checkDisplayOn(char c) {
|
2025-05-27 19:10:56 -07:00
|
|
|
if (_display != NULL) {
|
2025-08-08 20:01:31 +10:00
|
|
|
if (!_display->isOn()) {
|
2026-05-10 18:01:59 +02:00
|
|
|
_display->turnOn();
|
|
|
|
|
#ifdef PIN_LED
|
|
|
|
|
digitalWrite(PIN_LED, LOW); // ensure LED is off when waking display (userLedHandler takes over)
|
|
|
|
|
#endif
|
2026-05-17 09:55:51 +02:00
|
|
|
if (_locked) {
|
|
|
|
|
_lock_wake_until = millis() + 5000;
|
|
|
|
|
_next_refresh = 0;
|
|
|
|
|
return 0; // eat the waking key press
|
|
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
c = 0;
|
2025-05-27 19:10:56 -07:00
|
|
|
}
|
2026-05-17 09:55:51 +02:00
|
|
|
if (!_locked) {
|
|
|
|
|
uint32_t aoff = autoOffMillis();
|
|
|
|
|
if (aoff > 0) _auto_off = millis() + aoff; // extend auto-off timer
|
|
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
_next_refresh = 0; // trigger refresh
|
2025-05-27 19:10:56 -07:00
|
|
|
}
|
2025-08-08 20:01:31 +10:00
|
|
|
return c;
|
2025-05-27 19:10:56 -07:00
|
|
|
}
|
|
|
|
|
|
2025-08-08 20:01:31 +10:00
|
|
|
char UITask::handleLongPress(char c) {
|
|
|
|
|
if (millis() - ui_started_at < 8000) { // long press in first 8 seconds since startup -> CLI/rescue
|
|
|
|
|
the_mesh.enterCLIRescue();
|
2026-05-11 18:56:26 +02:00
|
|
|
return 0;
|
2025-05-29 13:06:24 -07:00
|
|
|
}
|
2026-05-11 18:56:26 +02:00
|
|
|
if (c == KEY_ENTER) return KEY_CONTEXT_MENU;
|
2025-08-08 20:01:31 +10:00
|
|
|
return c;
|
2025-05-27 19:10:56 -07:00
|
|
|
}
|
|
|
|
|
|
2025-09-03 08:25:59 +10:00
|
|
|
char UITask::handleDoubleClick(char c) {
|
|
|
|
|
MESH_DEBUG_PRINTLN("UITask: double click triggered");
|
2025-09-03 08:31:38 +10:00
|
|
|
checkDisplayOn(c);
|
2025-09-03 08:25:59 +10:00
|
|
|
return c;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char UITask::handleTripleClick(char c) {
|
|
|
|
|
MESH_DEBUG_PRINTLN("UITask: triple click triggered");
|
2025-09-03 08:31:38 +10:00
|
|
|
checkDisplayOn(c);
|
2025-09-03 08:25:59 +10:00
|
|
|
toggleBuzzer();
|
|
|
|
|
c = 0;
|
|
|
|
|
return c;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-23 10:39:43 +02:00
|
|
|
bool UITask::getGPSState() {
|
|
|
|
|
if (_sensors != NULL) {
|
|
|
|
|
int num = _sensors->getNumSettings();
|
|
|
|
|
for (int i = 0; i < num; i++) {
|
|
|
|
|
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
|
|
|
|
return !strcmp(_sensors->getSettingValue(i), "1");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 08:25:59 +10:00
|
|
|
void UITask::toggleGPS() {
|
|
|
|
|
if (_sensors != NULL) {
|
|
|
|
|
// toggle GPS on/off
|
|
|
|
|
int num = _sensors->getNumSettings();
|
|
|
|
|
for (int i = 0; i < num; i++) {
|
|
|
|
|
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
|
|
|
|
if (strcmp(_sensors->getSettingValue(i), "1") == 0) {
|
|
|
|
|
_sensors->setSettingValue("gps", "0");
|
2025-11-29 16:37:10 +08:00
|
|
|
_node_prefs->gps_enabled = 0;
|
2025-09-17 08:53:50 +08:00
|
|
|
notify(UIEventType::ack);
|
2025-09-03 08:25:59 +10:00
|
|
|
} else {
|
|
|
|
|
_sensors->setSettingValue("gps", "1");
|
2025-11-29 16:37:10 +08:00
|
|
|
_node_prefs->gps_enabled = 1;
|
2025-09-17 08:53:50 +08:00
|
|
|
notify(UIEventType::ack);
|
2025-09-03 08:25:59 +10:00
|
|
|
}
|
2025-11-29 16:37:10 +08:00
|
|
|
the_mesh.savePrefs();
|
2025-12-11 09:26:09 +01:00
|
|
|
showAlert(_node_prefs->gps_enabled ? "GPS: Enabled" : "GPS: Disabled", 800);
|
2025-09-03 08:25:59 +10:00
|
|
|
_next_refresh = 0;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
void UITask::applyTxPower() {
|
|
|
|
|
if (_node_prefs == NULL) return;
|
|
|
|
|
radio_set_tx_power(_node_prefs->tx_power_dbm);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void UITask::applyGPSInterval() {
|
|
|
|
|
if (_node_prefs == NULL) return;
|
|
|
|
|
char buf[12];
|
|
|
|
|
sprintf(buf, "%u", _node_prefs->gps_interval);
|
|
|
|
|
sensors.setSettingValue("gps_interval", buf);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void UITask::applyBrightness() {
|
|
|
|
|
if (_display != NULL && _node_prefs != NULL) {
|
|
|
|
|
_display->setBrightness(_node_prefs->display_brightness);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-20 09:29:38 +02:00
|
|
|
void UITask::applyFont() {
|
|
|
|
|
if (_display != NULL && _node_prefs != NULL) {
|
|
|
|
|
_display->setLemonFont(_node_prefs->use_lemon_font != 0);
|
2026-05-20 09:36:27 +02:00
|
|
|
_next_refresh = 0;
|
2026-05-20 09:29:38 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-20 21:53:40 +02:00
|
|
|
void UITask::applyRotation() {
|
|
|
|
|
if (_display != NULL && _node_prefs != NULL) {
|
|
|
|
|
_display->setDisplayRotation(_node_prefs->display_rotation);
|
|
|
|
|
_next_refresh = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
void UITask::setBrightnessLevel(uint8_t level) {
|
|
|
|
|
if (_node_prefs == NULL) return;
|
|
|
|
|
if (level > 4) level = 4;
|
|
|
|
|
_node_prefs->display_brightness = level;
|
|
|
|
|
applyBrightness();
|
|
|
|
|
_next_refresh = 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 09:17:37 +02:00
|
|
|
void UITask::setBuzzerVolumeLevel(uint8_t level) {
|
|
|
|
|
#ifdef PIN_BUZZER
|
|
|
|
|
if (_node_prefs == NULL) return;
|
|
|
|
|
if (level > 4) level = 4;
|
|
|
|
|
_node_prefs->buzzer_volume = level;
|
|
|
|
|
buzzer.setVolume(level);
|
2026-05-20 09:06:00 +02:00
|
|
|
if (level > 0) buzzer.playForced("Vol:d=16,o=6,b=120:c");
|
2026-05-12 09:17:37 +02:00
|
|
|
_next_refresh = 0;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 08:25:59 +10:00
|
|
|
void UITask::toggleBuzzer() {
|
2025-05-27 19:10:56 -07:00
|
|
|
#ifdef PIN_BUZZER
|
2026-05-12 20:12:54 +02:00
|
|
|
if (_node_prefs) _node_prefs->buzzer_auto = 0; // exit auto mode
|
2025-05-30 22:55:53 -07:00
|
|
|
if (buzzer.isQuiet()) {
|
|
|
|
|
buzzer.quiet(false);
|
2025-09-17 08:53:50 +08:00
|
|
|
notify(UIEventType::ack);
|
2025-05-30 22:55:53 -07:00
|
|
|
} else {
|
|
|
|
|
buzzer.quiet(true);
|
|
|
|
|
}
|
2026-05-12 20:12:54 +02:00
|
|
|
if (_node_prefs) _node_prefs->buzzer_quiet = buzzer.isQuiet();
|
2025-11-20 18:55:39 -08:00
|
|
|
the_mesh.savePrefs();
|
2025-12-11 09:26:09 +01:00
|
|
|
showAlert(buzzer.isQuiet() ? "Buzzer: OFF" : "Buzzer: ON", 800);
|
2026-05-12 20:12:54 +02:00
|
|
|
_next_refresh = 0;
|
2025-05-27 19:10:56 -07:00
|
|
|
#endif
|
|
|
|
|
}
|
2026-05-12 20:12:54 +02:00
|
|
|
|
|
|
|
|
int UITask::getBuzzerMode() {
|
|
|
|
|
#ifdef PIN_BUZZER
|
|
|
|
|
if (_node_prefs && _node_prefs->buzzer_auto) return 2;
|
|
|
|
|
return buzzer.isQuiet() ? 1 : 0;
|
|
|
|
|
#else
|
|
|
|
|
return 1;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void UITask::cycleBuzzerMode() {
|
|
|
|
|
#ifdef PIN_BUZZER
|
|
|
|
|
if (!_node_prefs) return;
|
|
|
|
|
int mode = getBuzzerMode();
|
|
|
|
|
mode = (mode + 1) % 3; // ON(0) → OFF(1) → Auto(2) → ON
|
|
|
|
|
_node_prefs->buzzer_auto = (mode == 2) ? 1 : 0;
|
|
|
|
|
if (mode == 0) { buzzer.quiet(false); _node_prefs->buzzer_quiet = 0; notify(UIEventType::ack); }
|
|
|
|
|
if (mode == 1) { buzzer.quiet(true); _node_prefs->buzzer_quiet = 1; }
|
|
|
|
|
if (mode == 2) { buzzer.quiet(hasConnection()); }
|
|
|
|
|
static const char* labels[] = { "Buzzer: ON", "Buzzer: OFF", "Buzzer: Auto" };
|
|
|
|
|
showAlert(labels[mode], 800);
|
|
|
|
|
_next_refresh = 0;
|
|
|
|
|
#endif
|
|
|
|
|
}
|