| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837 |
- #include "UITask.h"
- #include <helpers/TxtDataHelpers.h>
- #include "../MyMesh.h"
- #include "target.h"
- #include "u8g2_icons.h"
- #ifdef WIFI_SSID
- #include <WiFi.h>
- #endif
- #ifndef AUTO_OFF_MILLIS
- #define AUTO_OFF_MILLIS 15000 // 15 seconds
- #endif
- #define BOOT_SCREEN_MILLIS 4000 // 4 seconds
- #ifdef PIN_STATUS_LED
- #define LED_ON_MILLIS 20
- #define LED_ON_MSG_MILLIS 200
- #define LED_CYCLE_MILLIS 4000
- #endif
- #define LONG_PRESS_MILLIS 1200
- #ifndef UI_RECENT_LIST_SIZE
- #define UI_RECENT_LIST_SIZE 4
- #endif
- #if UI_HAS_JOYSTICK
- #define PRESS_LABEL "press Enter"
- #else
- #define PRESS_LABEL "long press"
- #endif
- class SplashScreen : public UIScreen {
- UITask* _task;
- unsigned long dismiss_after;
- unsigned long version_after;
- char _version_info[12];
- public:
- SplashScreen(UITask* task) : _task(task) {
- // strip off dash and commit hash by changing dash to null terminator
- // e.g: v1.2.3-abcdef -> v1.2.3
- 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;
- version_after = millis() + BOOT_SCREEN_MILLIS / 2;
- dismiss_after = millis() + BOOT_SCREEN_MILLIS;
- }
- int render(DisplayDriver& display) override {
- if (millis() < version_after) {
- // meshcore logo
- display.setColor(DisplayDriver::BLUE);
- int logoWidth = 72;
- display.drawXbm(0, 0, meshcore_logo, 72, 36);
- } else {
- // meshcore website
- const char* website = "meshcore.io";
- display.setColor(DisplayDriver::LIGHT);
- display.setTextSize(1);
- uint16_t websiteWidth = display.getTextWidth(website);
- display.setCursor((display.width() - websiteWidth) / 2, 9);
- display.print(website);
- // version info
- display.setColor(DisplayDriver::LIGHT);
- display.setTextSize(1);
- display.drawTextCentered(display.width()/2, 18, _version_info);
- display.setTextSize(1);
- display.drawTextCentered(display.width()/2, 27, FIRMWARE_BUILD_DATE);
- }
- return 1000;
- }
- void poll() override {
- if (millis() >= dismiss_after) {
- _task->gotoHomeScreen();
- }
- }
- };
- class HomeScreen : public UIScreen {
- enum HomePage {
- FIRST,
- RECENT,
- RADIO,
- BLUETOOTH,
- ADVERT,
- #if ENV_INCLUDE_GPS == 1
- GPS,
- #endif
- #if UI_SENSORS_PAGE == 1
- SENSORS,
- #endif
- SHUTDOWN,
- Count // keep as last
- };
- UITask* _task;
- mesh::RTCClock* _rtc;
- SensorManager* _sensors;
- NodePrefs* _node_prefs;
- uint8_t _page;
- bool _shutdown_init;
- AdvertPath recent[UI_RECENT_LIST_SIZE];
- CayenneLPP sensors_lpp;
- int sensors_nb = 0;
- bool sensors_scroll = false;
- int sensors_scroll_offset = 0;
- int next_sensors_refresh = 0;
- void refresh_sensors() {
- if (millis() > next_sensors_refresh) {
- 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;
- #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
- }
- }
- public:
- HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs)
- : _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0),
- _shutdown_init(false), sensors_lpp(200) { }
- 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];
- if (_page == HomePage::FIRST) {
- // // node name
- // display.setTextSize(1);
- // display.setColor(DisplayDriver::GREEN);
- // char filtered_name[sizeof(_node_prefs->node_name)];
- // display.translateUTF8ToBlocks(filtered_name, _node_prefs->node_name, sizeof(filtered_name));
- // display.setCursor(0, 0);
- // display.print(filtered_name);
- display.setColor(DisplayDriver::YELLOW);
- display.setTextSize(2);
- sprintf(tmp, "MSG: %d", _task->getMsgCount());
- display.setCursor(0, 10);
- display.print(tmp);
- sprintf(tmp, "BATT: %.2fV", _task->getCachedBattMV() / 1000.0f);
- display.setCursor(0, 19);
- display.print(tmp);
- #ifdef WIFI_SSID
- IPAddress ip = WiFi.localIP();
- snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
- display.setTextSize(1);
- display.drawTextCentered(display.width() / 2, 54, tmp);
- #endif
- if (_task->hasConnection()) {
- display.setColor(DisplayDriver::GREEN);
- display.setTextSize(1);
- display.drawTextCentered(display.width() / 2, display.height()-8, "< Connected >");
- } else if (the_mesh.getBLEPin() != 0) { // BT pin
- display.setColor(DisplayDriver::RED);
- display.setTextSize(2);
- sprintf(tmp, "Pin:%d", the_mesh.getBLEPin());
- display.drawTextCentered(display.width() / 2, display.height()-8, tmp);
- }
- } else if (_page == HomePage::RECENT) {
- the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE);
- display.setColor(DisplayDriver::GREEN);
- int y = 8;
- for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) {
- 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));
- }
- 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);
- display.print(tmp);
- }
- } else if (_page == HomePage::RADIO) {
- display.setColor(DisplayDriver::YELLOW);
- display.setTextSize(1);
- // frequency and spreading factor
- display.setCursor(0, 8);
- sprintf(tmp, "FQ %06.3f", _node_prefs->freq);
- display.print(tmp);
- sprintf(tmp, "SF%d", _node_prefs->sf);
- display.drawTextRightAlign(display.width(), 8, tmp);
- // bandwidth and coding rate
- display.setCursor(0, 17);
- sprintf(tmp, "BW %03.2f", _node_prefs->bw);
- display.print(tmp);
- sprintf(tmp, "CR%d", _node_prefs->cr);
- display.drawTextRightAlign(display.width(), 17, tmp);
- // tx power and noise floor
- display.setCursor(0, 26);
- sprintf(tmp, "NF %ddB", radio_driver.getNoiseFloor());
- display.print(tmp);
- sprintf(tmp, "TX%d", _node_prefs->tx_power_dbm);
- display.drawTextRightAlign(display.width(), 26, tmp);
- } else if (_page == HomePage::BLUETOOTH) {
- display.setColor(DisplayDriver::GREEN);
- display.drawXbm((display.width() - 32) / 2, 8,
- _task->isSerialEnabled() ? bluetooth_on : bluetooth_off,
- 32, 32);
- display.setTextSize(1);
- // display.drawTextCentered(display.width() / 2, 40 - 11, "toggle: " PRESS_LABEL);
- } else if (_page == HomePage::ADVERT) {
- display.setColor(DisplayDriver::GREEN);
- display.drawXbm((display.width() - 32) / 2, 8, advert_icon, 32, 32);
- // display.drawTextCentered(display.width() / 2, 40 - 11, "advert: " PRESS_LABEL);
- #if ENV_INCLUDE_GPS == 1
- } else if (_page == HomePage::GPS) {
- LocationProvider* nmea = sensors.getLocationProvider();
- char buf[50];
- int y = 8;
- 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);
- if (nmea == NULL) {
- // y = y + 8;
- display.drawTextLeftAlign(0, y, "Can't access GPS");
- } else {
- if (!gps_state || !nmea->isValid()) {
- strcpy(buf, "no fix");
- } else {
- sprintf(buf, "%d sat", nmea->satellitesCount());
- }
- display.drawTextRightAlign(display.width()-1, y, buf);
- y = y + 8;
- sprintf(buf, "lat %.4f",
- nmea->getLatitude()/1000000.);
- display.drawTextLeftAlign(0, y, buf);
- y = y + 8;
- sprintf(buf, "lon %.4f",
- nmea->getLongitude()/1000000.);
- display.drawTextLeftAlign(0, y, buf);
- y = y + 8;
- sprintf(buf, "alt %.1f", nmea->getAltitude()/1000.);
- display.drawTextLeftAlign(0, y, buf);
- }
- #endif
- #if UI_SENSORS_PAGE == 1
- } else if (_page == HomePage::SENSORS) {
- int y = 8;
- refresh_sensors();
- char buf[30];
- char name[30];
- LPPReader r(sensors_lpp.getBuffer(), sensors_lpp.getSize());
- for (int i = 0; i < sensors_scroll_offset; i++) {
- uint8_t channel, type;
- r.readHeader(channel, type);
- r.skipData(type);
- }
- for (int i = 0; i < (sensors_scroll?UI_RECENT_LIST_SIZE:sensors_nb); i++) {
- uint8_t channel, type;
- if (!r.readHeader(channel, type)) { // reached end, reset
- r.reset();
- r.readHeader(channel, type);
- }
- display.setCursor(0, y);
- float v;
- switch (type) {
- case LPP_GPS: // GPS
- float lat, lon, alt;
- r.readGPS(lat, lon, alt);
- strcpy(name, "gps"); sprintf(buf, "%.4f %.4f", lat, lon);
- break;
- case LPP_VOLTAGE:
- r.readVoltage(v);
- strcpy(name, "voltage"); sprintf(buf, "%6.2f", v);
- break;
- case LPP_CURRENT:
- r.readCurrent(v);
- strcpy(name, "current"); sprintf(buf, "%.3f", v);
- break;
- case LPP_TEMPERATURE:
- r.readTemperature(v);
- strcpy(name, "temperature"); sprintf(buf, "%.2f", v);
- break;
- case LPP_RELATIVE_HUMIDITY:
- r.readRelativeHumidity(v);
- strcpy(name, "humidity"); sprintf(buf, "%.2f", v);
- break;
- case LPP_BAROMETRIC_PRESSURE:
- r.readPressure(v);
- strcpy(name, "pressure"); sprintf(buf, "%.2f", v);
- break;
- case LPP_ALTITUDE:
- r.readAltitude(v);
- strcpy(name, "altitude"); sprintf(buf, "%.0f", v);
- break;
- case LPP_POWER:
- r.readPower(v);
- strcpy(name, "power"); sprintf(buf, "%6.2f", v);
- break;
- default:
- r.skipData(type);
- strcpy(name, "unk"); sprintf(buf, "");
- }
- display.setCursor(0, y);
- display.print(name);
- display.setCursor(
- display.width()-display.getTextWidth(buf)-1, y
- );
- display.print(buf);
- y = y + 12;
- }
- if (sensors_scroll) sensors_scroll_offset = (sensors_scroll_offset+1)%sensors_nb;
- else sensors_scroll_offset = 0;
- #endif
- } else if (_page == HomePage::SHUTDOWN) {
- display.setColor(DisplayDriver::GREEN);
- display.setTextSize(1);
- if (_shutdown_init) {
- display.drawTextCentered(display.width() / 2, 20, "hibernating...");
- } else {
- display.drawXbm((display.width() - 32) / 2, 8, power_icon, 32, 32);
- // display.drawTextCentered(display.width() / 2, 40 - 11, "hibernate:" PRESS_LABEL);
- }
- }
- return 5000; // next render after 5000 ms
- }
- bool handleInput(char c) override {
- if (c == KEY_LEFT || c == KEY_PREV) {
- _page = (_page + HomePage::Count - 1) % HomePage::Count;
- return true;
- }
- if (c == KEY_NEXT || c == KEY_RIGHT) {
- _page = (_page + 1) % HomePage::Count;
- 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) {
- _task->notify(UIEventType::ack);
- if (the_mesh.advert()) {
- _task->showAlert("Advert sent!", 1000);
- } else {
- _task->showAlert("Advert failed..", 1000);
- }
- return true;
- }
- #if ENV_INCLUDE_GPS == 1
- if (c == KEY_ENTER && _page == HomePage::GPS) {
- _task->toggleGPS();
- return true;
- }
- #endif
- #if UI_SENSORS_PAGE == 1
- if (c == KEY_ENTER && _page == HomePage::SENSORS) {
- _task->toggleGPS();
- next_sensors_refresh=0;
- return true;
- }
- #endif
- if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) {
- _shutdown_init = true; // need to wait for button to be released
- return true;
- }
- return false;
- }
- };
- void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs) {
- _display = display;
- _sensors = sensors;
- _auto_off = millis() + AUTO_OFF_MILLIS;
- _cached_batt_mv = getBattMilliVolts();
- #if defined(PIN_USER_BTN)
- user_btn.begin();
- #endif
- #if defined(PIN_USER_BTN_ANA)
- analog_btn.begin();
- #endif
- _node_prefs = node_prefs;
- if (_display != NULL) {
- _display->turnOn();
- }
- _statusBar.begin(_display->width());
- #ifdef PIN_BUZZER
- buzzer.begin();
- buzzer.quiet(_node_prefs->buzzer_quiet);
- buzzer.startup();
- #endif
- #ifdef PIN_VIBRATION
- vibration.begin();
- #endif
- ui_started_at = millis();
- _alert_expiry = 0;
- splash = new SplashScreen(this);
- home = new HomeScreen(this, &rtc_clock, sensors, node_prefs);
- setCurrScreen(splash);
- }
- void UITask::showAlert(const char* text, int duration_millis) {
- strcpy(_alert, text);
- _alert_expiry = millis() + duration_millis;
- }
- void UITask::notify(UIEventType t) {
- #if defined(PIN_BUZZER)
- switch(t){
- case UIEventType::contactMessage:
- // gemini's pick
- buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7");
- break;
- case UIEventType::channelMessage:
- buzzer.play("kerplop:d=16,o=6,b=120:32g#,32c#");
- break;
- case UIEventType::ack:
- buzzer.play("ack:d=32,o=8,b=120:c");
- break;
- case UIEventType::roomMessage:
- case UIEventType::newContactMessage:
- case UIEventType::none:
- default:
- break;
- }
- #endif
- #ifdef PIN_VIBRATION
- // Trigger vibration for all UI events except none
- if (t != UIEventType::none) {
- vibration.trigger();
- }
- #endif
- }
- void UITask::msgRead(int msgcount) {
- _msgcount = msgcount;
- if (msgcount == 0) {
- gotoHomeScreen();
- }
- }
- void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) {
- _msgcount = msgcount;
- if (_display != NULL) {
- if (!_display->isOn() && !hasConnection()) {
- _display->turnOn();
- }
- if (_display->isOn()) {
- _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer
- _next_refresh = 100; // trigger refresh
- }
- }
- }
- void UITask::userLedHandler() {
- #ifdef PIN_STATUS_LED
- int cur_time = millis();
- if (cur_time > next_led_change) {
- if (led_state == 0) {
- led_state = 1;
- if (_msgcount > 0) {
- last_led_increment = LED_ON_MSG_MILLIS;
- } else {
- last_led_increment = LED_ON_MILLIS;
- }
- next_led_change = cur_time + last_led_increment;
- } else {
- led_state = 0;
- next_led_change = cur_time + LED_CYCLE_MILLIS - last_led_increment;
- }
- digitalWrite(PIN_STATUS_LED, led_state == LED_STATE_ON);
- }
- #endif
- }
- void UITask::setCurrScreen(UIScreen* c) {
- curr = c;
- _next_refresh = 100;
- }
- /*
- hardware-agnostic pre-shutdown activity should be done here
- */
- void UITask::shutdown(bool restart){
- #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
- while (buzzer.isPlaying() && (millis() - 2500) < buzzer_timer)
- buzzer.loop();
- #endif // PIN_BUZZER
- if (restart) {
- _board->reboot();
- } else {
- _display->turnOff();
- radio_driver.powerOff();
- _board->powerOff();
- }
- }
- bool UITask::isButtonPressed() const {
- #ifdef PIN_USER_BTN
- return user_btn.isPressed();
- #else
- return false;
- #endif
- }
- void UITask::loop() {
- char c = 0;
- #if UI_HAS_JOYSTICK
- int ev = user_btn.check();
- if (ev == BUTTON_EVENT_CLICK) {
- c = checkDisplayOn(KEY_ENTER);
- } else if (ev == BUTTON_EVENT_LONG_PRESS) {
- c = handleLongPress(KEY_ENTER); // REVISIT: could be mapped to different key code
- }
- ev = joystick_left.check();
- if (ev == BUTTON_EVENT_CLICK) {
- c = checkDisplayOn(KEY_LEFT);
- } else if (ev == BUTTON_EVENT_LONG_PRESS) {
- c = handleLongPress(KEY_LEFT);
- }
- ev = joystick_right.check();
- if (ev == BUTTON_EVENT_CLICK) {
- c = checkDisplayOn(KEY_RIGHT);
- } else if (ev == BUTTON_EVENT_LONG_PRESS) {
- c = handleLongPress(KEY_RIGHT);
- }
- ev = back_btn.check();
- if (ev == BUTTON_EVENT_TRIPLE_CLICK) {
- c = handleTripleClick(KEY_SELECT);
- }
- #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);
- }
- #endif
- #if defined(PIN_USER_BTN_ANA)
- if (abs(millis() - _analogue_pin_read_millis) > 10) {
- int 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();
- }
- #endif
- #if defined(BACKLIGHT_BTN)
- if (millis() > next_backlight_btn_check) {
- bool touch_state = digitalRead(PIN_BUTTON2);
- #if defined(DISP_BACKLIGHT)
- digitalWrite(DISP_BACKLIGHT, !touch_state);
- #elif defined(EXP_PIN_BACKLIGHT)
- expander.digitalWrite(EXP_PIN_BACKLIGHT, !touch_state);
- #endif
- next_backlight_btn_check = millis() + 300;
- }
- #endif
- #if defined(HAS_TORCH)
- ev = back_btn.check();
- if (ev == BUTTON_EVENT_CLICK && c == 0) {
- c = checkDisplayOn(KEY_PREV);
- } else if (ev == BUTTON_EVENT_DOUBLE_CLICK) {
- board.toggleTorch();
- c = 0;
- }
- #endif
- if (c != 0 && curr) {
- curr->handleInput(c);
- _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
- _next_refresh = 100; // trigger refresh
- }
- userLedHandler();
- #ifdef PIN_BUZZER
- if (buzzer.isPlaying()) buzzer.loop();
- #endif
- if (curr) curr->poll();
- if (_display != NULL && _display->isOn()) {
- _statusBar.update(*_display,
- _node_prefs->node_name,
- _cached_batt_mv,
- isBuzzerQuiet(),
- getGPSState(),
- isSerialEnabled());
- bool status_dirty = _statusBar.needsRedraw();
- bool content_dirty = (millis() >= _next_refresh && curr);
- if (status_dirty || content_dirty) {
- _display->startFrame();
- _statusBar.render(*_display);
- if (curr) {
- int delay_millis = curr->render(*_display);
- if (content_dirty) {
- _next_refresh = millis() + delay_millis;
- }
- }
- if (millis() < _alert_expiry) { // render alert popup
- _display->setTextSize(1);
- int y = _display->height() / 3;
- int p = _display->height() / 32;
- _display->setColor(DisplayDriver::DARK);
- _display->fillRect(p, y, _display->width() - p*2, y);
- _display->setColor(DisplayDriver::LIGHT); // draw box border
- _display->drawRect(p, y, _display->width() - p*2, y);
- _display->drawTextCentered(_display->width() / 2, y + p*3, _alert);
- _next_refresh = _alert_expiry; // will need refresh when alert is dismissed
- }
- _display->endFrame();
- }
- #if AUTO_OFF_MILLIS > 0
- #ifdef KEEP_DISPLAY_ON_USB
- // Opt-in: refresh the auto-off deadline while externally powered, so the
- // timer counts from the moment external power is removed. Off by default
- // because OLED panels burn in quickly; only enable for LCD targets or
- // where the display is replaceable.
- if (board.isExternalPowered()) {
- _auto_off = millis() + AUTO_OFF_MILLIS;
- }
- #endif
- if (millis() > _auto_off) {
- _display->turnOff();
- }
- #endif
- }
- #ifdef PIN_VIBRATION
- vibration.loop();
- #endif
- #ifdef AUTO_SHUTDOWN_MILLIVOLTS
- if (millis() > next_batt_chck) {
- _cached_batt_mv = getBattMilliVolts();
- if (_cached_batt_mv > 0 && _cached_batt_mv < AUTO_SHUTDOWN_MILLIVOLTS) {
- if(!board.isExternalPowered()) {
- if (_display != NULL) {
- _display->startFrame();
- _display->setTextSize(2);
- _display->drawTextCentered(_display->width() / 2, 6, "Low battery!");
- _display->setTextSize(1);
- _display->drawTextCentered(_display->width() / 2, 18, "Shutting down!");
- _display->endFrame();
- if (_display->isEink() == false) { delay(3000); }
- }
- shutdown();
- }
- }
- next_batt_chck = millis() + 8000;
- }
- #else
- if (_display != NULL && _display->isOn() && millis() >= next_batt_chck) {
- _cached_batt_mv = getBattMilliVolts();
- next_batt_chck = millis() + 8000;
- }
- #endif
- }
- char UITask::checkDisplayOn(char c) {
- if (_display != NULL) {
- if (!_display->isOn()) {
- _display->turnOn(); // turn display on and consume event
- c = 0;
- }
- _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
- _next_refresh = 0; // trigger refresh
- }
- return c;
- }
- char UITask::handleLongPress(char c) {
- if (millis() - ui_started_at < 8000) { // long press in first 8 seconds since startup -> CLI/rescue
- the_mesh.enterCLIRescue();
- c = 0; // consume event
- }
- return c;
- }
- char UITask::handleDoubleClick(char c) {
- MESH_DEBUG_PRINTLN("UITask: double-click triggered");
- checkDisplayOn(c);
- return c;
- }
- char UITask::handleTripleClick(char c) {
- MESH_DEBUG_PRINTLN("UITask: triple click triggered");
- checkDisplayOn(c);
- toggleBuzzer();
- c = 0;
- return c;
- }
- 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;
- }
- 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");
- _node_prefs->gps_enabled = 0;
- notify(UIEventType::ack);
- } else {
- _sensors->setSettingValue("gps", "1");
- _node_prefs->gps_enabled = 1;
- notify(UIEventType::ack);
- }
- the_mesh.savePrefs();
- showAlert(_node_prefs->gps_enabled ? "GPS: Enabled" : "GPS: Disabled", 800);
- _next_refresh = 0;
- break;
- }
- }
- }
- }
- void UITask::toggleBuzzer() {
- // Toggle buzzer quiet mode
- #ifdef PIN_BUZZER
- if (buzzer.isQuiet()) {
- buzzer.quiet(false);
- notify(UIEventType::ack);
- } else {
- buzzer.quiet(true);
- }
- _node_prefs->buzzer_quiet = buzzer.isQuiet();
- the_mesh.savePrefs();
- showAlert(buzzer.isQuiet() ? "Buzzer: OFF" : "Buzzer: ON", 800);
- _next_refresh = 0; // trigger refresh
- #endif
- }
|