diff --git a/README.md b/README.md index a2c284dc..efb2c5ec 100644 --- a/README.md +++ b/README.md @@ -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 <частота>,<полоса>,,`, поэтому остальные значения сохраняются; - остальные пункты: мощность 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, координаты, подтверждение, ответ устройства); та же документация опубликована в вики проекта — ; +- документация с иллюстрациями: страница [Настройка с дисплея и кнопки](./posadmesh-docs/device-setup.md) дополнена схемой управления и изображениями всех экранов (состояние, меню, частота и правка разряда, полоса, CR, SF, координаты, подтверждение, ответ устройства, батарея, ADC); та же документация опубликована в вики проекта — ; - сборка проверена на `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 ` убрана вместе с ручным опросом из `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` читает значение по правильному смещению; diff --git a/examples/simple_repeater/UITask.cpp b/examples/simple_repeater/UITask.cpp index c3f2b363..b35fab1f 100644 --- a/examples/simple_repeater/UITask.cpp +++ b/examples/simple_repeater/UITask.cpp @@ -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; diff --git a/examples/simple_repeater/UITask.h b/examples/simple_repeater/UITask.h index bdf3c6a1..c8f35da1 100644 --- a/examples/simple_repeater/UITask.h +++ b/examples/simple_repeater/UITask.h @@ -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); diff --git a/posadmesh-docs/_assets/wizard/13-battery.png b/posadmesh-docs/_assets/wizard/13-battery.png new file mode 100644 index 00000000..674d4f7e Binary files /dev/null and b/posadmesh-docs/_assets/wizard/13-battery.png differ diff --git a/posadmesh-docs/_assets/wizard/14-adc.png b/posadmesh-docs/_assets/wizard/14-adc.png new file mode 100644 index 00000000..87edb441 Binary files /dev/null and b/posadmesh-docs/_assets/wizard/14-adc.png differ diff --git a/posadmesh-docs/device-setup.md b/posadmesh-docs/device-setup.md index cc25cfd7..2e8a685d 100644 --- a/posadmesh-docs/device-setup.md +++ b/posadmesh-docs/device-setup.md @@ -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 <частота>,<полоса>,,`, поэтому остальные три параметра сохраняют свои значения. Устройство отвечает `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 ` — той же, что и в последовательном CLI. + ## Ограничения - На экранах без кириллицы подписи меню выводятся латиницей.