feat(l2,core): battery curve editor and an L2 feedback round

- Battery curve: NodePrefs batt_curve_mv[11] (voltage at 0..100 %, all
  zeros = built-in LiPo curve, sentinel 0xC0DE0032). ui-core/Battery.h
  uses it for every percentage. L1: Settings > System > Batt curve list
  (Left/Right 10 mV, Reset); L2: Settings > Power > Battery curve chart
  with draggable points and a line at the cell's voltage.
- L2: the Nodes map is the full navigation map plus the nodes, Back
  returns to Nodes; the favourite button is lit while it is one; pinning
  to the Home dial uses a thumbtack (fonts regenerated); minimap dots
  follow the accent; arranging tiles shows where they land while dragging;
  emoji row in Diagnostics > Font.
- Sim hooks: sim_open nodesmap / accent@N / homeedit / battcurve.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-10-02 11:25:58 +02:00
co-authored by Claude Opus 5.5
parent 1166ea8dde
commit 61c75946f6
21 changed files with 702 additions and 227 deletions
@@ -9,6 +9,7 @@
#include "AccordionList.h"
#include "PopupMenu.h" // scope list management's per-row action menu
#include "InfoKit.h" // value / switch rows
#include "../ui-core/Battery.h" // the Battery curve row
class SettingsScreen : public UIScreen {
UITask* _task;
@@ -49,6 +50,7 @@ class SettingsScreen : public UIScreen {
SECTION_SYSTEM,
DEVICE_NAME,
SCHEMA_SYSTEM, // power, units
BATT_CURVE,
REBOOT,
// Keyboard section
SECTION_KEYBOARD,
@@ -78,6 +80,7 @@ class SettingsScreen : public UIScreen {
int _selected = 0; // SettingItem under the cursor, resolved per input/render
int _reserve = 0; // right-edge px reserved for the scrollbar (0 when list fits)
bool _dirty = false;
bool _keep_place = false; // set when opening a screen from a row, so Back lands on it
AccordionList _acc;
static const int NUM_SECTIONS = 8;
@@ -512,6 +515,8 @@ class SettingsScreen : public UIScreen {
sw("Lock PIN", _task->passwordLockEnabled());
} else if (item == DEVICE_NAME) {
val("Name", the_mesh.getNodeName());
} else if (item == BATT_CURVE) {
val("Batt curve", p && battery::validCurve(p->batt_curve_mv) ? "Custom" : "LiPo");
} else if (item == REBOOT) {
display.print("Reboot"); // action row: Enter reboots this device
} else if (item == KEYBOARD_TYPE) {
@@ -629,6 +634,8 @@ public:
void onShow() override {
const bool keep = _keep_place; // back from a screen opened here: same row
_keep_place = false;
_dirty = false;
_edit_name = false;
_scope_mgmt_active = false;
@@ -638,7 +645,7 @@ public:
_prune_confirm.active = false;
_edit_lock_pass = false;
_lock_pass_first[0] = '\0';
resetList();
if (!keep) resetList();
_editor.freq.active = false;
}
@@ -981,6 +988,12 @@ public:
_scope_mgmt_scroll = 0;
return true;
}
if (_selected == BATT_CURVE && enter) {
_keep_place = true; // back from it to this row, not a folded list
_task->savePrefsIfDirty(_dirty);
_task->gotoBatteryCurve();
return true;
}
if (_selected == REBOOT && enter) {
_task->showAlert("Rebooting...", 800);
_task->shutdown(true); // flushes prefs/RTC/contacts/trail, then reboots -- single choke point