mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-01 10:21:59 +00:00
- 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>
170 lines
7.2 KiB
C++
170 lines
7.2 KiB
C++
#pragma once
|
||
// Settings › Power › Battery curve: the cell voltage at 0, 10, ... 100 %
|
||
// (NodePrefs::batt_curve_mv, ui-core/Battery.h) as a chart. A finger takes
|
||
// the point nearest it along the % axis and drags it up or down in 10 mV
|
||
// steps, kept between its neighbours; the first drag copies the built-in
|
||
// curve in, Reset goes back to it. A dashed line marks the cell now, so a
|
||
// point can be set against what the battery reads. Included by UITask.cpp.
|
||
|
||
namespace battview {
|
||
static lv_obj_t* s_chart = nullptr;
|
||
static lv_obj_t* s_now = nullptr; // "Now 3912 mV 76 %"
|
||
static lv_obj_t* s_reset = nullptr; // header button's label
|
||
static int s_drag = -1; // point under the finger
|
||
static const int PTS = battery::CURVE_PTS;
|
||
static const int MV_LO = 3000, MV_HI = 4400; // the chart's voltage range
|
||
static const int PAD_L = 44, PAD_R = 12, PAD_T = 10, PAD_B = 22;
|
||
|
||
static int ptX(const lv_area_t& a, int i) { return a.x1 + PAD_L + i * (lv_area_get_width(&a) - PAD_L - PAD_R) / (PTS - 1); }
|
||
static int mvY(const lv_area_t& a, int mv) {
|
||
const int h = lv_area_get_height(&a) - PAD_T - PAD_B;
|
||
return a.y2 - PAD_B - (constrain(mv, MV_LO, MV_HI) - MV_LO) * h / (MV_HI - MV_LO);
|
||
}
|
||
static int yMv(const lv_area_t& a, int y) {
|
||
const int h = lv_area_get_height(&a) - PAD_T - PAD_B;
|
||
return MV_LO + (a.y2 - PAD_B - y) * (MV_HI - MV_LO) / h;
|
||
}
|
||
|
||
static void text(lv_layer_t* layer, const char* t, int x, int y, int w, lv_text_align_t align, uint32_t col) {
|
||
lv_draw_label_dsc_t d;
|
||
lv_draw_label_dsc_init(&d);
|
||
d.font = THEME_FONT_SMALL;
|
||
d.color = lv_color_hex(col);
|
||
d.align = align;
|
||
d.text = t;
|
||
d.text_local = 1;
|
||
lv_area_t r = { x, y, x + w - 1, y + 16 };
|
||
lv_draw_label(layer, &d, &r);
|
||
}
|
||
static void line(lv_layer_t* layer, int x1, int y1, int x2, int y2, uint32_t col, int w, int dash = 0) {
|
||
lv_draw_line_dsc_t d;
|
||
lv_draw_line_dsc_init(&d);
|
||
d.color = lv_color_hex(col);
|
||
d.width = w;
|
||
d.dash_width = dash;
|
||
d.dash_gap = dash;
|
||
d.p1.x = x1; d.p1.y = y1; d.p2.x = x2; d.p2.y = y2;
|
||
lv_draw_line(layer, &d);
|
||
}
|
||
|
||
static void onDraw(lv_event_t* e) {
|
||
lv_obj_t* obj = lv_event_get_current_target_obj(e);
|
||
lv_layer_t* layer = lv_event_get_layer(e);
|
||
lv_area_t a;
|
||
lv_obj_get_coords(obj, &a);
|
||
char t[16];
|
||
for (int mv = 3200; mv <= MV_HI; mv += 400) { // voltage grid
|
||
const int y = mvY(a, mv);
|
||
line(layer, a.x1 + PAD_L, y, a.x2 - PAD_R, y, theme::SURFACE_2, 1);
|
||
snprintf(t, sizeof(t), "%d.%d V", mv / 1000, mv % 1000 / 100);
|
||
text(layer, t, a.x1, y - 8, PAD_L - 6, LV_TEXT_ALIGN_RIGHT, theme::TEXT_MUTED);
|
||
}
|
||
for (int i = 0; i < PTS; i += 5) { // 0, 50, 100 %
|
||
snprintf(t, sizeof(t), "%d%%", i * 10);
|
||
const int x = i == PTS - 1 ? a.x2 - 40 : ptX(a, i) - 20; // the last one ends at the edge
|
||
text(layer, t, x, a.y2 - PAD_B + 4, 40, i == PTS - 1 ? LV_TEXT_ALIGN_RIGHT : LV_TEXT_ALIGN_CENTER, theme::TEXT_MUTED);
|
||
}
|
||
const int now = s_ui ? s_ui->battMv() : 0;
|
||
if (now > 0) line(layer, a.x1 + PAD_L, mvY(a, now), a.x2 - PAD_R, mvY(a, now), theme::OK, 1, 3);
|
||
for (int i = 1; i < PTS; i++)
|
||
line(layer, ptX(a, i - 1), mvY(a, battery::curveMvAt(i - 1)), ptX(a, i), mvY(a, battery::curveMvAt(i)), theme::ACCENT, 2);
|
||
for (int i = 0; i < PTS; i++) {
|
||
lv_draw_rect_dsc_t r;
|
||
lv_draw_rect_dsc_init(&r);
|
||
r.radius = LV_RADIUS_CIRCLE;
|
||
r.bg_color = lv_color_hex(i == s_drag ? theme::ACCENT : theme::BG);
|
||
r.border_color = lv_color_hex(theme::ACCENT);
|
||
r.border_width = 2;
|
||
const int d = i == s_drag ? 7 : 4, x = ptX(a, i), y = mvY(a, battery::curveMvAt(i));
|
||
lv_area_t c = { x - d, y - d, x + d, y + d };
|
||
lv_draw_rect(layer, &r, &c);
|
||
}
|
||
if (s_drag >= 0) { // the point being moved, above it
|
||
const int x = ptX(a, s_drag), y = mvY(a, battery::curveMvAt(s_drag));
|
||
snprintf(t, sizeof(t), "%d%% %d mV", s_drag * 10, battery::curveMvAt(s_drag));
|
||
const int lx = constrain(x - 50, (int)a.x1, (int)a.x2 - 100);
|
||
lv_draw_rect_dsc_t bg; // on a pill, clear of the grid and the "now" line
|
||
lv_draw_rect_dsc_init(&bg);
|
||
bg.radius = theme::RADIUS_SM;
|
||
bg.bg_color = lv_color_hex(theme::SURFACE_2);
|
||
lv_area_t ba = { lx, y - 30, lx + 99, y - 12 };
|
||
lv_draw_rect(layer, &bg, &ba);
|
||
text(layer, t, lx, y - 29, 100, LV_TEXT_ALIGN_CENTER, theme::TEXT);
|
||
}
|
||
}
|
||
|
||
static void onTouch(lv_event_t* e) {
|
||
lv_event_code_t code = lv_event_get_code(e);
|
||
lv_obj_t* obj = lv_event_get_current_target_obj(e);
|
||
lv_indev_t* in = lv_indev_active();
|
||
lv_point_t p = { 0, 0 };
|
||
if (in) lv_indev_get_point(in, &p);
|
||
lv_area_t a;
|
||
lv_obj_get_coords(obj, &a);
|
||
if (code == LV_EVENT_PRESSED) { // the point nearest along the % axis
|
||
int best = 0;
|
||
for (int i = 1; i < PTS; i++) if (abs(ptX(a, i) - p.x) < abs(ptX(a, best) - p.x)) best = i;
|
||
s_drag = best;
|
||
}
|
||
NodePrefs* np = s_ui->prefsMut();
|
||
if ((code == LV_EVENT_PRESSED || code == LV_EVENT_PRESSING) && s_drag >= 0 && np) {
|
||
uint16_t* c = np->batt_curve_mv;
|
||
if (!battery::validCurve(c))
|
||
for (int k = 0; k < PTS; k++) c[k] = (uint16_t)battery::builtinMvAt(k);
|
||
const int lo = s_drag > 0 ? c[s_drag - 1] + 10 : battery::CURVE_MIN_MV;
|
||
const int hi = s_drag < PTS - 1 ? c[s_drag + 1] - 10 : battery::CURVE_MAX_MV;
|
||
const int mv = constrain((yMv(a, p.y) + 5) / 10 * 10, lo, hi);
|
||
if (mv != c[s_drag]) { c[s_drag] = (uint16_t)mv; lv_obj_invalidate(obj); s_ui->refreshBattCurve(); }
|
||
}
|
||
if (code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) {
|
||
s_drag = -1;
|
||
lv_obj_invalidate(obj);
|
||
prefsSave();
|
||
}
|
||
}
|
||
|
||
static void onReset(lv_event_t* e) {
|
||
(void)e;
|
||
NodePrefs* np = s_ui->prefsMut();
|
||
if (!np || !battery::validCurve(np->batt_curve_mv)) return;
|
||
memset(np->batt_curve_mv, 0, sizeof(np->batt_curve_mv));
|
||
prefsSave();
|
||
if (s_chart) lv_obj_invalidate(s_chart);
|
||
s_ui->refreshBattCurve();
|
||
}
|
||
} // namespace battview
|
||
|
||
void UITask::showBattCurve() {
|
||
using namespace battview;
|
||
_screen = SCR_BATT;
|
||
lv_obj_t* body = newScreen("Battery curve", true);
|
||
headerButton(_header, LV_SYMBOL_REFRESH " Reset", onReset, 8, &s_reset);
|
||
lv_obj_remove_flag(body, LV_OBJ_FLAG_SCROLLABLE);
|
||
s_chart = lv_obj_create(body);
|
||
styleSurface(s_chart, theme::BG);
|
||
lv_obj_remove_flag(s_chart, LV_OBJ_FLAG_SCROLLABLE);
|
||
lv_obj_set_width(s_chart, LV_PCT(100));
|
||
lv_obj_set_flex_grow(s_chart, 1);
|
||
lv_obj_add_flag(s_chart, LV_OBJ_FLAG_CLICKABLE);
|
||
lv_obj_add_flag(s_chart, LV_OBJ_FLAG_PRESS_LOCK); // still its drag when the finger strays off it
|
||
lv_obj_add_event_cb(s_chart, onDraw, LV_EVENT_DRAW_MAIN_END, NULL);
|
||
lv_obj_add_event_cb(s_chart, onTouch, LV_EVENT_ALL, NULL);
|
||
s_now = noteLabel(body, "");
|
||
s_drag = -1;
|
||
refreshBattCurve();
|
||
}
|
||
|
||
// Once a second from loop(), and after a change: the cell now, the reset's state.
|
||
void UITask::refreshBattCurve() {
|
||
using namespace battview;
|
||
if (_screen != SCR_BATT || !s_now) return;
|
||
const int mv = battMv();
|
||
const bool custom = _prefs && battery::validCurve(_prefs->batt_curve_mv);
|
||
char t[64];
|
||
if (mv > 0) snprintf(t, sizeof(t), "Now %d mV - %d %% - %s curve", mv, battery::rawPercent(mv), custom ? "your" : "LiPo");
|
||
else snprintf(t, sizeof(t), "%s curve - drag a point up or down", custom ? "Your" : "LiPo");
|
||
lv_label_set_text(s_now, t);
|
||
if (s_reset) lv_obj_set_style_text_opa(s_reset, custom ? LV_OPA_COVER : LV_OPA_40, 0);
|
||
if (s_chart && s_drag < 0) lv_obj_invalidate(s_chart); // the "now" line moves with the cell
|
||
}
|