ui-new: темы сохраняются сразу, POWER: ADC от текущего значения и крупная батарея, рамка частоты по фактическим ширинам

T096Board: множитель АЦП вынесен в поле с set/get вместо захардкоженного 4.9
This commit is contained in:
shade committed 2026-10-12 09:59:09 +03:00
1 parent c58782aeeb
commit 440aa8a512
5 files changed
+110 -48

No files matched your search

+17 -9
View File
@@ -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
+80 -37
View File
@@ -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);
+2 -1
View File
@@ -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
+8 -1
View File
@@ -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);
+3
View File
@@ -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;