fix(ui): всплывающее окно для ответов устройства в мастере настройки
- ответ CLI показывался строкой поверх экрана и сливался с содержимым, особенно в верхней части (например OK - reboot to apply на экране частоты) - теперь это модальное окно: рамка, свой фон, перенос текста и кнопка OK; закрывается коротким нажатием, пока открыто — экран под ним не меняется - окно показывается сразу (сброс таймера отрисовки), при закрытии экран перерисовывается немедленно - setToast/renderToast заменены на showPopup/renderPopup - документация: posadmesh-docs/device-setup.md (раздел «Всплывающие сообщения» и макет экрана), README — журнал изменений Проверено сборкой: heltec_v4_repeater_mqtt, Meshadventurer_sx1262_repeater_mqtt, Generic_E22_sx1262_repeater_mqtt (без дисплея)
This commit is contained in:
1 parent
8f27aa9202
commit
82ec2f4c83
5 files changed
+64
-28
No files matched your search
@@ -397,6 +397,7 @@ MQTT-репитер использует:
|
||||
|
||||
**Исправлено**
|
||||
|
||||
- ответы устройства в мастере настройки показывались строкой поверх экрана и сливались с содержимым (например `OK - reboot to apply` в верхней части). Теперь это модальное всплывающее окно с рамкой, собственным фоном, переносом текста и кнопкой `OK`; закрывается коротким нажатием, пока открыто — экран под ним не меняется;
|
||||
- мастер настройки: при выходе из меню отправлялась команда `set radio` с нулевыми значениями рабочей копии, если в этой сессии не открывали ни один радиопункт (например сразу `Save & exit`) — устройство отвечало `Error, invalid radio params`. Теперь радиопараметры отправляются только когда их действительно правили, значения перед отправкой перечитываются из настроек узла, а при каждом входе в мастер рабочая копия обновляется;
|
||||
- `set mqtt.meshcoretel on|off` не срабатывал: обработчик сравнивался со строкой `set mqtt.mqtt.meshcoretel `. Алиас `set mqtt.meshcoretel.ru on|off` читает значение по правильному смещению;
|
||||
- `setEndpointEnabled` маскировал сохранённую маску брокеров значением `0x07` и терял бит собственного брокера при переключении любого другого;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#define WIZARD_AUTO_OFF_MILLIS 60000 // в мастере настройки экран живёт дольше
|
||||
#define BOOT_SCREEN_MILLIS 4000 // 4 seconds
|
||||
#define POWEROFF_DELAY 3000
|
||||
#define TOAST_MILLIS 3000
|
||||
#define CHAR_W 6 // ширина символа при setTextSize(1)
|
||||
|
||||
// позиции курсора на экране частоты: МГц, три разряда кГц и пункт сохранения
|
||||
@@ -140,7 +139,7 @@ int UITask::rowHeight() const {
|
||||
bool UITask::runCommand(const char* command) {
|
||||
_toast[0] = 0;
|
||||
if (_cli == nullptr) {
|
||||
setToast("no CLI");
|
||||
showPopup("no CLI");
|
||||
return false;
|
||||
}
|
||||
char buf[96];
|
||||
@@ -150,18 +149,22 @@ bool UITask::runCommand(const char* command) {
|
||||
reply[0] = 0;
|
||||
_cli->handleCommand(0, buf, reply);
|
||||
if (reply[0] == 0) {
|
||||
setToast("OK");
|
||||
showPopup("OK");
|
||||
return true;
|
||||
}
|
||||
bool ok = !(reply[0] == 'E' || reply[0] == 'e'); // "Error..." / "OK..."
|
||||
setToast(reply);
|
||||
showPopup(reply);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void UITask::setToast(const char* msg) {
|
||||
// Всплывающее окно с ответом устройства: показывается поверх экрана мастера
|
||||
// и закрывается коротким нажатием кнопки (см. handleButton).
|
||||
void UITask::showPopup(const char* msg) {
|
||||
strncpy(_toast, msg, sizeof(_toast) - 1);
|
||||
_toast[sizeof(_toast) - 1] = 0;
|
||||
_toast_until = millis() + TOAST_MILLIS;
|
||||
_popup_active = true;
|
||||
_next_refresh = 0; // показать окно сразу, не ждать тика отрисовки
|
||||
_auto_off = millis() + WIZARD_AUTO_OFF_MILLIS;
|
||||
}
|
||||
|
||||
void UITask::openMenu() {
|
||||
@@ -432,15 +435,24 @@ void UITask::coordSave() {
|
||||
bool okLon = runCommand(cmd);
|
||||
|
||||
if (okLat && okLon) {
|
||||
setToast("coords saved");
|
||||
showPopup("coords saved");
|
||||
} else {
|
||||
setToast(latMsg[0] ? latMsg : _toast);
|
||||
showPopup(latMsg[0] ? latMsg : _toast);
|
||||
}
|
||||
}
|
||||
|
||||
// --- кнопка -----------------------------------------------------------------
|
||||
|
||||
void UITask::handleButton(int ev) {
|
||||
// Пока открыто всплывающее окно, оно модальное: любое нажатие только закрывает его
|
||||
// (короткое — как просили), экран под ним не меняется.
|
||||
if (_popup_active) {
|
||||
_popup_active = false;
|
||||
_next_refresh = 0; // сразу перерисовать экран под окном
|
||||
_auto_off = millis() + WIZARD_AUTO_OFF_MILLIS;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_screen) {
|
||||
case SCREEN_STATUS:
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
@@ -538,7 +550,7 @@ void UITask::handleButton(int ev) {
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
char msg[32];
|
||||
snprintf(msg, sizeof(msg), "raw %u mV", (unsigned)(_board ? _board->getBattMilliVolts() : 0));
|
||||
setToast(msg);
|
||||
showPopup(msg);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS || ev == BUTTON_EVENT_DOUBLE_CLICK) {
|
||||
_screen = SCREEN_MENU;
|
||||
}
|
||||
@@ -643,6 +655,7 @@ void UITask::loop() {
|
||||
if (millis() >= _next_refresh) {
|
||||
_display->startFrame();
|
||||
renderCurrScreen();
|
||||
if (_popup_active) renderPopup(); // окно рисуется поверх экрана
|
||||
_display->endFrame();
|
||||
|
||||
_next_refresh = millis() + 1000; // refresh every second
|
||||
@@ -685,15 +698,37 @@ void UITask::renderHint(const char* hint) {
|
||||
_display->print(hint);
|
||||
}
|
||||
|
||||
void UITask::renderToast() {
|
||||
if (_toast[0] == 0 || millis() > _toast_until) return;
|
||||
int w = _display->width();
|
||||
int h = _display->height();
|
||||
// Всплывающее окно: рамка, свой фон (чтобы текст не сливался со строками экрана)
|
||||
// и кнопка OK внизу. Рисуется поверх текущего экрана.
|
||||
void UITask::renderPopup() {
|
||||
const int w = _display->width();
|
||||
const int h = _display->height();
|
||||
int box_h = 46;
|
||||
if (box_h > h - 8) box_h = h - 8;
|
||||
int top = (h - box_h) / 2;
|
||||
if (top < 12) top = 12;
|
||||
|
||||
// рамка: белая заливка и чёрная внутренняя область
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
_display->fillRect(4, top, w - 8, box_h);
|
||||
_display->setColor(UIColor::window_bkg);
|
||||
_display->fillRect(6, top + 2, w - 12, box_h - 4);
|
||||
|
||||
// текст сообщения
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
_display->drawRect(0, 12, w, h - 12);
|
||||
_display->setTextSize(1);
|
||||
_display->setCursor(2, 15);
|
||||
_display->printWordWrap(_toast, w - 4);
|
||||
_display->setCursor(8, top + 5);
|
||||
_display->printWordWrap(_toast, w - 20);
|
||||
|
||||
// кнопка OK
|
||||
const char* ok = "OK";
|
||||
int ok_w = (int)strlen(ok) * CHAR_W + 10;
|
||||
int ok_x = (w - ok_w) / 2;
|
||||
int ok_y = top + box_h - 14;
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
_display->drawRect(ok_x, ok_y, ok_w, 12);
|
||||
_display->setCursor(ok_x + 5, ok_y + 2);
|
||||
_display->print(ok);
|
||||
}
|
||||
|
||||
void UITask::renderMenu() {
|
||||
@@ -724,7 +759,6 @@ void UITask::renderMenu() {
|
||||
}
|
||||
_display->setColor(UIColor::primary_txt);
|
||||
renderHint("click=next hold=sel");
|
||||
renderToast();
|
||||
}
|
||||
|
||||
void UITask::renderValue() {
|
||||
@@ -768,7 +802,6 @@ void UITask::renderValue() {
|
||||
_display->setCursor(2, 20);
|
||||
_display->print(value);
|
||||
renderHint("clk+ dbl- hold=OK");
|
||||
renderToast();
|
||||
}
|
||||
|
||||
// поразрядный ввод значения с точкой: полоса ("062.50") или множитель ADC ("0.00")
|
||||
@@ -821,7 +854,6 @@ void UITask::renderDigits() {
|
||||
} else {
|
||||
renderHint("hold=edit clk=next");
|
||||
}
|
||||
renderToast();
|
||||
}
|
||||
|
||||
// заряд аккумулятора: напряжение, процент, полоска и текущий множитель ADC
|
||||
@@ -865,7 +897,6 @@ void UITask::renderBattery() {
|
||||
_display->print(line);
|
||||
|
||||
renderHint("hold=menu clk=raw");
|
||||
renderToast();
|
||||
}
|
||||
|
||||
// частота: целые МГц выделяются целиком (шаг 1), кГц — по разрядам, затем Save & exit
|
||||
@@ -924,7 +955,6 @@ void UITask::renderFreq() {
|
||||
} else {
|
||||
renderHint("hold=edit clk=next");
|
||||
}
|
||||
renderToast();
|
||||
}
|
||||
|
||||
void UITask::renderCoords() {
|
||||
@@ -964,7 +994,6 @@ void UITask::renderCoords() {
|
||||
} else {
|
||||
renderHint("hold=edit clk=next");
|
||||
}
|
||||
renderToast();
|
||||
}
|
||||
|
||||
void UITask::renderCoordsSave() {
|
||||
@@ -974,7 +1003,6 @@ void UITask::renderCoordsSave() {
|
||||
_display->setCursor(2, 20);
|
||||
_display->print("Save & exit?");
|
||||
renderHint("hold=save dbl=cancel");
|
||||
renderToast();
|
||||
}
|
||||
|
||||
void UITask::renderStatus() {
|
||||
@@ -1011,7 +1039,6 @@ void UITask::renderStatus() {
|
||||
if (_display->height() >= 56) {
|
||||
renderHint("dbl-click: setup");
|
||||
}
|
||||
renderToast();
|
||||
}
|
||||
|
||||
void UITask::renderCurrScreen() {
|
||||
|
||||
@@ -43,8 +43,8 @@ private:
|
||||
bool _coord_editing = false; // true: цифра правится, false: применена
|
||||
bool _coord_applied[12];
|
||||
char _coord_digits[12];
|
||||
char _toast[40];
|
||||
unsigned long _toast_until = 0;
|
||||
char _toast[64];
|
||||
bool _popup_active = false; // всплывающее окно с ответом устройства открыто
|
||||
|
||||
// радиопараметры: рабочая копия, пока мастер открыт
|
||||
bool _radio_loaded = false;
|
||||
@@ -85,7 +85,7 @@ private:
|
||||
void coordFromPrefs();
|
||||
void coordSave();
|
||||
double coordAxisValue(uint8_t axis) const; // axis 0 = lat, 1 = lon
|
||||
void setToast(const char* msg);
|
||||
void showPopup(const char* msg);
|
||||
bool runCommand(const char* command);
|
||||
|
||||
void handleButton(int ev);
|
||||
@@ -101,7 +101,7 @@ private:
|
||||
void renderCoordsSave();
|
||||
void renderTitleBar(const char* title);
|
||||
void renderHint(const char* hint);
|
||||
void renderToast();
|
||||
void renderPopup();
|
||||
int rowHeight() const;
|
||||
|
||||
public:
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.0 KiB |
@@ -128,6 +128,14 @@
|
||||
|
||||
`0.00` означает «использовать заводское значение платы»: команда `set adc.multiplier 0` возвращает расчёт по умолчанию. Значение применяется командой `set adc.multiplier <x.xx>` — той же, что и в последовательном CLI.
|
||||
|
||||
## Всплывающие сообщения
|
||||
|
||||
Ответ устройства на любую команду показывается отдельным окном поверх экрана: рамка, свой фон, текст с переносом строк и кнопка `OK`. Окно модальное — пока оно открыто, экран под ним не меняется, а **короткое нажатие кнопки закрывает его**. Так видно и успешные ответы (например `OK - reboot to apply` после правки радио), и ошибки проверки значений.
|
||||
|
||||

|
||||
|
||||
Признак успеха: после правки частоты, полосы, `CR` или `SF` окно показывает `OK - reboot to apply` — изменения радио вступают в силу после перезагрузки узла. Если в ответе ошибка (например значение вне диапазона), окно покажет её текст, а настройка не сохранится.
|
||||
|
||||
## Ограничения
|
||||
|
||||
- На экранах без кириллицы подписи меню выводятся латиницей.
|
||||
|
||||
Reference in new issue
Block a user