From 440aa8a51297dc4616d0df8302c78b4b1f4f4725 Mon Sep 17 00:00:00 2001 From: Roman Terekhov Date: Mon, 12 Oct 2026 09:59:09 +0300 Subject: [PATCH] =?UTF-8?q?ui-new:=20=D1=82=D0=B5=D0=BC=D1=8B=20=D1=81?= =?UTF-8?q?=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D1=8F=D1=8E=D1=82=D1=81=D1=8F=20?= =?UTF-8?q?=D1=81=D1=80=D0=B0=D0=B7=D1=83,=20POWER:=20ADC=20=D0=BE=D1=82?= =?UTF-8?q?=20=D1=82=D0=B5=D0=BA=D1=83=D1=89=D0=B5=D0=B3=D0=BE=20=D0=B7?= =?UTF-8?q?=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D0=BA?= =?UTF-8?q?=D1=80=D1=83=D0=BF=D0=BD=D0=B0=D1=8F=20=D0=B1=D0=B0=D1=82=D0=B0?= =?UTF-8?q?=D1=80=D0=B5=D1=8F,=20=D1=80=D0=B0=D0=BC=D0=BA=D0=B0=20=D1=87?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=BE=D1=82=D1=8B=20=D0=BF=D0=BE=20=D1=84?= =?UTF-8?q?=D0=B0=D0=BA=D1=82=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D0=BC=20?= =?UTF-8?q?=D1=88=D0=B8=D1=80=D0=B8=D0=BD=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit T096Board: множитель АЦП вынесен в поле с set/get вместо захардкоженного 4.9 --- README.md | 26 +++-- examples/companion_radio/ui-new/UITask.cpp | 117 ++++++++++++++------- examples/companion_radio/ui-new/UITask.h | 3 +- variants/heltec_t096/T096Board.cpp | 9 +- variants/heltec_t096/T096Board.h | 3 + 5 files changed, 110 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 6c3b6c55..9dd7f396 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,10 @@ git merge upstream/main # правки для upstream отправляю (`amber`, `teal`, `signal`, `neon`, `classic`) лежат в общей таблице [`src/helpers/ui/ColorThemes.h`](./src/helpers/ui/ColorThemes.h) и работают и на ST7735, и на ST7789LCD. В `ui-new` есть страница `THEME`: живой предпросмотр, образцы цветов, - сохранение выбора в prefs. По умолчанию — янтарь на Heltec T096 и официальная светлая - схема MeshCore на остальных платах (как в upstream). + сохранение выбора в prefs. **Тема сохраняется сразу при выборе** — что видно на экране, + то и записано, поэтому выход из меню (в том числе по таймауту) ничего не «откатывает» + и не подменяет ранее выбранную тему. По умолчанию — янтарь на Heltec T096 и официальная + светлая схема MeshCore на остальных платах (как в upstream). - **Индикатор страниц** в `ui-new` строится на `warning_txt` / `secondary_txt`: в upstream цвета брались из `title_bkg`, из-за чего на тёмных темах активная точка сливалась с фоном. @@ -79,17 +81,23 @@ git merge upstream/main # правки для upstream отправляю каналов, личных сообщений и уведомлений о новых контактах, но сообщения не теряются. - **Страница POWER** (последняя в списке страниц; длинное нажатие — вход в меню) — питание, батарея и перезагрузка. Раньше этот пункт назывался `Hibernation` и сразу выключал устройство. - - `Battery` — что показывать рядом с иконкой батареи: `Volt` (напряжение, `4.05V`), - `Perc` (процент) или `Off` (индикатор скрыт). Длинное нажатие переключает значение; - применяется и сохраняется сразу. Иконка батареи стала компактнее (18×9). - - `ADC corr` — коррекция АЦП батареи. Видно текущее значение (или `auto`, если коррекция - не задана); длинное нажатие открывает правку, где короткое нажатие даёт `+0.01`, - двойное `-0.01`, а длинное возвращает в меню. Значение применяется к плате и сохраняется - сразу, а при следующем включении подхватывается из prefs. + На самой странице крупно нарисованы батарея с заливкой по заряду, процент и напряжение. + - `Battery` — что показывать рядом с иконкой батареи в статус-баре: `Volt` (напряжение, + `4.05V`), `Perc` (процент) или `Off` (индикатор скрыт). Длинное нажатие переключает + значение; применяется и сохраняется сразу. Иконка батареи стала компактнее (18×9). + - `ADC corr` — коррекция АЦП батареи. Показывается **тот множитель, который действует + сейчас**: значение из настроек, иначе — из платы, иначе базовый `ADC_MULTIPLIER` варианта + (то есть правка начинается не с нуля, а с реального значения прошивки). Длинное нажатие + открывает правку, где короткое нажатие даёт `+0.01`, двойное `-0.01`, а длинное возвращает + в меню. Значение применяется к плате и сохраняется сразу, а при следующем включении + подхватывается из prefs. - `Reboot/Shut` — подменю из трёх пунктов: `Reboot`, `Shutdown`, `Back`; длинное нажатие выполняет выбранное. - `Back` — выход из меню. Сохранять ничего не нужно: всё сохраняется в момент изменения, через 5 минут простоя меню закрывается само. + - Коррекция работает на платах, где борд её поддерживает (`Heltec V4`, `V4 R8`, `Heltec + T096`, `promicro`, `Heltec RC32`, `Meshnology W12`); на остальных при попытке правки + выводится `ADC not supported`. ### Сборка и CI diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 47571433..54331084 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -206,6 +206,7 @@ class HomeScreen : public UIScreen { void powerStep(int8_t dir); void powerAction(); void powerAdcStep(int8_t dir); + float adcBaseValue(); // текущий множитель АЦП: prefs → борд → ADC_MULTIPLIER варианта void renderPowerPage(DisplayDriver& display); void renderPowerScreen(DisplayDriver& display); void renderPowerAdc(DisplayDriver& display); @@ -231,12 +232,9 @@ class HomeScreen : public UIScreen { _node_prefs->ui_theme[sizeof(_node_prefs->ui_theme) - 1] = 0; the_mesh.savePrefs(); } - void themeEditLeave(bool save) { + void themeEditLeave(bool confirm) { _theme_edit = false; - if (save) { - themeStore(); - _task->showAlert("Saved", 1000); - } + if (confirm) _task->showAlert("Saved", 800); // тема сохраняется в момент выбора, тут только подтверждение _task->showPendingMessages(); } void themeRowStep(int8_t dir) { @@ -244,7 +242,10 @@ class HomeScreen : public UIScreen { if (d == NULL) return; uint8_t rows = d->getThemeCount() + 1; // темы + "Save & exit" _theme_sel = (_theme_sel + rows + dir) % rows; - if (_theme_sel < d->getThemeCount()) d->setTheme(_theme_sel); // живой предпросмотр + if (_theme_sel < d->getThemeCount()) { + d->setTheme(_theme_sel); // применяем сразу + themeStore(); // и сразу пишем в prefs: что видно, то и сохранено + } } void renderThemePage(DisplayDriver& display); void renderThemeEditor(DisplayDriver& display); @@ -972,29 +973,60 @@ void HomeScreen::powerAction() { } void HomeScreen::powerAdcStep(int8_t dir) { - float v = _node_prefs->adc_multiplier + (float)dir * 0.01f; + // считаем от того значения, которое реально работает сейчас, а не от нуля + float base = adcBaseValue(); + if (base <= 0.0f) base = 1.0f; // плата без множителя — считаем от 1.00 + float v = base + (float)dir * 0.01f; if (v < 0.0f) v = 0.0f; if (v > 10.0f) v = 10.0f; v = roundf(v * 100.0f) / 100.0f; // без накопления погрешности float - _node_prefs->adc_multiplier = v; if (!_task->setAdcMultiplier(v)) { - _task->showAlert("ADC not supported", 1200); + _task->showAlert("ADC not supported", 1200); // плата не умеет корректировать — не сохраняем + return; } + _node_prefs->adc_multiplier = v; the_mesh.savePrefs(); } +// Множитель АЦП, который действует сейчас: из prefs, иначе из платы, +// иначе — базовый множитель варианта (ADC_MULTIPLIER в variant.h) +float HomeScreen::adcBaseValue() { + if (_node_prefs->adc_multiplier > 0.0f) return _node_prefs->adc_multiplier; + float v = _task->getAdcMultiplier(); + if (v > 0.0f) return v; +#ifdef ADC_MULTIPLIER + return (float)ADC_MULTIPLIER; +#else + return 0.0f; +#endif +} + void HomeScreen::renderPowerPage(DisplayDriver& display) { - char tmp[32]; - display.setTextSize(1); + char tmp[24]; + uint16_t mv = _task->getBattMilliVolts(); + int pct = ((int)mv - BATT_MIN_MILLIVOLTS) * 100 / (BATT_MAX_MILLIVOLTS - BATT_MIN_MILLIVOLTS); + if (pct < 0) pct = 0; + if (pct > 100) pct = 100; + + // крупная иконка батареи, справа — заряд и напряжение + const int iconX = 6, iconY = 8, iconW = 44, iconH = 22; + display.setColor(UIColor::primary_txt); + display.drawRect(iconX, iconY, iconW, iconH); + display.fillRect(iconX + iconW, iconY + 6, 3, iconH - 12); // "пипка" + display.setColor(pct <= 15 ? UIColor::warning_txt : UIColor::primary_txt); + display.fillRect(iconX + 3, iconY + 3, (pct * (iconW - 6)) / 100, iconH - 6); + + display.setColor(UIColor::warning_txt); + display.setTextSize(2); + snprintf(tmp, sizeof(tmp), "%d%%", pct); + display.drawTextRightAlign(display.width() - 4, iconY + 2, tmp); + display.setColor(UIColor::secondary_txt); - snprintf(tmp, sizeof(tmp), "Battery: %s", battDisplayName(_node_prefs->batt_display)); - display.drawTextCentered(display.width() / 2, 20, tmp); - if (_node_prefs->adc_multiplier > 0.0f) { - snprintf(tmp, sizeof(tmp), "ADC corr: %.2f", _node_prefs->adc_multiplier); - } else { - strcpy(tmp, "ADC corr: auto"); - } - display.drawTextCentered(display.width() / 2, 32, tmp); + display.setTextSize(1); + if (mv > 0) snprintf(tmp, sizeof(tmp), "%.2f V", mv / 1000.0f); + else strcpy(tmp, "-- V"); + display.drawTextRightAlign(display.width() - 4, iconY + iconH + 4, tmp); + display.setColor(UIColor::primary_txt); display.drawTextCentered(display.width() / 2, 64 - 11, "power: " PRESS_LABEL); } @@ -1014,7 +1046,8 @@ void HomeScreen::renderPowerScreen(DisplayDriver& display) { return; } char val[16]; - if (_node_prefs->adc_multiplier > 0.0f) snprintf(val, sizeof(val), "%.2f", _node_prefs->adc_multiplier); + float adc = adcBaseValue(); + if (adc > 0.0f) snprintf(val, sizeof(val), "%.2f", adc); else strcpy(val, "auto"); drawMenuRow(display, 2, _power_sel == PWR_BATT, "Battery", battDisplayName(_node_prefs->batt_display)); drawMenuRow(display, 12, _power_sel == PWR_ADC_ROW, "ADC corr", val); @@ -1029,7 +1062,8 @@ void HomeScreen::renderPowerAdc(DisplayDriver& display) { display.setColor(UIColor::secondary_txt); display.setTextSize(1); display.drawTextLeftAlign(0, 2, "ADC +0.01/-0.01"); - if (_node_prefs->adc_multiplier > 0.0f) snprintf(buf, sizeof(buf), "%.2f", _node_prefs->adc_multiplier); + float adc = adcBaseValue(); + if (adc > 0.0f) snprintf(buf, sizeof(buf), "%.2f", adc); else strcpy(buf, "auto"); display.setColor(UIColor::warning_txt); display.setTextSize(2); @@ -1056,27 +1090,36 @@ void HomeScreen::renderRadioEditor(DisplayDriver& display) { display.setTextSize(1); display.drawTextLeftAlign(0, 2, head); - display.setColor(UIColor::warning_txt); - display.setTextSize(2); - int w = display.getTextWidth(buf); - int x = (display.width() - w) / 2; - const int y = 20; - display.drawTextLeftAlign(x, y, buf); - + // Позиции символов считаем по реальным ширинам: у пропорциональных шрифтов + // (TFT_eSPI на T096) точка уже цифры, из-за фиксированного шага рамка уезжала. char mhz_txt[8]; snprintf(mhz_txt, sizeof(mhz_txt), "%d", mhz); int sel_len = _radio_fq_pos == 0 ? (int)strlen(mhz_txt) : 1; int sel_start = _radio_fq_pos == 0 ? 0 : (int)strlen(mhz_txt) + _radio_fq_pos; // +1 — точка - char sel[10]; - if (sel_len > (int)sizeof(sel) - 1) sel_len = (int)sizeof(sel) - 1; - memcpy(sel, buf + sel_start, sel_len); - sel[sel_len] = 0; + const int buf_len = (int)strlen(buf); - const int char_w = 6 * 2; // шрифт моноширинный: 6 px на символ при размере 2 - display.setColor(UIColor::primary_txt); - display.fillRect(x + sel_start * char_w - 1, y - 2, sel_len * char_w + 2, 20); - display.setColor(UIColor::window_bkg); - display.drawTextLeftAlign(x + sel_start * char_w, y, sel); + display.setTextSize(2); + int total = 0; + for (int i = 0; i < buf_len; i++) { + char one[2] = { buf[i], 0 }; + total += (int)display.getTextWidth(one); + } + int cx = (display.width() - total) / 2; + const int y = 20; + for (int i = 0; i < buf_len; i++) { + char one[2] = { buf[i], 0 }; + int cw = (int)display.getTextWidth(one); + bool mark = (i >= sel_start && i < sel_start + sel_len); + if (mark) { + display.setColor(UIColor::primary_txt); + display.fillRect(cx - 1, y - 2, cw + 2, 20); + display.setColor(UIColor::window_bkg); + } else { + display.setColor(UIColor::warning_txt); + } + display.drawTextLeftAlign(cx, y, one); + cx += cw; + } display.setColor(UIColor::secondary_txt); display.setTextSize(1); diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 1a658a10..09160504 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -89,8 +89,9 @@ public: bool isButtonPressed() const; // Коррекция АЦП батареи: реализация в board конкретного варианта, - // если плата так не умеет — вернёт false + // если плата так не умеет — set вернёт false, get вернёт 0 bool setAdcMultiplier(float multiplier) { return _board->setAdcMultiplier(multiplier); } + float getAdcMultiplier() const { return _board->getAdcMultiplier(); } bool isBuzzerQuiet() { #ifdef PIN_BUZZER diff --git a/variants/heltec_t096/T096Board.cpp b/variants/heltec_t096/T096Board.cpp index 78af529d..ed667e56 100644 --- a/variants/heltec_t096/T096Board.cpp +++ b/variants/heltec_t096/T096Board.cpp @@ -76,7 +76,14 @@ uint16_t T096Board::getBattMilliVolts() { adcvalue = analogRead(PIN_VBAT_READ); digitalWrite(PIN_BAT_CTL, 0); - return (uint16_t)((float)adcvalue * MV_LSB * 4.9); + return (uint16_t)((float)adcvalue * MV_LSB * adc_mult); +} + +// Коррекция АЦП батареи (страница POWER в ui-new): полный множитель делителя +bool T096Board::setAdcMultiplier(float multiplier) { + if (multiplier <= 0.0f) return false; + adc_mult = multiplier; + return true; } void T096Board::variant_shutdown() { nrf_gpio_cfg_default(PIN_VEXT_EN); diff --git a/variants/heltec_t096/T096Board.h b/variants/heltec_t096/T096Board.h index 15c7e68b..02ae2183 100644 --- a/variants/heltec_t096/T096Board.h +++ b/variants/heltec_t096/T096Board.h @@ -8,6 +8,7 @@ class T096Board : public NRF52BoardDCDC { protected: + float adc_mult = ADC_MULTIPLIER; // множитель делителя батареи, правится из UI (страница POWER) #ifdef NRF52_POWER_MANAGEMENT void initiateShutdown(uint8_t reason) override; #endif @@ -23,6 +24,8 @@ public: void onBeforeTransmit(void) override; void onAfterTransmit(void) override; uint16_t getBattMilliVolts() override; + bool setAdcMultiplier(float multiplier) override; + float getAdcMultiplier() const override { return adc_mult; } const char* getManufacturerName() const override ; void powerOff() override; bool setLoRaFemLnaEnabled(bool enable) override;