mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
Add hardware watchdog and QuickMsgScreen
Watchdog: nRF52840 hardware WDT with 30s timeout, runs during sleep. Resets device if main loop stalls for >30s. Pets in every loop() iteration so normal operation never triggers it. QuickMsgScreen: new UI screen accessible from home screen pages. Two-phase UI: contact picker (shows all contacts with hop count), then predefined message picker (location, I'm OK, need help, on my way, ETA 10min, ETA 30min). Location message uses live GPS fix if available. Sends via the_mesh.sendMessage() and shows sent/error alert. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -221,9 +221,19 @@ void setup() {
|
||||
#ifdef DISPLAY_CLASS
|
||||
ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved
|
||||
#endif
|
||||
|
||||
#ifdef NRF52_PLATFORM
|
||||
NRF_WDT->CONFIG = 0x01; // run during sleep; pause during debug halt
|
||||
NRF_WDT->CRV = 32768*30-1; // 30 second timeout
|
||||
NRF_WDT->RREN = 0x01; // enable reload register 0
|
||||
NRF_WDT->TASKS_START = 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
void loop() {
|
||||
#ifdef NRF52_PLATFORM
|
||||
NRF_WDT->RR[0] = 0x6E524635UL; // pet watchdog
|
||||
#endif
|
||||
the_mesh.loop();
|
||||
sensors.loop();
|
||||
#ifdef DISPLAY_CLASS
|
||||
|
||||
@@ -330,6 +330,184 @@ const uint16_t SettingsScreen::LOW_BAT_OPTS[7] = { 0, 3000, 3100, 3200, 3300,
|
||||
const char* SettingsScreen::LOW_BAT_LABELS[7] = { "off", "3.0V", "3.1V", "3.2V", "3.3V", "3.4V", "3.5V" };
|
||||
const char* SettingsScreen::BATT_DISPLAY_LABELS[3] = { "icon", "%", "V" };
|
||||
|
||||
static const char* const QUICK_MSG_LABELS[] = {
|
||||
"My location",
|
||||
"I'm OK",
|
||||
"Need help!",
|
||||
"On my way",
|
||||
"ETA 10min",
|
||||
"ETA 30min",
|
||||
};
|
||||
static const int QUICK_MSG_COUNT = 6;
|
||||
|
||||
class QuickMsgScreen : public UIScreen {
|
||||
UITask* _task;
|
||||
|
||||
enum Phase { CONTACT_PICK, MSG_PICK };
|
||||
Phase _phase;
|
||||
int _contact_sel, _contact_scroll;
|
||||
int _msg_sel, _msg_scroll;
|
||||
int _num_contacts;
|
||||
ContactInfo _sel_contact;
|
||||
|
||||
static const int VISIBLE = 4;
|
||||
static const int ITEM_H = 12;
|
||||
static const int START_Y = 12;
|
||||
|
||||
void buildLocMsg(char* buf, int len) {
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
LocationProvider* loc = sensors.getLocationProvider();
|
||||
if (loc && loc->isValid()) {
|
||||
snprintf(buf, len, "Loc: %.5f,%.5f",
|
||||
loc->getLatitude() / 1000000.0,
|
||||
loc->getLongitude() / 1000000.0);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
snprintf(buf, len, "Loc: no GPS fix");
|
||||
}
|
||||
|
||||
void renderScrollHints(DisplayDriver& display, int scroll, int count) {
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
if (scroll > 0) {
|
||||
display.setCursor(display.width() - 6, START_Y);
|
||||
display.print("^");
|
||||
}
|
||||
if (scroll + VISIBLE < count) {
|
||||
display.setCursor(display.width() - 6, START_Y + (VISIBLE-1)*ITEM_H);
|
||||
display.print("v");
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
QuickMsgScreen(UITask* task)
|
||||
: _task(task), _phase(CONTACT_PICK),
|
||||
_contact_sel(0), _contact_scroll(0),
|
||||
_msg_sel(0), _msg_scroll(0), _num_contacts(0) {}
|
||||
|
||||
void reset() {
|
||||
_phase = CONTACT_PICK;
|
||||
_contact_sel = _contact_scroll = 0;
|
||||
_msg_sel = _msg_scroll = 0;
|
||||
_num_contacts = the_mesh.getNumContacts();
|
||||
}
|
||||
|
||||
int render(DisplayDriver& display) override {
|
||||
display.setTextSize(1);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
|
||||
if (_phase == CONTACT_PICK) {
|
||||
display.drawTextCentered(display.width()/2, 0, "SELECT CONTACT");
|
||||
display.fillRect(0, 10, display.width(), 1);
|
||||
|
||||
if (_num_contacts == 0) {
|
||||
display.setColor(DisplayDriver::YELLOW);
|
||||
display.drawTextCentered(display.width()/2, 32, "No contacts");
|
||||
return 5000;
|
||||
}
|
||||
|
||||
for (int i = 0; i < VISIBLE && (_contact_scroll+i) < _num_contacts; i++) {
|
||||
int idx = _contact_scroll + i;
|
||||
bool sel = (idx == _contact_sel);
|
||||
int y = START_Y + i * ITEM_H;
|
||||
display.setColor(sel ? DisplayDriver::YELLOW : DisplayDriver::LIGHT);
|
||||
display.setCursor(0, y);
|
||||
display.print(sel ? ">" : " ");
|
||||
|
||||
ContactInfo c;
|
||||
if (the_mesh.getContactByIdx(idx, c)) {
|
||||
char filtered[sizeof(c.name)];
|
||||
display.translateUTF8ToBlocks(filtered, c.name, sizeof(filtered));
|
||||
display.drawTextEllipsized(8, y, display.width() - 24, filtered);
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
char hop[5];
|
||||
snprintf(hop, sizeof(hop), c.path_len == 0xFF ? "D" : "%dh", (int)c.path_len);
|
||||
display.setCursor(display.width() - display.getTextWidth(hop) - 1, y);
|
||||
display.print(hop);
|
||||
}
|
||||
}
|
||||
renderScrollHints(display, _contact_scroll, _num_contacts);
|
||||
|
||||
} else {
|
||||
char title[24];
|
||||
snprintf(title, sizeof(title), "TO:%.14s", _sel_contact.name);
|
||||
display.drawTextCentered(display.width()/2, 0, title);
|
||||
display.fillRect(0, 10, display.width(), 1);
|
||||
|
||||
for (int i = 0; i < VISIBLE && (_msg_scroll+i) < QUICK_MSG_COUNT; i++) {
|
||||
int idx = _msg_scroll + i;
|
||||
bool sel = (idx == _msg_sel);
|
||||
int y = START_Y + i * ITEM_H;
|
||||
display.setColor(sel ? DisplayDriver::YELLOW : DisplayDriver::LIGHT);
|
||||
display.setCursor(0, y);
|
||||
display.print(sel ? ">" : " ");
|
||||
|
||||
if (idx == 0) {
|
||||
char loc[36];
|
||||
buildLocMsg(loc, sizeof(loc));
|
||||
display.drawTextEllipsized(8, y, display.width() - 10, loc);
|
||||
} else {
|
||||
display.setCursor(8, y);
|
||||
display.print(QUICK_MSG_LABELS[idx]);
|
||||
}
|
||||
}
|
||||
renderScrollHints(display, _msg_scroll, QUICK_MSG_COUNT);
|
||||
}
|
||||
return 300;
|
||||
}
|
||||
|
||||
bool handleInput(char c) override {
|
||||
if (_phase == CONTACT_PICK) {
|
||||
if (c == KEY_CANCEL) { _task->gotoHomeScreen(); return true; }
|
||||
if ((c == KEY_UP) && _contact_sel > 0) {
|
||||
_contact_sel--;
|
||||
if (_contact_sel < _contact_scroll) _contact_scroll = _contact_sel;
|
||||
return true;
|
||||
}
|
||||
if ((c == KEY_DOWN) && _contact_sel < _num_contacts - 1) {
|
||||
_contact_sel++;
|
||||
if (_contact_sel >= _contact_scroll + VISIBLE) _contact_scroll = _contact_sel - VISIBLE + 1;
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_ENTER && _num_contacts > 0) {
|
||||
if (the_mesh.getContactByIdx(_contact_sel, _sel_contact)) {
|
||||
_phase = MSG_PICK;
|
||||
_msg_sel = _msg_scroll = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (c == KEY_CANCEL) { _phase = CONTACT_PICK; return true; }
|
||||
if ((c == KEY_UP) && _msg_sel > 0) {
|
||||
_msg_sel--;
|
||||
if (_msg_sel < _msg_scroll) _msg_scroll = _msg_sel;
|
||||
return true;
|
||||
}
|
||||
if ((c == KEY_DOWN) && _msg_sel < QUICK_MSG_COUNT - 1) {
|
||||
_msg_sel++;
|
||||
if (_msg_sel >= _msg_scroll + VISIBLE) _msg_scroll = _msg_sel - VISIBLE + 1;
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_ENTER) {
|
||||
char msg[80];
|
||||
if (_msg_sel == 0) {
|
||||
buildLocMsg(msg, sizeof(msg));
|
||||
} else {
|
||||
strncpy(msg, QUICK_MSG_LABELS[_msg_sel], sizeof(msg)-1);
|
||||
msg[sizeof(msg)-1] = '\0';
|
||||
}
|
||||
uint32_t expected_ack = 0, est_timeout = 0;
|
||||
int result = the_mesh.sendMessage(_sel_contact, rtc_clock.getCurrentTime(), 0,
|
||||
msg, expected_ack, est_timeout);
|
||||
_task->showAlert(result > 0 ? "Sent!" : "Send failed", 1500);
|
||||
_task->gotoHomeScreen();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class HomeScreen : public UIScreen {
|
||||
enum HomePage {
|
||||
FIRST,
|
||||
@@ -345,6 +523,7 @@ class HomeScreen : public UIScreen {
|
||||
SENSORS,
|
||||
#endif
|
||||
SETTINGS,
|
||||
QUICK_MSG,
|
||||
SHUTDOWN,
|
||||
Count // keep as last
|
||||
};
|
||||
@@ -699,6 +878,13 @@ public:
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawTextCentered(display.width() / 2, 38, "brightness, buzzer");
|
||||
display.drawTextCentered(display.width() / 2, 52, PRESS_LABEL " to open");
|
||||
} else if (_page == HomePage::QUICK_MSG) {
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.setTextSize(1);
|
||||
display.drawTextCentered(display.width() / 2, 22, "Quick Message");
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawTextCentered(display.width() / 2, 38, "send to contact");
|
||||
display.drawTextCentered(display.width() / 2, 52, PRESS_LABEL " to open");
|
||||
} else if (_page == HomePage::SHUTDOWN) {
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.setTextSize(1);
|
||||
@@ -758,6 +944,10 @@ public:
|
||||
_task->gotoSettingsScreen();
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_ENTER && _page == HomePage::QUICK_MSG) {
|
||||
_task->gotoQuickMsgScreen();
|
||||
return true;
|
||||
}
|
||||
if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) {
|
||||
_shutdown_init = true; // need to wait for button to be released
|
||||
return true;
|
||||
@@ -892,11 +1082,17 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
home = new HomeScreen(this, &rtc_clock, sensors, node_prefs);
|
||||
msg_preview = new MsgPreviewScreen(this, &rtc_clock);
|
||||
settings = new SettingsScreen(this);
|
||||
quick_msg = new QuickMsgScreen(this);
|
||||
setCurrScreen(splash);
|
||||
|
||||
applyBrightness();
|
||||
}
|
||||
|
||||
void UITask::gotoQuickMsgScreen() {
|
||||
((QuickMsgScreen*)quick_msg)->reset();
|
||||
setCurrScreen(quick_msg);
|
||||
}
|
||||
|
||||
void UITask::showAlert(const char* text, int duration_millis) {
|
||||
strcpy(_alert, text);
|
||||
_alert_expiry = millis() + duration_millis;
|
||||
|
||||
@@ -53,6 +53,7 @@ class UITask : public AbstractUITask {
|
||||
UIScreen* home;
|
||||
UIScreen* msg_preview;
|
||||
UIScreen* settings;
|
||||
UIScreen* quick_msg;
|
||||
UIScreen* curr;
|
||||
|
||||
void userLedHandler();
|
||||
@@ -79,6 +80,7 @@ public:
|
||||
uint16_t getBattMilliVolts() const { return _batt_mv > 0 ? _batt_mv : AbstractUITask::getBattMilliVolts(); }
|
||||
void gotoHomeScreen() { setCurrScreen(home); }
|
||||
void gotoSettingsScreen() { setCurrScreen(settings); }
|
||||
void gotoQuickMsgScreen();
|
||||
void showAlert(const char* text, int duration_millis);
|
||||
int getMsgCount() const { return _msgcount; }
|
||||
bool hasDisplay() const { return _display != NULL; }
|
||||
|
||||
Reference in New Issue
Block a user