feat(ui): экран батареи и калибровка ADC в мастере настройки

- UITask: новые пункты меню Battery и ADC
  Battery — напряжение, процент заряда, полоска и текущий множитель ADC; короткое
  нажатие показывает «сырое» значение АЦП в милливольтах, длинное/двойное — выход в меню
  ADC — поразрядная правка множителя в формате x.xx (как полоса), под цифрами
  показывается измеренное напряжение, поэтому калибровать можно прямо на экране
- процент заряда берётся у платы (getBatteryPercent), а если плата его не считает —
  линейно по getBatteryMinMilliVolts()/getBatteryMaxMilliVolts()
- множитель применяется командой set adc.multiplier, значение 0.00 возвращает
  заводской расчёт платы; проверка и сохранение остаются в CommonCLI
- поразрядный редактор SCREEN_DIGITS теперь обслуживает и полосу, и ADC
- документация: posadmesh-docs/device-setup.md (таблица меню, раздел «Батарея и
  калибровка ADC», два новых макета экранов), README — журнал изменений

Проверено сборкой: heltec_v4_repeater_mqtt, Meshadventurer_sx1262_repeater_mqtt,
Generic_E22_sx1262_repeater_mqtt (без дисплея — код исключается препроцессором)
This commit is contained in:
shade committed 2026-10-16 10:32:27 +03:00
1 parent 8fa0bfe1d3
commit 1e751a85d4
6 files changed
+168 -7

No files matched your search

+13 -1
View File
@@ -379,10 +379,22 @@ MQTT-репитер использует:
- ввод координат в формате `xx.xxxx yy.yyyy`: длинное нажатие — правка текущей цифры, короткое — `+1`, двойное — `−1`, длинное — применить, короткое после применения — следующая цифра; после всех 12 цифр — экран сохранения;
- радиопараметры настраиваются по отдельности: `Frequency` (значение `868.731 MHz`: целые МГц шагом ±1, кГц по разрядам), `Bandwidth` (`062.50 kHz`, поразрядно), `CR` и `SF` (шагом); внизу экранов частоты и полосы — пункт `Save & exit`, после сохранения возврат в меню; любой из четырёх пунктов отправляет `set radio <частота>,<полоса>,<sf>,<cr>`, поэтому остальные значения сохраняются;
- остальные пункты: мощность TX, пересылка пакетов, размер хэша пути, интервалы advert и flood advert, лимит flood; внизу меню — `Save & exit`;
- добавлены экран батареи и калибровка ADC: пункт `Battery` показывает напряжение, процент заряда (от платы, иначе линейная оценка по её границам) и полоску, короткое нажатие выводит «сырое» значение АЦП в милливольтах; пункт `ADC` правит множитель ADC поразрядно в формате `x.xx` (как полоса) и показывает измеренное напряжение прямо под цифрами, поэтому калибровать можно на экране; значение применяется командой `set adc.multiplier`, а `0.00` возвращает заводской расчёт платы;
- значения применяются через тот же `CommonCLI`, что и последовательный порт, а ответ устройства (`OK` или текст ошибки) выводится на экран;
- документация с иллюстрациями: страница [Настройка с дисплея и кнопки](./posadmesh-docs/device-setup.md) дополнена схемой управления и изображениями всех экранов (состояние, меню, частота и правка разряда, полоса, CR, SF, координаты, подтверждение, ответ устройства); та же документация опубликована в вики проекта — <https://git.meshinfo.ru/shade/MeshCore-Posadmesh/wiki/Device-Setup>;
- документация с иллюстрациями: страница [Настройка с дисплея и кнопки](./posadmesh-docs/device-setup.md) дополнена схемой управления и изображениями всех экранов (состояние, меню, частота и правка разряда, полоса, CR, SF, координаты, подтверждение, ответ устройства, батарея, ADC); та же документация опубликована в вики проекта — <https://git.meshinfo.ru/shade/MeshCore-Posadmesh/wiki/Device-Setup>;
- сборка проверена на `heltec_v4_repeater_mqtt`, `Meshadventurer_sx1262_repeater_mqtt`, `Generic_E22_sx1262_repeater_mqtt` (без дисплея) и `RAK_3112_repeater_mqtt`.
**Обновление до upstream MeshCore v1.18.0**
- база проекта поднята с `v1.17.1` до `v1.18.0`: влит релизный тег `repeater-v1.18.0` (266 коммитов upstream, 201 файл, +7185/−1010) через интеграционную ветку `integrate/upstream-1.18.0`; страховочный тег `pre-1.18.0` и `git bundle` лежат в истории репозитория;
- слияние дало 12 конфликтных файлов. Companion-часть (4 файла) взята из upstream целиком — в этом форке она не собирается. В `simple_repeater` сохранены наши возможности, а интеграция вентилятора T-Beam 1W заменена на upstream-реализацию: у него задача FreeRTOS, чтение NTC, гистерезис, TX-кулдаун, свои настройки в NVS и CLI (`get fan`, `set fan auto|on|off`, `set fan.lo/hi`); наша команда `set fan timeout <Ns>` убрана вместе с ручным опросом из `loop()`;
- время на ESP32 восстанавливается в два слоя: сначала из NVS (наша надстройка, переживает отключение питания), затем из RTC-памяти upstream, иначе опорная дата; deep sleep по-прежнему не трогаем;
- в менеджере датчиков взята логика upstream (карта `detected[]`, один драйвер на тип), поверх возвращено определение адреса платой; попутно восстановлен потерянный слиянием helper `scanI2CBus` с нашим таймаутом 20 мс;
- наша сигнатура `format*StatsReply(char*, size_t)` и наши префы (`FanPrefs`, `WebSensorSnapshot`, `BlacklistEntry`, `ChanNameFilter`, `getCLI()`) сохранены; из upstream добавлены `isHiddenNode()`, `DynamicConfigSerializer custom`, `attachDynamicPrefs`;
- добавлена плата **Waveshare ESP32-S3 LR1121** из 1.18.0 — три MQTT-окружения (`Waveshare_S3_LR1121_repeater_mqtt`, `_espnow`, `_bridge`); остальные новые платы 1.18.0 (ThinkNode M8 и M4, Wio Tracker L1 1W, Muzi Base) выполнены на nRF52, WiFi у них нет, поэтому MQTT-сборки для них невозможны;
- MQTT-целей стало 112 (42 uplink, 34 espnow, 36 bridge); после слияния проверены сборкой `heltec_v4_repeater_mqtt`, `LilyGo_TBeam_1W_repeater_mqtt`, `Meshadventurer_sx1262_repeater_mqtt`, `RAK_3112_repeater_mqtt`, `heltec_v4_repeater_mqtt_espnow`, `heltec_v4_repeater_mqtt_bridge`, `LilyGo_Tlora_C6_repeater_mqtt_espnow`, `LilyGo_TBeam_1W_repeater_mqtt_espnow`, `Waveshare_S3_LR1121_repeater_mqtt`, `Waveshare_S3_LR1121_repeater_mqtt_bridge`;
- в `AGENTS.md` обновлён процесс: работаем в `main` (ветка `develop` — устаревшая, от эпохи EastMesh), upstream тянем релизным тегом, а не веткой `dev`, версия upstream — `v1.18.0`.
**Исправлено**
- `set mqtt.meshcoretel on|off` не срабатывал: обработчик сравнивался со строкой `set mqtt.mqtt.meshcoretel `. Алиас `set mqtt.meshcoretel.ru on|off` читает значение по правильному смещению;
+130 -5
View File
@@ -31,6 +31,8 @@ enum {
MENU_CR,
MENU_SF,
MENU_TX,
MENU_BATTERY,
MENU_ADC,
MENU_REPEAT,
MENU_PATHHASH,
MENU_ADVERT,
@@ -47,6 +49,8 @@ static const char* const MENU_NAMES[MENU_COUNT] = {
"CR",
"SF",
"TX power",
"Battery",
"ADC",
"Repeat",
"Path hash",
"Advert int",
@@ -224,6 +228,26 @@ void UITask::beginBwEdit() {
_screen = SCREEN_DIGITS;
}
void UITask::beginAdcEdit() {
// Множитель ADC: x.xx, где 0.00 означает «использовать заводское значение платы»
_digit_int = 1;
_digit_count = 3;
for (int i = 0; i < _digit_count; i++) _digit_applied[i] = false;
_digit_index = 0;
_digit_editing = false;
float v = _node_prefs ? _node_prefs->adc_multiplier : 0.0f;
if (v < 0) v = 0;
if (v > 9.99f) v = 9.99f;
int ip = (int)v;
int fp = (int)((v - ip) * 100.0f + 0.5f);
if (fp > 99) { fp = 0; ip++; }
char buf[8];
snprintf(buf, sizeof(buf), "%d%02d", ip % 10, fp);
memcpy(_digit_digits, buf, _digit_count);
_screen = SCREEN_DIGITS;
}
double UITask::digitValue() const {
int ip = 0, fp = 0;
for (int i = 0; i < _digit_int; i++) ip = ip * 10 + (_digit_digits[i] - '0');
@@ -233,6 +257,35 @@ double UITask::digitValue() const {
return ip + fp / scale;
}
// Применение поразрядного редактора: полоса (set radio) или множитель ADC
void UITask::applyDigits() {
if (_menu_index == MENU_ADC) {
char cmd[48];
snprintf(cmd, sizeof(cmd), "set adc.multiplier %d.%02d",
_digit_digits[0] - '0',
(_digit_digits[1] - '0') * 10 + (_digit_digits[2] - '0'));
runCommand(cmd);
return;
}
_radio_bw = digitValue();
applyRadio();
}
// Процент заряда: значение платы, а если его нет — линейная оценка по напряжению
int UITask::batteryPercent() const {
if (_board == nullptr) return -1;
int pct = _board->getBatteryPercent();
if (pct >= 0) return pct > 100 ? 100 : pct;
const uint16_t mv = _board->getBattMilliVolts();
const uint16_t lo = _board->getBatteryMinMilliVolts();
const uint16_t hi = _board->getBatteryMaxMilliVolts();
if (hi <= lo) return -1;
if (mv <= lo) return 0;
if (mv >= hi) return 100;
return (int)(((uint32_t)(mv - lo) * 100U) / (uint32_t)(hi - lo));
}
// --- меню -------------------------------------------------------------------
void UITask::openField(uint8_t index) {
@@ -261,6 +314,12 @@ void UITask::openField(uint8_t index) {
case MENU_TX:
_value = _node_prefs ? _node_prefs->tx_power_dbm : 0;
break;
case MENU_BATTERY:
_screen = SCREEN_BATTERY;
return;
case MENU_ADC:
beginAdcEdit();
return;
case MENU_REPEAT:
_value = (_node_prefs && _node_prefs->disable_fwd) ? 0 : 1;
break;
@@ -451,8 +510,7 @@ void UITask::handleButton(int ev) {
}
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
if (_digit_index >= _digit_count) { // Save & exit
_radio_bw = digitValue();
applyRadio();
applyDigits();
_screen = SCREEN_MENU;
} else {
_digit_editing = true;
@@ -465,6 +523,17 @@ void UITask::handleButton(int ev) {
break;
}
case SCREEN_BATTERY: { // заряд аккумулятора: только просмотр
if (ev == BUTTON_EVENT_CLICK) {
char msg[32];
snprintf(msg, sizeof(msg), "raw %u mV", (unsigned)(_board ? _board->getBattMilliVolts() : 0));
setToast(msg);
} else if (ev == BUTTON_EVENT_LONG_PRESS || ev == BUTTON_EVENT_DOUBLE_CLICK) {
_screen = SCREEN_MENU;
}
break;
}
case SCREEN_FREQ: { // частота: МГц шагом 1, кГц по разрядам, затем Save & exit
if (_freq_editing) {
if (_freq_index == 0) {
@@ -690,9 +759,10 @@ void UITask::renderValue() {
renderToast();
}
// поразрядный ввод значения с точкой (полоса): "062.50" + пункт Save & exit
// поразрядный ввод значения с точкой: полоса ("062.50") или множитель ADC ("0.00")
void UITask::renderDigits() {
renderTitleBar("Bandwidth");
const bool adc = (_menu_index == MENU_ADC);
renderTitleBar(adc ? "ADC" : "Bandwidth");
_display->setTextSize(1);
int y = 18;
int cx = 2;
@@ -718,7 +788,17 @@ void UITask::renderDigits() {
}
_display->setColor(UIColor::primary_txt);
_display->setCursor(cx + CHAR_W, y);
_display->print("kHz");
_display->print(adc ? "x" : "kHz");
// На экране ADC показываем измеренное напряжение: по нему видно результат калибровки
if (adc) {
const unsigned mv = _board ? _board->getBattMilliVolts() : 0;
char volts[24];
snprintf(volts, sizeof(volts), "%u.%02u V", mv / 1000U, (mv % 1000U) / 10U);
_display->setColor(UIColor::primary_txt);
_display->setCursor(2, 45);
_display->print(volts);
}
renderRow(_display, 34, "Save & exit", _digit_index >= _digit_count);
@@ -732,6 +812,50 @@ void UITask::renderDigits() {
renderToast();
}
// заряд аккумулятора: напряжение, процент, полоска и текущий множитель ADC
void UITask::renderBattery() {
renderTitleBar("Battery");
_display->setTextSize(1);
_display->setColor(UIColor::primary_txt);
const unsigned mv = _board ? _board->getBattMilliVolts() : 0;
char line[32];
snprintf(line, sizeof(line), "%u.%02u V", mv / 1000U, (mv % 1000U) / 10U);
_display->setCursor(2, 15);
_display->print(line);
const int pct = batteryPercent();
if (pct >= 0) {
snprintf(line, sizeof(line), "%d %%", pct);
int x = _display->width() - (int)strlen(line) * CHAR_W - 2;
if (x < 2) x = 2;
_display->setCursor(x, 15);
_display->print(line);
}
// полоска заряда
const int bar_w = _display->width() - 4;
_display->setColor(UIColor::primary_txt);
_display->drawRect(2, 27, bar_w, 9);
if (pct > 0) {
int fill = (bar_w - 4) * (pct > 100 ? 100 : pct) / 100;
if (fill > 0) _display->fillRect(4, 29, fill, 5);
}
const float adc = _node_prefs ? _node_prefs->adc_multiplier : 0.0f;
if (adc > 0.0f) {
snprintf(line, sizeof(line), "ADC %.3f", (double)adc);
} else {
snprintf(line, sizeof(line), "ADC default");
}
_display->setColor(UIColor::primary_txt);
_display->setCursor(2, 40);
_display->print(line);
renderHint("hold=menu clk=raw");
renderToast();
}
// частота: целые МГц выделяются целиком (шаг 1), кГц — по разрядам, затем Save & exit
void UITask::renderFreq() {
renderTitleBar("Frequency");
@@ -921,6 +1045,7 @@ void UITask::renderCurrScreen() {
case SCREEN_VALUE: renderValue(); break;
case SCREEN_DIGITS: renderDigits(); break;
case SCREEN_FREQ: renderFreq(); break;
case SCREEN_BATTERY: renderBattery(); break;
case SCREEN_COORDS: renderCoords(); break;
case SCREEN_COORDS_SAVE: renderCoordsSave(); break;
default: renderStatus(); break;
+6 -1
View File
@@ -16,8 +16,9 @@ public:
SCREEN_STATUS = 0,
SCREEN_MENU,
SCREEN_VALUE, // числа и переключатели: SF, CR, TX, repeat, path hash, advert, flood
SCREEN_DIGITS, // поразрядный ввод значения с точкой (полоса)
SCREEN_DIGITS, // поразрядный ввод значения с точкой (полоса, ADC)
SCREEN_FREQ, // частота: МГц шагом 1, кГц по разрядам
SCREEN_BATTERY, // заряд аккумулятора: напряжение, процент, множитель ADC
SCREEN_COORDS,
SCREEN_COORDS_SAVE
};
@@ -74,9 +75,12 @@ private:
void loadRadio();
void applyRadio();
void beginBwEdit();
void beginAdcEdit();
void beginFreqEdit();
void applyDigits();
double digitValue() const;
double freqValue() const;
int batteryPercent() const;
void coordFromPrefs();
void coordSave();
double coordAxisValue(uint8_t axis) const; // axis 0 = lat, 1 = lon
@@ -91,6 +95,7 @@ private:
void renderValue();
void renderDigits();
void renderFreq();
void renderBattery();
void renderCoords();
void renderCoordsSave();
void renderTitleBar(const char* title);
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

+19
View File
@@ -32,6 +32,8 @@
| `CR` | coding rate, 5–8 | `set radio` |
| `SF` | spreading factor, 5–12 | `set radio` |
| `TX power` | мощность передатчика, 1–30 дБм | `set tx` |
| `Battery` | напряжение, процент заряда и текущий множитель ADC — только просмотр | `get battery`, `get adc.multiplier` |
| `ADC` | множитель ADC для калибровки измерения батареи (0.00 — заводское значение платы) | `set adc.multiplier` |
| `Repeat` | пересылка пакетов включена или выключена | `set repeat` |
| `Path hash` | размер хэша пути: 0, 1 или 2 байта | `set path.hash.mode` |
| `Advert int` | интервал advert в минутах (0 — выключено, иначе 60–240) | `set advert.interval` |
@@ -109,6 +111,23 @@
Любой из четырёх пунктов отправляет команду целиком — `set radio <частота>,<полоса>,<sf>,<cr>`, поэтому остальные три параметра сохраняют свои значения. Устройство отвечает `OK - reboot to apply`: изменения радио вступают в силу после перезагрузки.
## Батарея и калибровка ADC
Пункт `Battery` показывает напряжение в вольтах, процент заряда и полоску, а также текущий множитель ADC. Значения обновляются раз в секунду.
| Экран батареи | Правка множителя ADC |
| --- | --- |
| ![Батарея](_assets/wizard/13-battery.png) | ![ADC](_assets/wizard/14-adc.png) |
- **короткое** нажатие — показать «сырое» значение АЦП в милливольтах (удобно сверять с мультиметром);
- **длинное** или **двойное** — вернуться в меню.
Процент берётся у платы, если она умеет его считать (например T-Beam с PMU). Если не умеет — считается линейно по напряжению между `getBatteryMinMilliVolts()` и `getBatteryMaxMilliVolts()` платы; на большинстве плат это 3000–4200 мВ.
Пункт `ADC` правит множитель ADC поразрядно, формат `x.xx` — так же, как полоса: длинное нажатие входит в правку разряда, короткое `+1`, двойное `−1`, длинное применяет разряд, короткое после применения переходит к следующему разряду, а на пункте `Save & exit` длинное нажатие сохраняет значение и возвращает в меню. Под цифрами видно измеренное напряжение, поэтому калибровать удобно прямо на экране: подстроили множитель — сразу видите новое напряжение.
`0.00` означает «использовать заводское значение платы»: команда `set adc.multiplier 0` возвращает расчёт по умолчанию. Значение применяется командой `set adc.multiplier <x.xx>` — той же, что и в последовательном CLI.
## Ограничения
- На экранах без кириллицы подписи меню выводятся латиницей.