Added some colour and reworked some of the screens

This commit is contained in:
Matthew Burton
2026-06-29 21:05:22 +02:00
parent 43f455393e
commit 7748480d46
+445 -51
View File
@@ -56,6 +56,22 @@ LOG_MODULE_REGISTER(ui_pages, CONFIG_ZEPHCORE_BOARD_LOG_LEVEL);
#define CONTENT_Y (SEP_Y + 4) /* below separator */
#define MAX_CHARS (DISP_W / (FONT_W ? FONT_W : 1))
/* Compact RGB565 palette for small color TFTs. These are used only through
* mc_display_has_color(); monochrome displays keep the existing CFB path. */
#define UI_COLOR_HEADER_BG 0x018c /* deep teal/blue */
#define UI_COLOR_TITLE 0x5dff /* readable cyan */
#define UI_COLOR_LABEL 0x8c71 /* muted blue-gray */
#define UI_COLOR_VALUE MC_COLOR_WHITE
#define UI_COLOR_OK 0x07e0
#define UI_COLOR_ACTIVE 0x05ff
#define UI_COLOR_WARN 0xffa0
#define UI_COLOR_ERROR MC_COLOR_RED
#define UI_COLOR_DIM 0x4208
#define UI_COLOR_DISABLED MC_COLOR_GRAY
#define COLOR_FONT_W 6
#define COLOR_FONT_H 8
/* Vertically center `total` rows of text within the content area and
* return the y pixel position for row `idx` (0-based). This replaces
* the old hand-picked offsets (CONTENT_Y+16, +28, +34, ...) that were
@@ -141,6 +157,7 @@ static char time_source_tag(enum time_sync_source src)
static void render_top_bar(void)
{
bool color = mc_display_has_color();
/* Right side: "HH:MM<S> XX%" — clock (with 1-char source tag) then
* battery, right-aligned. Built first so the node name can be clipped
* to the space that remains on the left, preventing overlap. */
@@ -173,15 +190,33 @@ static void render_top_bar(void)
int right_x = DISP_W;
if (right[0]) {
right_x = DISP_W - ((int)strlen(right) * FONT_W);
mc_display_text(right_x, TOP_BAR_Y, right, false);
int fw = color ? COLOR_FONT_W : FONT_W;
uint16_t batt_color = UI_COLOR_VALUE;
if (state.battery_mv > 0) {
uint8_t pct = state.battery_pct;
if (pct == 0) {
pct = calc_battery_pct(state.battery_mv);
}
batt_color = (pct <= 15) ? UI_COLOR_ERROR :
(pct <= 30) ? UI_COLOR_WARN : UI_COLOR_OK;
}
right_x = DISP_W - ((int)strlen(right) * fw);
if (color) {
mc_display_color_text(right_x, TOP_BAR_Y, right, batt_color);
} else {
mc_display_text(right_x, TOP_BAR_Y, right, false);
}
}
/* Node name on the left, clipped to the gap before the right block
* (one char-width of padding so it never touches the clock). */
if (state.node_name[0]) {
char name[16];
int max_chars = (right_x - FONT_W) / FONT_W;
int fw = color ? COLOR_FONT_W : FONT_W;
int max_chars = (right_x - fw) / fw;
if (max_chars > (int)sizeof(name) - 1) {
max_chars = sizeof(name) - 1;
@@ -191,7 +226,11 @@ static void render_top_bar(void)
}
strncpy(name, state.node_name, max_chars);
name[max_chars] = '\0';
mc_display_text(0, TOP_BAR_Y, name, false);
if (color) {
mc_display_color_text(0, TOP_BAR_Y, name, UI_COLOR_TITLE);
} else {
mc_display_text(0, TOP_BAR_Y, name, false);
}
}
}
@@ -202,6 +241,7 @@ static void render_page_indicator(void)
/* Centered dots matching Arduino layout:
* Each dot spaced 10px apart, centered on screen.
* Current page = filled 3x3 block, others = single pixel. */
bool color = mc_display_has_color();
int total = ACTIVE_PAGE_COUNT;
int dot_spacing = 10;
int total_width = (total - 1) * dot_spacing;
@@ -212,15 +252,29 @@ static void render_page_indicator(void)
if (i == current_page_idx) {
/* Current page: filled 3x3 block */
mc_display_fill_rect(x - 1, DOTS_Y, 3, 3);
if (color) {
mc_display_color_fill_rect(x - 2, DOTS_Y - 1, 5, 5,
UI_COLOR_ACTIVE);
} else {
mc_display_fill_rect(x - 1, DOTS_Y, 3, 3);
}
} else {
/* Other pages: single pixel */
mc_display_fill_rect(x, DOTS_Y + 1, 1, 1);
if (color) {
mc_display_color_fill_rect(x, DOTS_Y + 1, 1, 1,
UI_COLOR_DIM);
} else {
mc_display_fill_rect(x, DOTS_Y + 1, 1, 1);
}
}
}
/* Separator line below dots */
mc_display_hline(0, SEP_Y, DISP_W);
if (color) {
mc_display_color_fill_rect(0, SEP_Y, DISP_W, 1, UI_COLOR_DIM);
} else {
mc_display_hline(0, SEP_Y, DISP_W);
}
}
/* ========== Helper: Center text ========== */
@@ -236,14 +290,77 @@ static void draw_centered(int y, const char *text)
mc_display_text(x, y, text, false);
}
static void draw_centered_color(int y, const char *text, uint16_t color)
{
int len = (int)strlen(text);
int x = (DISP_W - (len * COLOR_FONT_W)) / 2;
if (x < 0) {
x = 0;
}
mc_display_color_text(x, y, text, color);
}
static void draw_color_segments_at(int x, int y, const char *label,
const char *value, uint16_t value_color)
{
mc_display_color_text(x, y, label, UI_COLOR_LABEL);
x += (int)strlen(label) * 6;
mc_display_color_text(x, y, value, value_color);
}
static void draw_color_segments(int y, const char *label,
const char *value, uint16_t value_color)
{
int x = 0;
draw_color_segments_at(0, y, label, value, value_color);
}
mc_display_color_text(x, y, label, MC_COLOR_CYAN);
x += (int)strlen(label) * 6;
mc_display_color_text(x, y, value, value_color);
static int color_text_width(const char *text)
{
return (int)strlen(text) * COLOR_FONT_W;
}
static void draw_badge(int x, int y, const char *text, uint16_t color)
{
int w = color_text_width(text) + 4;
mc_display_color_fill_rect(x, y - 1, w, COLOR_FONT_H + 2, UI_COLOR_DIM);
mc_display_color_text(x + 2, y, text, color);
}
static void draw_metric_bar(int x, int y, int w, int h, int value,
int max_value, uint16_t color)
{
if (w <= 0 || h <= 0) {
return;
}
if (max_value <= 0) {
max_value = 1;
}
if (value < 0) {
value = 0;
}
if (value > max_value) {
value = max_value;
}
int filled = (w * value) / max_value;
mc_display_color_fill_rect(x, y, w, h, UI_COLOR_DIM);
if (filled > 0) {
mc_display_color_fill_rect(x, y, filled, h, color);
}
}
static void fmt_compact_count(char *buf, size_t len, uint32_t value)
{
if (value >= 1000000U) {
snprintf(buf, len, "%luM", (unsigned long)(value / 1000000U));
} else if (value >= 1000U) {
snprintf(buf, len, "%luk", (unsigned long)(value / 1000U));
} else {
snprintf(buf, len, "%lu", (unsigned long)value);
}
}
/* ========== Page Renderers ========== */
@@ -253,6 +370,38 @@ static void render_messages(void)
/* 3 centered rows: msg count, BLE status, offgrid status */
char buf[24];
if (mc_display_has_color()) {
int y = CONTENT_Y;
uint16_t ble_color = state.ble_connected ? UI_COLOR_OK : UI_COLOR_WARN;
const char *ble = state.ble_connected ? "BLE OK" : "BLE ADV";
draw_badge(0, y, ble, ble_color);
draw_badge(DISP_W - color_text_width(state.offgrid_enabled ? "GRID" : "LOCAL") - 4,
y, state.offgrid_enabled ? "GRID" : "LOCAL",
state.offgrid_enabled ? UI_COLOR_ACTIVE : UI_COLOR_DISABLED);
y += LINE_H + 2;
mc_display_color_text(0, y, "MESSAGES", UI_COLOR_LABEL);
snprintf(buf, sizeof(buf), "%u", state.msg_count);
mc_display_color_text(DISP_W - color_text_width(buf), y, buf,
state.msg_count ? UI_COLOR_WARN : UI_COLOR_VALUE);
y += LINE_H;
if (state.ble_connected) {
draw_centered_color(y, "Connected", UI_COLOR_OK);
} else {
draw_centered_color(y, "Waiting for app", UI_COLOR_WARN);
}
y += LINE_H;
snprintf(buf, sizeof(buf), "Offgrid %s",
state.offgrid_enabled ? "on" : "off");
draw_centered_color(y, buf,
state.offgrid_enabled ? UI_COLOR_ACTIVE
: UI_COLOR_DISABLED);
return;
}
snprintf(buf, sizeof(buf), "MSG: %u", state.msg_count);
draw_centered(centered_row(0, 3), buf);
@@ -270,7 +419,12 @@ static void render_messages(void)
static void render_recent(void)
{
if (state.recent_count == 0) {
draw_centered(centered_row(0, 1), "(no contacts heard)");
if (mc_display_has_color()) {
draw_centered_color(centered_row(0, 1), "no contacts heard",
UI_COLOR_DISABLED);
} else {
draw_centered(centered_row(0, 1), "(no contacts heard)");
}
return;
}
@@ -299,7 +453,18 @@ static void render_recent(void)
snprintf(buf, sizeof(buf), "%-13s %s",
state.recent[i].name, time_str);
mc_display_text(0, y, buf, false);
if (mc_display_has_color()) {
uint16_t age_color = (age_s < 300) ? UI_COLOR_OK :
(age_s < 3600) ? UI_COLOR_WARN
: UI_COLOR_DISABLED;
mc_display_color_text(0, y, state.recent[i].name,
UI_COLOR_VALUE);
mc_display_color_text(DISP_W - color_text_width(time_str),
y, time_str, age_color);
} else {
mc_display_text(0, y, buf, false);
}
y += LINE_H;
}
}
@@ -316,15 +481,30 @@ static void render_radio(void)
(state.lora_in_rx ? "RX" :
(state.lora_radio_ready ? "RDY" : "WAIT"));
const char *rx_mode = state.lora_rx_duty_cycle ? "DC" : "CONT";
uint16_t apc_color = state.lora_apc_enabled ? MC_COLOR_GREEN : MC_COLOR_GRAY;
uint16_t warn_color = (state.lora_apc_enabled && state.lora_apc_reduction > 0)
? MC_COLOR_ORANGE : MC_COLOR_GREEN;
? UI_COLOR_WARN : UI_COLOR_OK;
if (mc_display_has_color()) {
mc_display_color_fill_rect(0, y - 1, DISP_W, FONT_H + 2, MC_COLOR_BLUE);
snprintf(buf, sizeof(buf), "companion %s/%s", packet_state, rx_mode);
mc_display_color_text(2, y, "RAD", MC_COLOR_YELLOW);
mc_display_color_text(26, y, buf, MC_COLOR_WHITE);
uint16_t state_color = state.lora_tx_active ? UI_COLOR_WARN :
state.lora_in_rx ? UI_COLOR_ACTIVE :
state.lora_radio_ready ? UI_COLOR_OK
: UI_COLOR_DISABLED;
uint16_t tx_color = (state.lora_apc_enabled &&
state.lora_apc_reduction > 0)
? UI_COLOR_WARN : UI_COLOR_OK;
int max_tx = state.lora_tx_power > 0 ? state.lora_tx_power : 22;
int eff_tx = state.lora_effective_tx_power > 0
? state.lora_effective_tx_power : state.lora_tx_power;
int badge_x;
mc_display_color_fill_rect(0, y - 1, DISP_W, COLOR_FONT_H + 2,
UI_COLOR_HEADER_BG);
mc_display_color_text(2, y, "RADIO", UI_COLOR_TITLE);
badge_x = DISP_W - color_text_width(rx_mode) - 6;
draw_badge(badge_x, y, rx_mode,
state.lora_rx_duty_cycle ? UI_COLOR_WARN : UI_COLOR_ACTIVE);
badge_x -= color_text_width(packet_state) + 8;
draw_badge(badge_x, y, packet_state, state_color);
y += LINE_H;
if (bw_frac) {
@@ -334,22 +514,23 @@ static void render_radio(void)
snprintf(buf, sizeof(buf), "%u.%03u BW%u",
freq_mhz, freq_frac, bw_int);
}
draw_color_segments(y, "RF ", buf, MC_COLOR_WHITE);
draw_color_segments(y, "RF ", buf, UI_COLOR_VALUE);
y += LINE_H;
snprintf(buf, sizeof(buf), "SF%u CR%u SW%02X P%u",
state.lora_sf, state.lora_cr, state.lora_sync_word,
state.lora_preamble_len);
draw_color_segments(y, "LoRa ", buf, MC_COLOR_WHITE);
draw_color_segments(y, "LoRa ", buf, UI_COLOR_VALUE);
y += LINE_H;
if (state.lora_apc_enabled) {
snprintf(buf, sizeof(buf), "%d/%ddBm APC on",
snprintf(buf, sizeof(buf), "%d/%ddBm",
state.lora_effective_tx_power, state.lora_tx_power);
} else {
snprintf(buf, sizeof(buf), "%ddBm APC off", state.lora_tx_power);
snprintf(buf, sizeof(buf), "%ddBm", state.lora_tx_power);
}
draw_color_segments(y, "TX ", buf, apc_color);
draw_color_segments(y, "TX ", buf, tx_color);
draw_metric_bar(DISP_W - 50, y + 2, 48, 5, eff_tx, max_tx, tx_color);
y += LINE_H;
snprintf(buf, sizeof(buf), "red %d M%d.%d T%u",
@@ -358,15 +539,23 @@ static void render_radio(void)
abs(state.lora_apc_margin_x10 % 10),
state.lora_apc_target_margin);
draw_color_segments(y, "APC ", buf, warn_color);
draw_badge(DISP_W - color_text_width("APC") - 4, y, "APC",
!state.lora_apc_enabled ? UI_COLOR_DISABLED :
state.lora_apc_reduction > 0 ? UI_COLOR_WARN : UI_COLOR_OK);
y += LINE_H;
snprintf(buf, sizeof(buf), "NF%d R%lu T%lu E%lu",
state.lora_noise_floor,
(unsigned long)state.lora_packets_rx,
(unsigned long)state.lora_packets_tx,
(unsigned long)state.lora_packets_err);
char rx_count[6];
char tx_count[6];
char err_count[6];
fmt_compact_count(rx_count, sizeof(rx_count), state.lora_packets_rx);
fmt_compact_count(tx_count, sizeof(tx_count), state.lora_packets_tx);
fmt_compact_count(err_count, sizeof(err_count), state.lora_packets_err);
snprintf(buf, sizeof(buf), "NF%d R%s T%s E%s",
state.lora_noise_floor, rx_count, tx_count, err_count);
draw_color_segments(y, "PKT ", buf,
state.lora_packets_err ? MC_COLOR_RED : MC_COLOR_WHITE);
state.lora_packets_err ? UI_COLOR_ERROR
: UI_COLOR_VALUE);
return;
}
@@ -413,6 +602,24 @@ static void render_radio(void)
static void render_bluetooth(void)
{
if (mc_display_has_color()) {
int y = CONTENT_Y;
uint16_t color = !state.ble_enabled ? UI_COLOR_DISABLED :
state.ble_connected ? UI_COLOR_OK : UI_COLOR_WARN;
draw_badge(0, y, "BLE", color);
mc_display_color_text(32, y,
!state.ble_enabled ? "disabled" :
state.ble_connected ? "connected" : "advertising",
color);
y += LINE_H + 4;
draw_centered_color(y,
state.ble_enabled ? "Press to disable"
: "Press to enable",
UI_COLOR_VALUE);
return;
}
if (!state.ble_enabled) {
draw_centered(centered_row(0, 2), "BLE: OFF");
draw_centered(centered_row(1, 2), "Press to Enable");
@@ -435,6 +642,25 @@ static void render_advert(void)
bool just_sent = (state.advert_sent_time > 0 &&
(now - state.advert_sent_time) < 2000);
if (mc_display_has_color()) {
int y = CONTENT_Y;
draw_badge(0, y, "ADV", just_sent ? UI_COLOR_OK : UI_COLOR_ACTIVE);
mc_display_color_text(32, y, "Zero-hop advert", UI_COLOR_VALUE);
y += LINE_H + 2;
if (just_sent) {
draw_centered_color(y,
state.advert_was_flood ? "Flood sent" : "Sent",
UI_COLOR_OK);
} else {
draw_centered_color(y, "Press to send", UI_COLOR_VALUE);
}
y += LINE_H;
draw_centered_color(y, "2x press: flood", UI_COLOR_LABEL);
return;
}
draw_centered(centered_row(0, 3), "Send Zero-Hop Advert");
if (just_sent) {
@@ -466,21 +692,39 @@ static void render_gps(void)
{
char buf[32];
int y = CONTENT_Y;
bool color = mc_display_has_color();
/* No GPS hardware on this board */
if (!state.gps_available) {
mc_display_text(0, y, "GPS: not detected", false);
if (color) {
draw_badge(0, y, "GPS", UI_COLOR_DISABLED);
mc_display_color_text(32, y, "not detected", UI_COLOR_DISABLED);
} else {
mc_display_text(0, y, "GPS: not detected", false);
}
return;
}
/* GPS enabled/disabled state */
snprintf(buf, sizeof(buf), "GPS: %s",
state.gps_enabled ? "on" : "off");
mc_display_text(0, y, buf, false);
if (color) {
draw_badge(0, y, "GPS", state.gps_enabled ? UI_COLOR_OK
: UI_COLOR_DISABLED);
mc_display_color_text(32, y, state.gps_enabled ? "on" : "off",
state.gps_enabled ? UI_COLOR_OK
: UI_COLOR_DISABLED);
} else {
mc_display_text(0, y, buf, false);
}
y += LINE_H;
if (!state.gps_enabled) {
draw_centered(y + 8, "Press to Enable");
if (color) {
draw_centered_color(y + 8, "Press to enable", UI_COLOR_VALUE);
} else {
draw_centered(y + 8, "Press to Enable");
}
return;
}
@@ -489,7 +733,11 @@ static void render_gps(void)
/* ACQUIRING — actively searching for satellites */
snprintf(buf, sizeof(buf), "Searching... sat:%u",
state.gps_satellites);
mc_display_text(0, y, buf, false);
if (color) {
draw_color_segments(y, "SAT ", buf, UI_COLOR_WARN);
} else {
mc_display_text(0, y, buf, false);
}
y += LINE_H;
/* Show last fix age if we have one */
@@ -498,9 +746,17 @@ static void render_gps(void)
fmt_duration(tbuf, sizeof(tbuf), state.gps_last_fix_age_s);
snprintf(buf, sizeof(buf), "Last fix: %s ago", tbuf);
mc_display_text(0, y, buf, false);
if (color) {
draw_color_segments(y, "FIX ", buf, UI_COLOR_VALUE);
} else {
mc_display_text(0, y, buf, false);
}
} else {
mc_display_text(0, y, "No fix yet", false);
if (color) {
mc_display_color_text(0, y, "No fix yet", UI_COLOR_WARN);
} else {
mc_display_text(0, y, "No fix yet", false);
}
}
} else if (state.gps_state == 1) {
/* STANDBY — sleeping between fix cycles */
@@ -509,10 +765,18 @@ static void render_gps(void)
fmt_duration(tbuf, sizeof(tbuf), state.gps_last_fix_age_s);
snprintf(buf, sizeof(buf), "Last fix: %s ago", tbuf);
mc_display_text(0, y, buf, false);
if (color) {
draw_color_segments(y, "FIX ", buf, UI_COLOR_VALUE);
} else {
mc_display_text(0, y, buf, false);
}
y += LINE_H;
} else {
mc_display_text(0, y, "No fix yet", false);
if (color) {
mc_display_color_text(0, y, "No fix yet", UI_COLOR_WARN);
} else {
mc_display_text(0, y, "No fix yet", false);
}
y += LINE_H;
}
@@ -521,11 +785,19 @@ static void render_gps(void)
fmt_duration(tbuf, sizeof(tbuf), state.gps_next_search_s);
snprintf(buf, sizeof(buf), "Next search: %s", tbuf);
mc_display_text(0, y, buf, false);
if (color) {
draw_color_segments(y, "NEXT ", buf, UI_COLOR_LABEL);
} else {
mc_display_text(0, y, buf, false);
}
}
} else {
/* OFF — shouldn't reach here if gps_enabled is true */
mc_display_text(0, y, "GPS off", false);
if (color) {
mc_display_color_text(0, y, "GPS off", UI_COLOR_DISABLED);
} else {
mc_display_text(0, y, "GPS off", false);
}
}
}
@@ -548,6 +820,21 @@ static void render_leds(void)
char buf[24];
int y = CONTENT_Y;
if (mc_display_has_color()) {
draw_badge(0, y, "LED", state.leds_disabled ? UI_COLOR_DISABLED
: UI_COLOR_OK);
mc_display_color_text(32, y,
state.leds_disabled ? "off" : "on",
state.leds_disabled ? UI_COLOR_DISABLED
: UI_COLOR_OK);
y += LINE_H + 4;
draw_centered_color(y,
state.leds_disabled ? "Press to enable"
: "Press to disable",
UI_COLOR_VALUE);
return;
}
snprintf(buf, sizeof(buf), "LEDs: %s",
state.leds_disabled ? "off" : "on");
mc_display_text(0, y, buf, false);
@@ -561,6 +848,7 @@ static void render_sensors(void)
{
char buf[24];
int y = CONTENT_Y;
bool color = mc_display_has_color();
/* Lazy read: only fetch sensors when the user is actually looking at
* this page. Render is event-driven (schedule_render only on real UI
@@ -572,14 +860,22 @@ static void render_sensors(void)
int16_t t10 = (int16_t)(edata.temperature_c * 10);
snprintf(buf, sizeof(buf), "Temp: %d.%d C",
t10 / 10, abs(t10 % 10));
mc_display_text(0, y, buf, false);
if (color) {
draw_color_segments(y, "TMP ", buf, UI_COLOR_VALUE);
} else {
mc_display_text(0, y, buf, false);
}
y += LINE_H;
}
if (have_env && edata.has_pressure) {
snprintf(buf, sizeof(buf), "Press: %u hPa",
(unsigned)edata.pressure_hpa);
mc_display_text(0, y, buf, false);
if (color) {
draw_color_segments(y, "BAR ", buf, UI_COLOR_VALUE);
} else {
mc_display_text(0, y, buf, false);
}
y += LINE_H;
}
@@ -587,7 +883,11 @@ static void render_sensors(void)
uint16_t h10 = (uint16_t)(edata.humidity_pct * 10);
snprintf(buf, sizeof(buf), "Humid: %u.%u%%",
h10 / 10, h10 % 10);
mc_display_text(0, y, buf, false);
if (color) {
draw_color_segments(y, "HUM ", buf, UI_COLOR_VALUE);
} else {
mc_display_text(0, y, buf, false);
}
y += LINE_H;
}
@@ -596,7 +896,18 @@ static void render_sensors(void)
state.battery_pct > 0 ? state.battery_pct
: calc_battery_pct(state.battery_mv),
state.battery_mv);
mc_display_text(0, y, buf, false);
if (color) {
uint8_t pct = state.battery_pct > 0 ? state.battery_pct
: calc_battery_pct(state.battery_mv);
uint16_t batt_color = (pct <= 15) ? UI_COLOR_ERROR :
(pct <= 30) ? UI_COLOR_WARN : UI_COLOR_OK;
snprintf(buf, sizeof(buf), "%u%% %umV", pct, state.battery_mv);
draw_color_segments(y, "BAT ", buf, batt_color);
draw_metric_bar(DISP_W - 44, y + 2, 42, 5, pct, 100, batt_color);
} else {
mc_display_text(0, y, buf, false);
}
}
static void render_offgrid(void)
@@ -607,10 +918,35 @@ static void render_offgrid(void)
state.offgrid_confirm_time = 0;
}
draw_centered(centered_row(0, 3), "Offgrid Mode");
char buf[24];
if (mc_display_has_color()) {
int y = CONTENT_Y;
draw_badge(0, y, "GRID", state.offgrid_enabled ? UI_COLOR_ACTIVE
: UI_COLOR_DISABLED);
mc_display_color_text(38, y, "client repeat",
state.offgrid_enabled ? UI_COLOR_ACTIVE
: UI_COLOR_LABEL);
y += LINE_H + 4;
snprintf(buf, sizeof(buf), "Status %s",
state.offgrid_enabled ? "on" : "off");
draw_centered_color(y, buf,
state.offgrid_enabled ? UI_COLOR_ACTIVE
: UI_COLOR_DISABLED);
y += LINE_H;
draw_centered_color(y,
state.offgrid_confirm_time != 0 ?
"Press to confirm" :
(state.offgrid_enabled ? "Press to disable"
: "Press to enable"),
state.offgrid_confirm_time != 0 ? UI_COLOR_WARN
: UI_COLOR_VALUE);
return;
}
draw_centered(centered_row(0, 3), "Offgrid Mode");
snprintf(buf, sizeof(buf), "Status: %s",
state.offgrid_enabled ? "ON" : "OFF");
draw_centered(centered_row(1, 3), buf);
@@ -632,6 +968,21 @@ static void render_dfu(void)
state.dfu_confirm_time = 0;
}
if (mc_display_has_color()) {
int y = CONTENT_Y;
draw_badge(0, y, "DFU", state.dfu_confirm_time != 0
? UI_COLOR_WARN : UI_COLOR_ACTIVE);
mc_display_color_text(32, y, "BLE update", UI_COLOR_VALUE);
y += LINE_H + 4;
draw_centered_color(y,
state.dfu_confirm_time != 0 ?
"Press to confirm" : "Press to run",
state.dfu_confirm_time != 0 ? UI_COLOR_WARN
: UI_COLOR_VALUE);
return;
}
draw_centered(centered_row(0, 2), "BLE DFU Update");
if (state.dfu_confirm_time != 0) {
draw_centered(centered_row(1, 2), "Press to confirm");
@@ -648,6 +999,21 @@ static void render_shutdown(void)
state.shutdown_confirm_time = 0;
}
if (mc_display_has_color()) {
int y = CONTENT_Y;
draw_badge(0, y, "PWR", state.shutdown_confirm_time != 0
? UI_COLOR_WARN : UI_COLOR_ERROR);
mc_display_color_text(32, y, "power off", UI_COLOR_VALUE);
y += LINE_H + 4;
draw_centered_color(y,
state.shutdown_confirm_time != 0 ?
"Press to confirm" : "Press to run",
state.shutdown_confirm_time != 0 ? UI_COLOR_WARN
: UI_COLOR_VALUE);
return;
}
draw_centered(centered_row(0, 2), "Power Off");
if (state.shutdown_confirm_time != 0) {
draw_centered(centered_row(1, 2), "Press to confirm");
@@ -660,9 +1026,15 @@ static void render_status(void)
{
char buf[28];
int y = CONTENT_Y;
bool color = mc_display_has_color();
/* Role label */
draw_centered(y, "REPEATER");
if (color) {
draw_badge(0, y, "MODE", UI_COLOR_ACTIVE);
mc_display_color_text(38, y, "repeater", UI_COLOR_VALUE);
} else {
draw_centered(y, "REPEATER");
}
y += LINE_H;
/* Uptime */
@@ -672,7 +1044,11 @@ static void render_status(void)
uint32_t mins = (up_s % 3600) / 60;
snprintf(buf, sizeof(buf), "Up: %ud %uh %um", days, hours, mins);
mc_display_text(0, y, buf, false);
if (color) {
draw_color_segments(y, "UP ", buf, UI_COLOR_VALUE);
} else {
mc_display_text(0, y, buf, false);
}
y += LINE_H;
/* Clock — only if RTC has been synced (after Jan 1 2025) */
@@ -683,9 +1059,17 @@ static void render_status(void)
uint8_t ss = day_sec % 60;
snprintf(buf, sizeof(buf), "Time: %02u:%02u:%02u UTC", hh, mm, ss);
mc_display_text(0, y, buf, false);
if (color) {
draw_color_segments(y, "CLK ", buf, UI_COLOR_OK);
} else {
mc_display_text(0, y, buf, false);
}
} else {
mc_display_text(0, y, "Time: not synced", false);
if (color) {
draw_color_segments(y, "CLK ", "not synced", UI_COLOR_WARN);
} else {
mc_display_text(0, y, "Time: not synced", false);
}
}
y += LINE_H;
@@ -697,7 +1081,17 @@ static void render_status(void)
pct = calc_battery_pct(state.battery_mv);
}
snprintf(buf, sizeof(buf), "Batt: %u%% (%umV)", pct, state.battery_mv);
mc_display_text(0, y, buf, false);
if (color) {
uint16_t batt_color = (pct <= 15) ? UI_COLOR_ERROR :
(pct <= 30) ? UI_COLOR_WARN : UI_COLOR_OK;
snprintf(buf, sizeof(buf), "%u%% %umV", pct, state.battery_mv);
draw_color_segments(y, "BAT ", buf, batt_color);
draw_metric_bar(DISP_W - 44, y + 2, 42, 5, pct, 100,
batt_color);
} else {
mc_display_text(0, y, buf, false);
}
}
}