From 28e802dcf04d018f516c25d7da3522c71fc67bfd Mon Sep 17 00:00:00 2001 From: Roman Terekhov Date: Thu, 8 Oct 2026 20:28:59 +0300 Subject: [PATCH] =?UTF-8?q?=D1=83=D0=B2=D0=B5=D0=B4=D0=BE=D0=BC=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F:=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC?= =?UTF-8?q?=D1=8B=20=D0=BF=D0=BE=20=D0=BA=D0=B0=D0=BD=D0=B0=D0=BB=D0=B0?= =?UTF-8?q?=D0=BC,=20=D1=82=D0=B8=D1=85=D0=B8=D0=B5=20=D1=87=D0=B0=D1=81?= =?UTF-8?q?=D1=8B=20=D1=81=20=D1=87=D0=B0=D1=81=D0=BE=D0=B2=D1=8B=D0=BC=20?= =?UTF-8?q?=D0=BF=D0=BE=D1=8F=D1=81=D0=BE=D0=BC,=20=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B0=D0=B2=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=B4=D0=B8=D1=81=D0=BF=D0=BB=D0=B5=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/companion_radio/AbstractUITask.h | 5 + examples/companion_radio/MyMesh.cpp | 46 +++- examples/companion_radio/NodePrefs.h | 58 +++++ examples/companion_radio/ui-new/UITask.cpp | 289 ++++++++++++++++++++- examples/companion_radio/ui-new/UITask.h | 4 + 5 files changed, 382 insertions(+), 20 deletions(-) diff --git a/examples/companion_radio/AbstractUITask.h b/examples/companion_radio/AbstractUITask.h index b25b1442..0ccd9129 100644 --- a/examples/companion_radio/AbstractUITask.h +++ b/examples/companion_radio/AbstractUITask.h @@ -41,6 +41,11 @@ public: void disableBluetooth() { _interfaceManager->disableBluetooth(); } virtual void msgRead(int msgcount) = 0; virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) = 0; + // То же, но без вывода на экран (тихий час или канал в режиме SILENT): + // сообщение попадает в непрочитанные, звука и всплытия нет. + virtual void newMsgSilent(uint8_t path_len, const char* from_name, const char* text, int msgcount) { + newMsg(path_len, from_name, text, msgcount); + } virtual void notify(UIEventType t = UIEventType::none) = 0; virtual void loop() = 0; }; diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index fc91b2ad..560db389 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -361,7 +361,10 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path } } else { #ifdef DISPLAY_CLASS - if (_ui) _ui->notify(UIEventType::newContactMessage); + // тихие часы глушат и уведомление о новом контакте + if (_ui && !_prefs.isQuietNow(getRTCClock()->getCurrentTime())) { + _ui->notify(UIEventType::newContactMessage); + } #endif } @@ -468,9 +471,13 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe // we only want to show text messages on display, not cli data bool should_display = txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN; if (should_display && _ui) { - _ui->newMsg(path_len, from.name, text, offline_queue_len); - if (!_serial->isConnected()) { - _ui->notify(UIEventType::contactMessage); + bool quiet = _prefs.isQuietNow(getRTCClock()->getCurrentTime()); + bool alert = _prefs.msg_popup && !quiet; + if (alert) { + if (!_serial->isConnected()) _ui->notify(UIEventType::contactMessage); + _ui->newMsg(path_len, from.name, text, offline_queue_len); + } else { + _ui->newMsgSilent(path_len, from.name, text, offline_queue_len); } } #endif @@ -573,19 +580,30 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe uint8_t frame[1]; frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle' _serial->writeFrame(frame, 1); - } else { -#ifdef DISPLAY_CLASS - if (_ui) _ui->notify(UIEventType::channelMessage); -#endif } #ifdef DISPLAY_CLASS - // Get the channel name from the channel index - const char *channel_name = "Unknown"; - ChannelDetails channel_details; - if (getChannel(channel_idx, channel_details)) { - channel_name = channel_details.name; + // Настройки уведомлений: канал может быть в режиме POPUP / SILENT / OFF, + // плюс тихие часы глушат звук и всплытие для всех входящих. + uint8_t ch_mode = _prefs.getChannelMode(channel_idx); + if (ch_mode != CH_MODE_OFF) { + bool quiet = _prefs.isQuietNow(getRTCClock()->getCurrentTime()); + bool alert = (ch_mode == CH_MODE_POPUP) && _prefs.msg_popup && !quiet; + + // Get the channel name from the channel index + const char *channel_name = "Unknown"; + ChannelDetails channel_details; + if (getChannel(channel_idx, channel_details)) { + channel_name = channel_details.name; + } + if (_ui) { + if (alert) { + if (!_serial->isConnected()) _ui->notify(UIEventType::channelMessage); + _ui->newMsg(path_len, channel_name, text, offline_queue_len); + } else { + _ui->newMsgSilent(path_len, channel_name, text, offline_queue_len); + } + } } - if (_ui) _ui->newMsg(path_len, channel_name, text, offline_queue_len); #endif } diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index f6aa9912..c64f71c7 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -9,6 +9,18 @@ #define ADVERT_LOC_NONE 0 #define ADVERT_LOC_SHARE 1 +// Режимы уведомлений по каналам (по 2 бита на канал) +#define CH_MODE_POPUP 0 // звук + экран переключается на сообщение +#define CH_MODE_SILENT 1 // в непрочитанные, но без звука и без всплытия +#define CH_MODE_OFF 2 // на устройстве не уведомляем вообще + +// Сколько каналов покрываем настройками. Совпадает с MAX_GROUP_CHANNELS у companion-сборок, +// но этот заголовок его не включает, поэтому задаём свой предел. +#ifndef NOTIFY_MAX_CHANNELS + #define NOTIFY_MAX_CHANNELS 40 +#endif +#define CH_MODE_BYTES ((NOTIFY_MAX_CHANNELS * 2 + 7) / 8) + class NodePrefs : public ConfigSerializer { // persisted to file public: float airtime_factor = 0; @@ -42,6 +54,15 @@ public: uint8_t default_scope_key[16]; char ui_theme[12]; // имя темы дисплея; пусто = тема по умолчанию для платы + // Уведомления о входящих (меню на первом экране) + uint8_t msg_popup = 1; // переключать экран на входящее сообщение + uint8_t msg_wake_display = 1; // зажигать дисплей при входящем сообщении + uint8_t quiet_en = 0; // часы тишины включены + uint8_t quiet_from = 23; // час начала (локальный) + uint8_t quiet_to = 7; // час окончания (локальный) + int8_t tz_offset = 0; // смещение от UTC в часах, -12..+14 + uint8_t ch_mode[CH_MODE_BYTES]; // режимы по каналам (CH_MODE_*), по 2 бита + private: class RadioPrefs : public ConfigSerializer { // COPIED from CommonCLI (for now) NodePrefs* _parent; @@ -114,6 +135,14 @@ private: def("auto_add", _parent->autoadd_config); // bitmask for auto-add contacts config def("man_add", _parent->manual_add_contacts); def("theme", _parent->ui_theme, sizeof(_parent->ui_theme)); // имя темы дисплея + // уведомления + def("msg_popup", _parent->msg_popup); + def("msg_wake", _parent->msg_wake_display); + def("quiet_en", _parent->quiet_en); + def("quiet_from", _parent->quiet_from); + def("quiet_to", _parent->quiet_to); + def("tz", _parent->tz_offset); + def("ch_mode", (void *)_parent->ch_mode, sizeof(_parent->ch_mode)); // hex-блоб def("tel_base", _parent->telemetry_mode_base); def("tel_loc", _parent->telemetry_mode_loc); def("tel_env", _parent->telemetry_mode_env); @@ -140,9 +169,38 @@ public: node_name[0] = 0; default_scope_name[0] = 0; ui_theme[0] = 0; + memset(ch_mode, 0, sizeof(ch_mode)); // все каналы — CH_MODE_POPUP memset(default_scope_key, 0, sizeof(default_scope_key)); } // new accessor methods bool isRepeatEn() const { return repeat.disable_fwd == 0; } void setRepeatEn(bool en) { repeat.disable_fwd = en ? 0 : 1; } + + // Настройки уведомлений по каналам + uint8_t getChannelMode(uint8_t idx) const { + if (idx >= NOTIFY_MAX_CHANNELS) return CH_MODE_POPUP; + uint8_t sh = (uint8_t)((idx & 3) * 2); + return (uint8_t)((ch_mode[idx >> 2] >> sh) & 0x03); + } + void setChannelMode(uint8_t idx, uint8_t mode) { + if (idx >= NOTIFY_MAX_CHANNELS) return; + uint8_t sh = (uint8_t)((idx & 3) * 2); + ch_mode[idx >> 2] = (uint8_t)((ch_mode[idx >> 2] & ~(0x03 << sh)) | ((mode & 0x03) << sh)); + } + + // Локальный час суток по unix-времени RTC и смещению часового пояса + uint8_t localHour(uint32_t utc) const { + int32_t h = (int32_t)((utc / 3600) % 24) + tz_offset; + h %= 24; + if (h < 0) h += 24; + return (uint8_t)h; + } + // Тихие часы: окно может пересекать полночь (например, 23..7) + bool isQuietNow(uint32_t utc) const { + if (!quiet_en) return false; + if (quiet_from == quiet_to) return false; + uint8_t h = localHour(utc); + if (quiet_from < quiet_to) return h >= quiet_from && h < quiet_to; + return h >= quiet_from || h < quiet_to; + } }; diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 43c90829..836bd4bb 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -43,6 +43,25 @@ #endif #define UI_RADIO_FREQ_STEP_KHZ 25 // шаг перестройки частоты #define UI_RADIO_MENU_TIMEOUT_MILLIS 300000UL // 5 минут простоя — выйти из меню без сохранения +#define NOTIFY_MAIN_ROWS 7 // строк в меню уведомлений + +// Строка меню: выбранная — инверсией, значение — справа вторичным цветом +static void drawMenuRow(DisplayDriver& display, int y, bool sel, const char* label, const char* value) { + if (sel) { + display.setColor(UIColor::primary_txt); + display.fillRect(0, y - 1, display.width(), 9); + display.setColor(UIColor::window_bkg); + display.drawTextLeftAlign(1, y, label); + if (value[0]) display.drawTextRightAlign(display.width() - 2, y, value); + } else { + display.setColor(UIColor::primary_txt); + display.drawTextLeftAlign(1, y, label); + if (value[0]) { + display.setColor(UIColor::secondary_txt); + display.drawTextRightAlign(display.width() - 2, y, value); + } + } +} // BW перебирается по списку допустимых значений, а не шагом static const float UI_RADIO_BW_STEPS[] = { @@ -203,6 +222,47 @@ class HomeScreen : public UIScreen { void renderThemeEditor(DisplayDriver& display); uint8_t pageStep(int8_t dir); + // меню уведомлений (первый экран, длинное нажатие) + enum NotifyScreen { NOTIFY_OFF, NOTIFY_MAIN, NOTIFY_CHANNELS, NOTIFY_QUIET }; + uint8_t _notify_screen; + uint8_t _notify_sel; + bool _notify_val_edit; // правка часа в подменю тихих часов + unsigned long _notify_last_activity; + + void notifyEnter() { + _task->clearAlert(); + _notify_screen = NOTIFY_MAIN; + _notify_sel = 0; + _notify_val_edit = false; + _notify_last_activity = millis(); + } + void notifyLeave(bool save) { + _notify_screen = NOTIFY_OFF; + _notify_val_edit = false; + if (save) { + the_mesh.savePrefs(); + _task->showAlert("Saved", 1000); + } + _task->showPendingMessages(); + } + uint8_t alertsMode() const { // 0 = звук+вибро, 1 = только звук, 2 = только вибро, 3 = выкл + bool snd = _node_prefs->buzzer_quiet == 0, vib = _node_prefs->vibe_quiet == 0; + return (uint8_t)(snd ? (vib ? 0 : 1) : (vib ? 2 : 3)); + } + void alertsCycle() { + uint8_t m = (uint8_t)((alertsMode() + 1) & 3); + // 0 both, 1 sound, 2 vibe, 3 off + _node_prefs->buzzer_quiet = (m == 2 || m == 3) ? 1 : 0; + _node_prefs->vibe_quiet = (m == 1 || m == 3) ? 1 : 0; + } + uint8_t channelCount(); + uint8_t channelIdxAt(uint8_t pos); + uint8_t notifyRowCount(); + void notifyStep(int8_t dir); + void notifyActivate(); + void notifyValueStep(int8_t dir); + void renderNotifyMenu(DisplayDriver& display); + void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) { // Convert millivolts to percentage @@ -276,10 +336,11 @@ public: : _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0), _shutdown_init(false), _radio_edit(false), _radio_val_edit(false), _radio_sel(0), _radio_last_activity(0), _theme_edit(false), _theme_sel(0), _theme_last_activity(0), + _notify_screen(NOTIFY_OFF), _notify_sel(0), _notify_val_edit(false), _notify_last_activity(0), sensors_lpp(200) { } - // в меню настройки (радио или темы) входящие сообщения не всплывают поверх экрана - bool suppressAlerts() override { return _radio_edit || _theme_edit; } + // в меню настроек (радио, темы, уведомления) входящие сообщения не всплывают поверх экрана + bool suppressAlerts() override { return _radio_edit || _theme_edit || _notify_screen != NOTIFY_OFF; } void poll() override { if (_radio_edit) { @@ -298,6 +359,14 @@ public: _task->keepDisplayOn(); } } + if (_notify_screen != NOTIFY_OFF) { + if (millis() - _notify_last_activity >= UI_RADIO_MENU_TIMEOUT_MILLIS) { + notifyLeave(false); + _task->blankDisplay(); + } else { + _task->keepDisplayOn(); + } + } if (_shutdown_init && !_task->isButtonPressed()) { // must wait for USR button to be released _task->shutdown(); } @@ -314,6 +383,10 @@ public: renderThemeEditor(display); return 5000; } + if (_notify_screen != NOTIFY_OFF) { + renderNotifyMenu(display); + return 5000; + } display.setColor(UIColor::title_bkg); display.fillRect(0, 0, display.width(), 12); char tmp[80]; @@ -613,6 +686,22 @@ public: } return true; } + if (_notify_screen != NOTIFY_OFF) { // меню уведомлений + _notify_last_activity = millis(); + if (c == KEY_NEXT || c == KEY_RIGHT) { + if (_notify_val_edit) notifyValueStep(+1); else notifyStep(+1); + return true; + } + if (c == KEY_PREV || c == KEY_LEFT) { + if (_notify_val_edit) notifyValueStep(-1); else notifyStep(-1); + return true; + } + if (c == KEY_ENTER) { + notifyActivate(); + return true; + } + return true; + } if (c == KEY_LEFT || c == KEY_PREV) { _page = pageStep(-1); return true; @@ -630,6 +719,10 @@ public: _task->showAlert("Nodes must match", 1500); return true; } + if (c == KEY_ENTER && _page == HomePage::FIRST) { + notifyEnter(); // настройки входящих сообщений и уведомлений + return true; + } if (c == KEY_ENTER && _page == HomePage::THEME) { themeEditEnter(); return true; @@ -845,6 +938,173 @@ void HomeScreen::renderThemeEditor(DisplayDriver& display) { } } +uint8_t HomeScreen::channelCount() { + uint8_t n = 0; + ChannelDetails d; + for (uint8_t i = 0; i < NOTIFY_MAX_CHANNELS; i++) { + if (the_mesh.getChannel(i, d) && d.name[0] != 0) n++; + } + return n; +} + +uint8_t HomeScreen::channelIdxAt(uint8_t pos) { + uint8_t n = 0; + ChannelDetails d; + for (uint8_t i = 0; i < NOTIFY_MAX_CHANNELS; i++) { + if (the_mesh.getChannel(i, d) && d.name[0] != 0) { + if (n == pos) return i; + n++; + } + } + return 0xFF; +} + +uint8_t HomeScreen::notifyRowCount() { + switch (_notify_screen) { + case NOTIFY_QUIET: return 5; // Quiet hours / From / To / Timezone / Back + case NOTIFY_CHANNELS: return (uint8_t)(channelCount() + 1); // каналы + Back + default: return NOTIFY_MAIN_ROWS; + } +} + +void HomeScreen::notifyStep(int8_t dir) { + uint8_t rows = notifyRowCount(); + if (rows == 0) return; + _notify_sel = (uint8_t)((_notify_sel + rows + dir) % rows); +} + +void HomeScreen::notifyValueStep(int8_t dir) { // правка часа тишины и часового пояса + if (_notify_screen != NOTIFY_QUIET) return; + if (_notify_sel == 1 || _notify_sel == 2) { + int v = (int)(_notify_sel == 1 ? _node_prefs->quiet_from : _node_prefs->quiet_to) + dir; + v %= 24; + if (v < 0) v += 24; + if (_notify_sel == 1) _node_prefs->quiet_from = (uint8_t)v; + else _node_prefs->quiet_to = (uint8_t)v; + } else if (_notify_sel == 3) { // timezone, -12..+14 + int v = (int)_node_prefs->tz_offset + dir; + if (v < -12) v = -12; + if (v > 14) v = 14; + _node_prefs->tz_offset = (int8_t)v; + } +} + +void HomeScreen::notifyActivate() { // длинное нажатие + if (_notify_val_edit) { + _notify_val_edit = false; // закончили правку часа + return; + } + if (_notify_screen == NOTIFY_MAIN) { + switch (_notify_sel) { + case 0: // Unread — открыть непрочитанные + notifyLeave(false); + _task->showMessages(); + return; + case 1: _node_prefs->msg_popup = _node_prefs->msg_popup ? 0 : 1; return; + case 2: _node_prefs->msg_wake_display = _node_prefs->msg_wake_display ? 0 : 1; return; + case 3: alertsCycle(); return; + case 4: _notify_screen = NOTIFY_CHANNELS; _notify_sel = 0; return; + case 5: _notify_screen = NOTIFY_QUIET; _notify_sel = 0; return; + default: notifyLeave(true); return; // Save & exit + } + } + if (_notify_screen == NOTIFY_QUIET) { + switch (_notify_sel) { + case 0: _node_prefs->quiet_en = _node_prefs->quiet_en ? 0 : 1; return; + case 1: + case 2: + case 3: _notify_val_edit = true; return; // править час / часовой пояс + default: _notify_screen = NOTIFY_MAIN; _notify_sel = 5; return; // Back + } + } + if (_notify_screen == NOTIFY_CHANNELS) { + if (_notify_sel >= channelCount()) { // Back + _notify_screen = NOTIFY_MAIN; + _notify_sel = 4; + return; + } + uint8_t idx = channelIdxAt(_notify_sel); + if (idx != 0xFF) { + uint8_t m = (uint8_t)((_node_prefs->getChannelMode(idx) + 1) % 3); + _node_prefs->setChannelMode(idx, m); // POPUP -> SILENT -> OFF + } + } +} + +void HomeScreen::renderNotifyMenu(DisplayDriver& display) { + char buf[24]; + char val[16]; + display.setTextSize(1); + + if (_notify_screen == NOTIFY_QUIET) { + static const char* names[5] = { "Quiet hours", "From", "To", "Timezone", "Back" }; + int y = 2; + for (uint8_t r = 0; r < 5; r++, y += 10) { + val[0] = 0; + if (r == 0) strcpy(val, _node_prefs->quiet_en ? "on" : "off"); + else if (r == 1) snprintf(val, sizeof(val), "%02d:00", _node_prefs->quiet_from); + else if (r == 2) snprintf(val, sizeof(val), "%02d:00", _node_prefs->quiet_to); + else if (r == 3) snprintf(val, sizeof(val), "%+03d", (int)_node_prefs->tz_offset); + drawMenuRow(display, y, r == _notify_sel, names[r], val); + } + if (_notify_val_edit) { + display.setColor(UIColor::warning_txt); + display.drawTextCentered(display.width() / 2, 64 - 10, "click+ dbl- hold=back"); + } + return; + } + + if (_notify_screen == NOTIFY_CHANNELS) { + uint8_t n = channelCount(); + uint8_t rows = (uint8_t)(n + 1); + const uint8_t VISIBLE = 6; + uint8_t first = _notify_sel >= VISIBLE ? (uint8_t)(_notify_sel - VISIBLE + 1) : 0; + static const char* mode_names[3] = { "popup", "silent", "off" }; + int y = 2; + for (uint8_t r = first; r < rows && y <= 52; r++, y += 10) { + if (r < n) { + uint8_t idx = channelIdxAt(r); + ChannelDetails cd; + if (the_mesh.getChannel(idx, cd)) { + strncpy(buf, cd.name, sizeof(buf) - 1); + buf[sizeof(buf) - 1] = 0; + } else { + strcpy(buf, "?"); + } + strcpy(val, mode_names[_node_prefs->getChannelMode(idx) % 3]); + } else { + strcpy(buf, "Back"); + val[0] = 0; + } + drawMenuRow(display, y, r == _notify_sel, buf, val); + } + return; + } + + // NOTIFY_MAIN + static const char* labels[NOTIFY_MAIN_ROWS] = { + "Unread", "Popups", "Wake screen", "Alerts", "Channels", "Quiet hours", "Save & exit" + }; + static const char* alert_names[4] = { "sound+vibe", "sound", "vibe", "off" }; + int y = 1; + for (uint8_t r = 0; r < NOTIFY_MAIN_ROWS; r++, y += 9) { + val[0] = 0; + switch (r) { + case 0: snprintf(val, sizeof(val), "%d", _task->getMsgCount()); break; + case 1: strcpy(val, _node_prefs->msg_popup ? "on" : "off"); break; + case 2: strcpy(val, _node_prefs->msg_wake_display ? "on" : "off"); break; + case 3: strcpy(val, alert_names[alertsMode()]); break; + case 4: snprintf(val, sizeof(val), "%d >", channelCount()); break; + case 5: + if (_node_prefs->quiet_en) snprintf(val, sizeof(val), "%02d-%02d", _node_prefs->quiet_from, _node_prefs->quiet_to); + else strcpy(val, "off"); + break; + default: break; + } + drawMenuRow(display, y, r == _notify_sel, labels[r], val); + } +} + class MsgPreviewScreen : public UIScreen { UITask* _task; mesh::RTCClock* _rtc; @@ -1044,13 +1304,16 @@ void UITask::msgRead(int msgcount) { } } -void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) { +void UITask::queueMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) { _msgcount = msgcount; - ((MsgPreviewScreen *) msg_preview)->addPreview(path_len, from_name, text); +} + +void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) { + queueMsg(path_len, from_name, text, msgcount); if (curr != NULL && curr->suppressAlerts()) { - // Экран попросил не перекрывать его (меню настройки радио): сообщение уже + // Экран попросил не перекрывать его (меню настройки): сообщение уже // в очереди непрочитанных, покажем его после выхода из меню. _msg_pending = true; _next_refresh = 100; @@ -1059,7 +1322,8 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i setCurrScreen(msg_preview); if (_display != NULL) { - if (!_display->isOn() && !hasConnection()) { + // зажигать экран по входящему можно запретить (в меню уведомлений) + if (!_display->isOn() && !hasConnection() && _node_prefs->msg_wake_display) { _display->turnOn(); } if (_display->isOn()) { @@ -1069,6 +1333,19 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i } } +void UITask::newMsgSilent(uint8_t path_len, const char* from_name, const char* text, int msgcount) { + // тихий режим (часы тишины или канал SILENT): только в непрочитанные, + // экран не трогаем и звук не подаём + queueMsg(path_len, from_name, text, msgcount); + _msg_pending = true; + _next_refresh = 100; // чтобы обновился счётчик на первом экране +} + +void UITask::showMessages() { + _msg_pending = false; + setCurrScreen(msg_preview); +} + void UITask::userLedHandler() { #ifdef PIN_STATUS_LED int cur_time = millis(); diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index eea0a31d..56e28dab 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -62,6 +62,8 @@ class UITask : public AbstractUITask { char handleDoubleClick(char c); char handleTripleClick(char c); + void queueMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount); + void setCurrScreen(UIScreen* c); public: @@ -79,6 +81,7 @@ public: void blankDisplay(); // погасить экран немедленно void clearAlert(); // снять текущий попап void showPendingMessages(); // открыть предпросмотр, если сообщение пришло «в фоне» + void showMessages(); // открыть предпросмотр непрочитанных по запросу int getMsgCount() const { return _msgcount; } bool hasDisplay() const { return _display != NULL; } DisplayDriver* getDisplay() { return _display; } @@ -101,6 +104,7 @@ public: // from AbstractUITask void msgRead(int msgcount) override; void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override; + void newMsgSilent(uint8_t path_len, const char* from_name, const char* text, int msgcount) override; void notify(UIEventType t = UIEventType::none) override; void loop() override;