mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-10-23 00:41:37 +00:00
Merge branch 'master' into dev
This commit is contained in:
commit
6ef03fb10e
29 files changed
+1886
-65
No files matched your search
@@ -497,12 +497,26 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else {
|
||||
strcpy(reply, "> strict");
|
||||
}
|
||||
} else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) {
|
||||
} else if (strcmp(config, "tx apc") == 0) {
|
||||
if (_callbacks->isAPCEnabled()) {
|
||||
int8_t apc = _callbacks->getAPCReduction();
|
||||
float margin = _callbacks->getAPCMargin();
|
||||
int effective = (int)_prefs->tx_power_dbm - (int)apc;
|
||||
snprintf(reply, CLI_REPLY_SIZE, "> %ddBm (max=%d apc=-%d margin=%.1f target=%d)",
|
||||
snprintf(reply, CLI_REPLY_SIZE,
|
||||
"> apc=on effective=%ddBm max=%d reduction=%d margin=%.1f target=%d",
|
||||
effective, (int)_prefs->tx_power_dbm, (int)apc, (double)margin,
|
||||
(int)_callbacks->getAPCTargetMargin());
|
||||
} else {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "> apc=off max=%ddBm target=%d",
|
||||
(int)_prefs->tx_power_dbm, (int)_callbacks->getAPCTargetMargin());
|
||||
}
|
||||
} else if (strcmp(config, "tx") == 0) {
|
||||
if (_callbacks->isAPCEnabled()) {
|
||||
int8_t apc = _callbacks->getAPCReduction();
|
||||
float margin = _callbacks->getAPCMargin();
|
||||
int effective = (int)_prefs->tx_power_dbm - (int)apc;
|
||||
snprintf(reply, CLI_REPLY_SIZE,
|
||||
"> %ddBm (apc=on max=%d reduction=%d margin=%.1f target=%d)",
|
||||
effective, (int)_prefs->tx_power_dbm, (int)apc, (double)margin,
|
||||
(int)_callbacks->getAPCTargetMargin());
|
||||
} else {
|
||||
@@ -806,21 +820,23 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
strcpy(reply, "Error: range 6-30 dB");
|
||||
}
|
||||
} else if (memcmp(config, "tx ", 3) == 0) {
|
||||
if (memcmp(&config[3], "apc", 3) == 0) {
|
||||
if (strcmp(&config[3], "apc") == 0) {
|
||||
_prefs->apc_enabled = 1;
|
||||
_callbacks->setAPCEnabled(true);
|
||||
savePrefs();
|
||||
snprintf(reply, CLI_REPLY_SIZE, "OK - tx power=%d dBm (apc=on)",
|
||||
(int)_prefs->tx_power_dbm);
|
||||
} else {
|
||||
int val = atoi(&config[3]);
|
||||
char *end = nullptr;
|
||||
long parsed = strtol(&config[3], &end, 10);
|
||||
int max_tx = 30;
|
||||
#ifdef CONFIG_ZEPHCORE_MAX_TX_POWER_DBM
|
||||
max_tx = CONFIG_ZEPHCORE_MAX_TX_POWER_DBM;
|
||||
#endif
|
||||
if (val < -9 || val > max_tx) {
|
||||
if (end == &config[3] || *end != '\0' || parsed < -9 || parsed > max_tx) {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "Error: range -9 to %d dBm, or 'apc'", max_tx);
|
||||
} else {
|
||||
int val = (int)parsed;
|
||||
_prefs->apc_enabled = 0;
|
||||
_prefs->tx_power_dbm = (int8_t)val;
|
||||
savePrefs();
|
||||
|
||||
@@ -56,6 +56,25 @@ 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 UI_COLOR_TX MC_COLOR_ORANGE
|
||||
#define UI_COLOR_RX UI_COLOR_ACTIVE
|
||||
|
||||
#define COLOR_FONT_W 6
|
||||
#define COLOR_FONT_H 8
|
||||
#define ACTIVITY_GRAPH_SAMPLES 16
|
||||
|
||||
/* 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
|
||||
@@ -76,6 +95,14 @@ static inline int centered_row(int idx, int total)
|
||||
/* ========== Global State ========== */
|
||||
static struct ui_state state;
|
||||
|
||||
static uint8_t activity_rx[ACTIVITY_GRAPH_SAMPLES];
|
||||
static uint8_t activity_tx[ACTIVITY_GRAPH_SAMPLES];
|
||||
static uint8_t activity_head;
|
||||
static bool activity_initialized;
|
||||
static uint32_t activity_last_rx;
|
||||
static uint32_t activity_last_tx;
|
||||
static uint32_t activity_last_sample_ms;
|
||||
|
||||
/* ========== Active Pages (role-dependent) ========== */
|
||||
/* Repeater: minimal pages — status, radio, shutdown.
|
||||
* Companion: full page set matching Arduino UI.
|
||||
@@ -95,7 +122,9 @@ static const enum ui_page active_pages[] = {
|
||||
UI_PAGE_BLUETOOTH,
|
||||
UI_PAGE_ADVERT,
|
||||
UI_PAGE_GPS,
|
||||
#ifdef CONFIG_ZEPHCORE_UI_BUZZER
|
||||
UI_PAGE_BUZZER,
|
||||
#endif
|
||||
UI_PAGE_LEDS,
|
||||
UI_PAGE_SENSORS,
|
||||
UI_PAGE_OFFGRID,
|
||||
@@ -140,6 +169,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. */
|
||||
@@ -172,15 +202,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;
|
||||
@@ -190,7 +238,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,6 +253,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;
|
||||
@@ -211,15 +264,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 ========== */
|
||||
@@ -235,6 +302,181 @@ 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)
|
||||
{
|
||||
draw_color_segments_at(0, y, label, 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);
|
||||
}
|
||||
}
|
||||
|
||||
static bool use_compact_color_home(void)
|
||||
{
|
||||
return mc_display_has_color() && DISP_W <= 180 && DISP_H <= 100;
|
||||
}
|
||||
|
||||
static uint8_t clamp_activity_delta(uint32_t delta)
|
||||
{
|
||||
return (delta > 15U) ? 15U : (uint8_t)delta;
|
||||
}
|
||||
|
||||
static void sample_activity_graph(void)
|
||||
{
|
||||
uint32_t now = k_uptime_get_32();
|
||||
uint32_t rx = state.lora_packets_rx;
|
||||
uint32_t tx = state.lora_packets_tx;
|
||||
uint32_t rx_delta = 0;
|
||||
uint32_t tx_delta = 0;
|
||||
|
||||
if (!activity_initialized) {
|
||||
activity_last_rx = rx;
|
||||
activity_last_tx = tx;
|
||||
activity_last_sample_ms = now;
|
||||
activity_initialized = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (rx >= activity_last_rx) {
|
||||
rx_delta = rx - activity_last_rx;
|
||||
}
|
||||
if (tx >= activity_last_tx) {
|
||||
tx_delta = tx - activity_last_tx;
|
||||
}
|
||||
|
||||
/* Age the graph slowly during quiet periods, but do not shift it on
|
||||
* every incidental repaint. Counter wrap/reset simply records zero. */
|
||||
if (rx_delta == 0 && tx_delta == 0 &&
|
||||
(now - activity_last_sample_ms) < 5000U) {
|
||||
return;
|
||||
}
|
||||
|
||||
activity_last_rx = rx;
|
||||
activity_last_tx = tx;
|
||||
activity_last_sample_ms = now;
|
||||
activity_head = (activity_head + 1U) % ACTIVITY_GRAPH_SAMPLES;
|
||||
activity_rx[activity_head] = clamp_activity_delta(rx_delta);
|
||||
activity_tx[activity_head] = clamp_activity_delta(tx_delta);
|
||||
}
|
||||
|
||||
static void draw_activity_graph(int x, int y, int w, int h)
|
||||
{
|
||||
uint8_t max_v = 0;
|
||||
int mid = y + h / 2;
|
||||
int col_w = w / ACTIVITY_GRAPH_SAMPLES;
|
||||
|
||||
if (col_w < 1) {
|
||||
col_w = 1;
|
||||
}
|
||||
|
||||
sample_activity_graph();
|
||||
|
||||
mc_display_color_fill_rect(x, y, w, h, MC_COLOR_BLACK);
|
||||
mc_display_color_fill_rect(x, mid, w, 1, UI_COLOR_DIM);
|
||||
|
||||
for (int i = 0; i < ACTIVITY_GRAPH_SAMPLES; i++) {
|
||||
uint8_t idx = (uint8_t)((activity_head + 1U + i) %
|
||||
ACTIVITY_GRAPH_SAMPLES);
|
||||
|
||||
if (activity_rx[idx] > max_v) {
|
||||
max_v = activity_rx[idx];
|
||||
}
|
||||
if (activity_tx[idx] > max_v) {
|
||||
max_v = activity_tx[idx];
|
||||
}
|
||||
}
|
||||
if (max_v == 0) {
|
||||
mc_display_color_text(x + w - 30, y + 1, "RX", UI_COLOR_RX);
|
||||
mc_display_color_text(x + w - 14, y + 1, "TX", UI_COLOR_TX);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ACTIVITY_GRAPH_SAMPLES; i++) {
|
||||
uint8_t idx = (uint8_t)((activity_head + 1U + i) %
|
||||
ACTIVITY_GRAPH_SAMPLES);
|
||||
int bx = x + i * col_w;
|
||||
int draw_w = (col_w > 1) ? col_w - 1 : 1;
|
||||
int tx_h = ((h / 2 - 1) * activity_tx[idx]) / max_v;
|
||||
int rx_h = ((h / 2 - 1) * activity_rx[idx]) / max_v;
|
||||
|
||||
if (tx_h > 0) {
|
||||
mc_display_color_fill_rect(bx, mid - tx_h, draw_w, tx_h,
|
||||
UI_COLOR_TX);
|
||||
}
|
||||
if (rx_h > 0) {
|
||||
mc_display_color_fill_rect(bx, mid + 1, draw_w, rx_h,
|
||||
UI_COLOR_RX);
|
||||
}
|
||||
}
|
||||
|
||||
mc_display_color_text(x + w - 30, y + 1, "RX", UI_COLOR_RX);
|
||||
mc_display_color_text(x + w - 14, y + 1, "TX", UI_COLOR_TX);
|
||||
}
|
||||
|
||||
/* ========== Page Renderers ========== */
|
||||
|
||||
static void render_messages(void)
|
||||
@@ -242,6 +484,73 @@ static void render_messages(void)
|
||||
/* 3 centered rows: msg count, BLE status, offgrid status */
|
||||
char buf[24];
|
||||
|
||||
if (use_compact_color_home()) {
|
||||
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";
|
||||
int graph_h = 16;
|
||||
int graph_y = DISP_H - graph_h - 2;
|
||||
|
||||
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 {
|
||||
mc_display_color_text(34, 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);
|
||||
draw_activity_graph(2, graph_y, DISP_W - 4, graph_h);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -259,7 +568,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;
|
||||
}
|
||||
|
||||
@@ -288,54 +602,173 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
static void render_radio(void)
|
||||
{
|
||||
char buf[28];
|
||||
char buf[32];
|
||||
int y = CONTENT_Y;
|
||||
|
||||
/* Line 1: FQ, SF, BW */
|
||||
uint32_t freq_mhz = state.lora_freq_hz / 1000000;
|
||||
uint32_t freq_frac = (state.lora_freq_hz % 1000000 + 500) / 1000;
|
||||
uint16_t bw_int = state.lora_bw_khz_x10 / 10;
|
||||
uint16_t bw_frac = state.lora_bw_khz_x10 % 10;
|
||||
const char *packet_state = state.lora_tx_active ? "TX" :
|
||||
(state.lora_in_rx ? "RX" :
|
||||
(state.lora_radio_ready ? "RDY" : "WAIT"));
|
||||
const char *rx_mode = state.lora_rx_duty_cycle ? "DC" : "CONT";
|
||||
uint16_t warn_color = (state.lora_apc_enabled && state.lora_apc_reduction > 0)
|
||||
? UI_COLOR_WARN : UI_COLOR_OK;
|
||||
|
||||
if (mc_display_has_color()) {
|
||||
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) {
|
||||
snprintf(buf, sizeof(buf), "%u.%03u BW%u.%u",
|
||||
freq_mhz, freq_frac, bw_int, bw_frac);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "%u.%03u BW%u",
|
||||
freq_mhz, freq_frac, bw_int);
|
||||
}
|
||||
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, UI_COLOR_VALUE);
|
||||
y += LINE_H;
|
||||
|
||||
if (state.lora_apc_enabled) {
|
||||
snprintf(buf, sizeof(buf), "%d/%ddBm",
|
||||
state.lora_effective_tx_power, state.lora_tx_power);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "%ddBm", state.lora_tx_power);
|
||||
}
|
||||
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",
|
||||
state.lora_apc_reduction,
|
||||
state.lora_apc_margin_x10 / 10,
|
||||
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;
|
||||
|
||||
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 ? UI_COLOR_ERROR
|
||||
: UI_COLOR_VALUE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bw_frac) {
|
||||
snprintf(buf, sizeof(buf), "%u.%03u SF%u BW%u.%u",
|
||||
freq_mhz, freq_frac, state.lora_sf, bw_int, bw_frac);
|
||||
snprintf(buf, sizeof(buf), "%u.%03u BW%u.%u",
|
||||
freq_mhz, freq_frac, bw_int, bw_frac);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "%u.%03u SF%u BW%u",
|
||||
freq_mhz, freq_frac, state.lora_sf, bw_int);
|
||||
snprintf(buf, sizeof(buf), "%u.%03u BW%u",
|
||||
freq_mhz, freq_frac, bw_int);
|
||||
}
|
||||
mc_display_text(0, y, buf, false);
|
||||
y += LINE_H;
|
||||
|
||||
/* Line 2: CR, TX Power */
|
||||
snprintf(buf, sizeof(buf), "CR:%u TX:%ddBm", state.lora_cr, state.lora_tx_power);
|
||||
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);
|
||||
mc_display_text(0, y, buf, false);
|
||||
y += LINE_H;
|
||||
|
||||
/* Line 3: Noise floor */
|
||||
snprintf(buf, sizeof(buf), "Noise: %ddBm", state.lora_noise_floor);
|
||||
if (state.lora_apc_enabled) {
|
||||
snprintf(buf, sizeof(buf), "TX:%d/%ddBm APC:on",
|
||||
state.lora_effective_tx_power, state.lora_tx_power);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "TX:%ddBm APC:off", state.lora_tx_power);
|
||||
}
|
||||
mc_display_text(0, y, buf, false);
|
||||
y += LINE_H;
|
||||
|
||||
/* Uptime */
|
||||
uint32_t up_s = (uint32_t)(k_uptime_get() / 1000);
|
||||
uint32_t days = up_s / 86400;
|
||||
uint32_t hours = (up_s % 86400) / 3600;
|
||||
uint32_t mins = (up_s % 3600) / 60;
|
||||
snprintf(buf, sizeof(buf), "R%d M%d.%d T%u %s/%s",
|
||||
state.lora_apc_reduction,
|
||||
state.lora_apc_margin_x10 / 10,
|
||||
abs(state.lora_apc_margin_x10 % 10),
|
||||
state.lora_apc_target_margin, packet_state, rx_mode);
|
||||
mc_display_text(0, y, buf, false);
|
||||
y += LINE_H;
|
||||
|
||||
snprintf(buf, sizeof(buf), "Up: %ud %uh %um", days, hours, mins);
|
||||
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);
|
||||
mc_display_text(0, y, buf, false);
|
||||
}
|
||||
|
||||
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");
|
||||
@@ -358,6 +791,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) {
|
||||
@@ -389,21 +841,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;
|
||||
}
|
||||
|
||||
@@ -412,7 +882,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 */
|
||||
@@ -421,9 +895,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 */
|
||||
@@ -432,10 +914,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;
|
||||
}
|
||||
|
||||
@@ -444,11 +934,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,6 +969,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);
|
||||
@@ -484,6 +997,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
|
||||
@@ -495,14 +1009,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;
|
||||
}
|
||||
|
||||
@@ -510,7 +1032,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;
|
||||
}
|
||||
|
||||
@@ -519,7 +1045,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)
|
||||
@@ -530,10 +1067,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);
|
||||
@@ -555,6 +1117,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");
|
||||
@@ -571,6 +1148,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");
|
||||
@@ -583,9 +1175,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 */
|
||||
@@ -595,7 +1193,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) */
|
||||
@@ -606,9 +1208,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;
|
||||
|
||||
@@ -620,7 +1230,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,20 @@ struct ui_state {
|
||||
uint8_t lora_cr;
|
||||
int8_t lora_tx_power;
|
||||
int16_t lora_noise_floor;
|
||||
int8_t lora_effective_tx_power;
|
||||
bool lora_apc_enabled;
|
||||
int8_t lora_apc_reduction;
|
||||
int16_t lora_apc_margin_x10;
|
||||
uint8_t lora_apc_target_margin;
|
||||
uint8_t lora_sync_word;
|
||||
uint16_t lora_preamble_len;
|
||||
bool lora_rx_duty_cycle;
|
||||
bool lora_radio_ready;
|
||||
bool lora_in_rx;
|
||||
bool lora_tx_active;
|
||||
uint32_t lora_packets_rx;
|
||||
uint32_t lora_packets_tx;
|
||||
uint32_t lora_packets_err;
|
||||
|
||||
/* Bluetooth page */
|
||||
bool ble_enabled; /* true = BLE active, false = serial mode */
|
||||
|
||||
@@ -891,6 +891,46 @@ void ui_set_radio_params(uint32_t freq_hz, uint8_t sf, uint16_t bw_khz_x10,
|
||||
s->lora_noise_floor = noise_floor;
|
||||
}
|
||||
|
||||
void ui_set_radio_runtime(int8_t effective_tx_power, bool apc_enabled,
|
||||
int8_t apc_reduction, int16_t apc_margin_x10,
|
||||
uint8_t apc_target_margin, uint8_t sync_word,
|
||||
uint16_t preamble_len, bool rx_duty_cycle,
|
||||
bool radio_ready, bool in_rx, bool tx_active)
|
||||
{
|
||||
struct ui_state *s = get_state();
|
||||
|
||||
s->lora_effective_tx_power = effective_tx_power;
|
||||
s->lora_apc_enabled = apc_enabled;
|
||||
s->lora_apc_reduction = apc_reduction;
|
||||
s->lora_apc_margin_x10 = apc_margin_x10;
|
||||
s->lora_apc_target_margin = apc_target_margin;
|
||||
s->lora_sync_word = sync_word;
|
||||
s->lora_preamble_len = preamble_len;
|
||||
s->lora_rx_duty_cycle = rx_duty_cycle;
|
||||
s->lora_radio_ready = radio_ready;
|
||||
s->lora_in_rx = in_rx;
|
||||
s->lora_tx_active = tx_active;
|
||||
}
|
||||
|
||||
void ui_set_radio_stats(uint32_t packets_rx, uint32_t packets_tx,
|
||||
uint32_t packets_err)
|
||||
{
|
||||
struct ui_state *s = get_state();
|
||||
bool activity_changed = packets_rx != s->lora_packets_rx ||
|
||||
packets_tx != s->lora_packets_tx;
|
||||
|
||||
s->lora_packets_rx = packets_rx;
|
||||
s->lora_packets_tx = packets_tx;
|
||||
s->lora_packets_err = packets_err;
|
||||
|
||||
#ifdef CONFIG_ZEPHCORE_UI_DISPLAY
|
||||
if (ui_initialized && activity_changed && mc_display_has_color() &&
|
||||
ui_pages_current() == UI_PAGE_MESSAGES) {
|
||||
schedule_render();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ui_set_gps_data(bool has_fix, uint8_t sats,
|
||||
int32_t lat_mdeg, int32_t lon_mdeg, int32_t alt_mm)
|
||||
{
|
||||
|
||||
@@ -218,6 +218,24 @@ extern "C" void ui_set_radio_params(
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void ui_set_radio_runtime(int8_t effective_tx_power, bool apc_enabled,
|
||||
int8_t apc_reduction, int16_t apc_margin_x10,
|
||||
uint8_t apc_target_margin, uint8_t sync_word,
|
||||
uint16_t preamble_len, bool rx_duty_cycle,
|
||||
bool radio_ready, bool in_rx, bool tx_active)
|
||||
{
|
||||
(void)effective_tx_power; (void)apc_enabled; (void)apc_reduction;
|
||||
(void)apc_margin_x10; (void)apc_target_margin; (void)sync_word;
|
||||
(void)preamble_len; (void)rx_duty_cycle; (void)radio_ready;
|
||||
(void)in_rx; (void)tx_active;
|
||||
}
|
||||
|
||||
extern "C" void ui_set_radio_stats(uint32_t pkt_recv, uint32_t pkt_sent,
|
||||
uint32_t pkt_errors)
|
||||
{
|
||||
ui_notify_radio_stats(pkt_recv, pkt_sent, pkt_errors);
|
||||
}
|
||||
|
||||
extern "C" void ui_notify_radio_stats(uint32_t pkt_recv, uint32_t pkt_sent, uint32_t pkt_errors)
|
||||
{
|
||||
if (s_task) {
|
||||
|
||||
@@ -970,6 +970,14 @@ static const uint8_t cfb_font_0608[224][6] = {
|
||||
{ 0x0C, 0x51, 0x50, 0x51, 0x3C, 0x00 },
|
||||
};
|
||||
|
||||
const uint8_t *zephcore_font_6x8_glyph(uint8_t c)
|
||||
{
|
||||
if (c < 32) {
|
||||
c = '?';
|
||||
}
|
||||
return cfb_font_0608[c - 32];
|
||||
}
|
||||
|
||||
FONT_ENTRY_DEFINE(cfb_font_6x8,
|
||||
6, 8,
|
||||
CFB_FONT_MONO_VPACKED,
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <zephyr/drivers/display.h>
|
||||
#include <zephyr/drivers/regulator.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
@@ -54,6 +55,39 @@ static uint16_t disp_height;
|
||||
static uint8_t font_w;
|
||||
static uint8_t font_h;
|
||||
static bool is_epd; /* true for e-paper displays */
|
||||
static bool has_color; /* true when a raw RGB565 TFT is available */
|
||||
|
||||
const uint8_t *zephcore_font_6x8_glyph(uint8_t c);
|
||||
|
||||
#define COLOR_TEXT_MAX_CHARS 32
|
||||
#define COLOR_MAX_OPS 72
|
||||
#define COLOR_MAX_WIDTH 320
|
||||
|
||||
enum color_op_type {
|
||||
COLOR_OP_TEXT,
|
||||
COLOR_OP_RECT,
|
||||
};
|
||||
|
||||
struct color_op {
|
||||
enum color_op_type type;
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
int16_t w;
|
||||
int16_t h;
|
||||
uint16_t color;
|
||||
char text[COLOR_TEXT_MAX_CHARS];
|
||||
};
|
||||
|
||||
static struct color_op color_ops[COLOR_MAX_OPS];
|
||||
static uint8_t color_op_count;
|
||||
static uint16_t color_line[COLOR_MAX_WIDTH];
|
||||
|
||||
#if DT_NODE_EXISTS(DT_NODELABEL(tft))
|
||||
static const struct device *color_dev =
|
||||
DEVICE_DT_GET_OR_NULL(DT_NODELABEL(tft));
|
||||
#else
|
||||
static const struct device *color_dev;
|
||||
#endif
|
||||
|
||||
/* Optional symmetric inset (pixels). Shrinks reported width/height and
|
||||
* offsets all draw primitives so panels with edge artefacts can hide them
|
||||
@@ -131,6 +165,159 @@ static inline void panel_vdd_enable(void)
|
||||
}
|
||||
}
|
||||
|
||||
static void color_overlay_probe(void)
|
||||
{
|
||||
has_color = false;
|
||||
|
||||
if (!color_dev || color_dev == disp_dev || !device_is_ready(color_dev)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct display_capabilities caps;
|
||||
|
||||
display_get_capabilities(color_dev, &caps);
|
||||
if ((caps.current_pixel_format == PIXEL_FORMAT_RGB_565 ||
|
||||
caps.current_pixel_format == PIXEL_FORMAT_RGB_565X ||
|
||||
(caps.supported_pixel_formats & (PIXEL_FORMAT_RGB_565 | PIXEL_FORMAT_RGB_565X))) &&
|
||||
!(caps.screen_info & SCREEN_INFO_EPD)) {
|
||||
has_color = true;
|
||||
LOG_INF("display: color overlay enabled");
|
||||
}
|
||||
}
|
||||
|
||||
static bool color_queue(enum color_op_type type, int x, int y, int w, int h,
|
||||
const char *text, uint16_t color)
|
||||
{
|
||||
if (!has_color || color_op_count >= COLOR_MAX_OPS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
struct color_op *op = &color_ops[color_op_count++];
|
||||
|
||||
op->type = type;
|
||||
op->x = (int16_t)x;
|
||||
op->y = (int16_t)y;
|
||||
op->w = (int16_t)w;
|
||||
op->h = (int16_t)h;
|
||||
op->color = color;
|
||||
op->text[0] = '\0';
|
||||
if (text) {
|
||||
strncpy(op->text, text, sizeof(op->text) - 1);
|
||||
op->text[sizeof(op->text) - 1] = '\0';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void color_write_rect_now(int x, int y, int w, int h, uint16_t color)
|
||||
{
|
||||
if (!has_color || w <= 0 || h <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
x += DISP_INSET;
|
||||
y += DISP_INSET;
|
||||
if (x < 0) {
|
||||
w += x;
|
||||
x = 0;
|
||||
}
|
||||
if (y < 0) {
|
||||
h += y;
|
||||
y = 0;
|
||||
}
|
||||
if (x + w > (int)disp_width) {
|
||||
w = (int)disp_width - x;
|
||||
}
|
||||
if (y + h > (int)disp_height) {
|
||||
h = (int)disp_height - y;
|
||||
}
|
||||
if (w <= 0 || h <= 0) {
|
||||
return;
|
||||
}
|
||||
if (w > COLOR_MAX_WIDTH) {
|
||||
w = COLOR_MAX_WIDTH;
|
||||
}
|
||||
|
||||
uint16_t be = sys_cpu_to_be16(color);
|
||||
for (int i = 0; i < w; i++) {
|
||||
color_line[i] = be;
|
||||
}
|
||||
|
||||
const struct display_buffer_descriptor desc = {
|
||||
.buf_size = (uint32_t)w * 2U,
|
||||
.width = (uint16_t)w,
|
||||
.height = 1U,
|
||||
.pitch = (uint16_t)w,
|
||||
};
|
||||
|
||||
for (int row = 0; row < h; row++) {
|
||||
display_write(color_dev, (uint16_t)x, (uint16_t)(y + row),
|
||||
&desc, color_line);
|
||||
}
|
||||
}
|
||||
|
||||
static void color_write_char_now(int x, int y, uint8_t c, uint16_t color)
|
||||
{
|
||||
uint16_t glyph_buf[6 * 8];
|
||||
const uint8_t *glyph = zephcore_font_6x8_glyph(c);
|
||||
uint16_t fg = sys_cpu_to_be16(color);
|
||||
uint16_t bg = sys_cpu_to_be16(MC_COLOR_BLACK);
|
||||
|
||||
for (int row = 0; row < 8; row++) {
|
||||
for (int col = 0; col < 6; col++) {
|
||||
bool on = (glyph[col] >> row) & 0x01;
|
||||
glyph_buf[row * 6 + col] = on ? fg : bg;
|
||||
}
|
||||
}
|
||||
|
||||
const struct display_buffer_descriptor desc = {
|
||||
.buf_size = sizeof(glyph_buf),
|
||||
.width = 6,
|
||||
.height = 8,
|
||||
.pitch = 6,
|
||||
};
|
||||
|
||||
display_write(color_dev, (uint16_t)(x + DISP_INSET),
|
||||
(uint16_t)(y + DISP_INSET), &desc, glyph_buf);
|
||||
}
|
||||
|
||||
static void color_write_text_now(int x, int y, const char *text, uint16_t color)
|
||||
{
|
||||
if (!has_color || !text) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const char *p = text; *p; p++, x += 6) {
|
||||
uint8_t c = (uint8_t)*p;
|
||||
|
||||
if (c < 32) {
|
||||
c = '?';
|
||||
}
|
||||
if (x + 6 > (int)mc_display_width() || y + 8 > (int)mc_display_height()) {
|
||||
break;
|
||||
}
|
||||
color_write_char_now(x, y, c, color);
|
||||
}
|
||||
}
|
||||
|
||||
static void color_flush_ops(void)
|
||||
{
|
||||
if (!has_color) {
|
||||
color_op_count = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < color_op_count; i++) {
|
||||
struct color_op *op = &color_ops[i];
|
||||
|
||||
if (op->type == COLOR_OP_RECT) {
|
||||
color_write_rect_now(op->x, op->y, op->w, op->h, op->color);
|
||||
} else {
|
||||
color_write_text_now(op->x, op->y, op->text, op->color);
|
||||
}
|
||||
}
|
||||
color_op_count = 0;
|
||||
}
|
||||
|
||||
/* Auto-off work */
|
||||
static struct k_work_delayable auto_off_work;
|
||||
|
||||
@@ -228,6 +415,7 @@ int mc_display_init(void)
|
||||
|
||||
LOG_INF("display: %ux%u%s", disp_width, disp_height,
|
||||
is_epd ? " (e-paper)" : "");
|
||||
color_overlay_probe();
|
||||
|
||||
/* OLED: blank before CFB init so stale VRAM isn't visible while we
|
||||
* build the first frame. EPD: driver init already performed a clean
|
||||
@@ -395,12 +583,18 @@ bool mc_display_is_epd(void)
|
||||
return is_epd;
|
||||
}
|
||||
|
||||
bool mc_display_has_color(void)
|
||||
{
|
||||
return has_color;
|
||||
}
|
||||
|
||||
void mc_display_clear(void)
|
||||
{
|
||||
if (!disp_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
color_op_count = 0;
|
||||
if (is_epd) {
|
||||
epd_frame_hash = 2166136261u;
|
||||
}
|
||||
@@ -430,6 +624,17 @@ void mc_display_text(int x, int y, const char *text, bool invert)
|
||||
}
|
||||
}
|
||||
|
||||
void mc_display_color_text(int x, int y, const char *text, uint16_t color)
|
||||
{
|
||||
if (!disp_initialized || !text) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!color_queue(COLOR_OP_TEXT, x, y, 0, 0, text, color)) {
|
||||
mc_display_text(x, y, text, false);
|
||||
}
|
||||
}
|
||||
|
||||
void mc_display_fill_rect(int x, int y, int w, int h)
|
||||
{
|
||||
if (!disp_initialized) {
|
||||
@@ -452,6 +657,17 @@ void mc_display_fill_rect(int x, int y, int w, int h)
|
||||
}
|
||||
}
|
||||
|
||||
void mc_display_color_fill_rect(int x, int y, int w, int h, uint16_t color)
|
||||
{
|
||||
if (!disp_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!color_queue(COLOR_OP_RECT, x, y, w, h, NULL, color)) {
|
||||
mc_display_fill_rect(x, y, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
void mc_display_hline(int x, int y, int w)
|
||||
{
|
||||
if (!disp_initialized) {
|
||||
@@ -564,6 +780,7 @@ void mc_display_finalize(void)
|
||||
}
|
||||
|
||||
cfb_framebuffer_finalize(disp_dev);
|
||||
color_flush_ops();
|
||||
|
||||
if (is_epd) {
|
||||
epd_last_frame_hash = epd_frame_hash;
|
||||
|
||||
@@ -83,6 +83,12 @@ bool mc_display_is_on(void);
|
||||
*/
|
||||
bool mc_display_is_epd(void);
|
||||
|
||||
/**
|
||||
* @return true when a raw RGB565-capable color panel is available for
|
||||
* optional color overlays. Monochrome displays return false.
|
||||
*/
|
||||
bool mc_display_has_color(void);
|
||||
|
||||
/**
|
||||
* Clear the framebuffer (fill with black).
|
||||
* Call before rendering a new frame.
|
||||
@@ -99,6 +105,25 @@ void mc_display_clear(void);
|
||||
*/
|
||||
void mc_display_text(int x, int y, const char *text, bool invert);
|
||||
|
||||
/* Common RGB565 colors for optional color-capable pages. */
|
||||
#define MC_COLOR_BLACK 0x0000
|
||||
#define MC_COLOR_WHITE 0xffff
|
||||
#define MC_COLOR_GREEN 0x07e0
|
||||
#define MC_COLOR_CYAN 0x07ff
|
||||
#define MC_COLOR_YELLOW 0xffe0
|
||||
#define MC_COLOR_ORANGE 0xfd20
|
||||
#define MC_COLOR_RED 0xf800
|
||||
#define MC_COLOR_BLUE 0x001f
|
||||
#define MC_COLOR_GRAY 0x8410
|
||||
|
||||
/**
|
||||
* Draw text using RGB565 color when supported. On non-color displays this
|
||||
* falls back to mc_display_text(..., invert=false).
|
||||
*
|
||||
* Color overlays are flushed after the normal CFB frame in mc_display_finalize().
|
||||
*/
|
||||
void mc_display_color_text(int x, int y, const char *text, uint16_t color);
|
||||
|
||||
/**
|
||||
* Draw a filled rectangle.
|
||||
*
|
||||
@@ -109,6 +134,12 @@ void mc_display_text(int x, int y, const char *text, bool invert);
|
||||
*/
|
||||
void mc_display_fill_rect(int x, int y, int w, int h);
|
||||
|
||||
/**
|
||||
* Draw a filled rectangle using RGB565 color when supported. On non-color
|
||||
* displays this falls back to mc_display_fill_rect().
|
||||
*/
|
||||
void mc_display_color_fill_rect(int x, int y, int w, int h, uint16_t color);
|
||||
|
||||
/**
|
||||
* Draw a horizontal line.
|
||||
*/
|
||||
|
||||
@@ -49,6 +49,25 @@ WEAK void ui_set_radio_params(uint32_t freq_hz, uint8_t sf,
|
||||
ARG_UNUSED(cr); ARG_UNUSED(tx_power); ARG_UNUSED(noise_floor);
|
||||
}
|
||||
|
||||
WEAK void ui_set_radio_runtime(int8_t effective_tx_power, bool apc_enabled,
|
||||
int8_t apc_reduction, int16_t apc_margin_x10,
|
||||
uint8_t apc_target_margin, uint8_t sync_word,
|
||||
uint16_t preamble_len, bool rx_duty_cycle,
|
||||
bool radio_ready, bool in_rx, bool tx_active)
|
||||
{
|
||||
ARG_UNUSED(effective_tx_power); ARG_UNUSED(apc_enabled);
|
||||
ARG_UNUSED(apc_reduction); ARG_UNUSED(apc_margin_x10);
|
||||
ARG_UNUSED(apc_target_margin); ARG_UNUSED(sync_word);
|
||||
ARG_UNUSED(preamble_len); ARG_UNUSED(rx_duty_cycle);
|
||||
ARG_UNUSED(radio_ready); ARG_UNUSED(in_rx); ARG_UNUSED(tx_active);
|
||||
}
|
||||
|
||||
WEAK void ui_set_radio_stats(uint32_t packets_rx, uint32_t packets_tx,
|
||||
uint32_t packets_err)
|
||||
{
|
||||
ARG_UNUSED(packets_rx); ARG_UNUSED(packets_tx); ARG_UNUSED(packets_err);
|
||||
}
|
||||
|
||||
WEAK void ui_set_gps_data(bool has_fix, uint8_t sats,
|
||||
int32_t lat_mdeg, int32_t lon_mdeg, int32_t alt_mm)
|
||||
{
|
||||
|
||||
@@ -334,13 +334,38 @@ extern "C" void mesh_housekeeping_ui_refresh(void)
|
||||
ui_set_clock(s_rtc_clock->getCurrentTime());
|
||||
|
||||
ui_set_radio_params(
|
||||
(uint32_t)(s_mesh->prefs.freq * 1000000.0f + 0.5f),
|
||||
s_mesh->prefs.sf,
|
||||
(uint16_t)(s_mesh->prefs.bw * 10.0f + 0.5f),
|
||||
s_mesh->prefs.cr,
|
||||
s_mesh->prefs.tx_power_dbm,
|
||||
s_lora_radio->getActiveFrequencyHz(),
|
||||
s_lora_radio->getActiveSpreadingFactor(),
|
||||
s_lora_radio->getActiveBandwidthKHzX10(),
|
||||
s_lora_radio->getActiveCodingRate(),
|
||||
s_lora_radio->getConfiguredTxPower(),
|
||||
s_lora_radio->getNoiseFloor());
|
||||
ui_notify_radio_stats(
|
||||
{
|
||||
bool apc_enabled = false;
|
||||
int8_t apc_reduction = 0;
|
||||
int16_t apc_margin_x10 = 0;
|
||||
uint8_t apc_target = s_mesh->prefs.apc_margin;
|
||||
|
||||
#ifdef CONFIG_ZEPHCORE_APC
|
||||
apc_enabled = s_mesh->isAPCEnabled();
|
||||
apc_reduction = s_mesh->getAPCReduction();
|
||||
apc_margin_x10 = (int16_t)(s_mesh->getAPCMargin() * 10.0f);
|
||||
apc_target = s_mesh->getAPCTargetMargin();
|
||||
#endif
|
||||
ui_set_radio_runtime(
|
||||
s_lora_radio->getEffectiveTxPower(),
|
||||
apc_enabled,
|
||||
apc_reduction,
|
||||
apc_margin_x10,
|
||||
apc_target,
|
||||
s_lora_radio->getActiveSyncWord(),
|
||||
s_lora_radio->getActivePreambleLength(),
|
||||
s_lora_radio->isRxDutyCycleEnabled(),
|
||||
s_lora_radio->isRadioReady(),
|
||||
s_lora_radio->isInRecvMode(),
|
||||
s_lora_radio->isTxActive());
|
||||
}
|
||||
ui_set_radio_stats(
|
||||
s_lora_radio->getPacketsRecv(),
|
||||
s_lora_radio->getPacketsSent(),
|
||||
s_lora_radio->getPacketsRecvErrors());
|
||||
|
||||
@@ -84,6 +84,21 @@ void ui_set_ble_status(bool connected, const char *name);
|
||||
void ui_set_radio_params(uint32_t freq_hz, uint8_t sf, uint16_t bw_khz_x10,
|
||||
uint8_t cr, int8_t tx_power, int16_t noise_floor);
|
||||
|
||||
/**
|
||||
* Update extended live radio/APC details for display.
|
||||
*/
|
||||
void ui_set_radio_runtime(int8_t effective_tx_power, bool apc_enabled,
|
||||
int8_t apc_reduction, int16_t apc_margin_x10,
|
||||
uint8_t apc_target_margin, uint8_t sync_word,
|
||||
uint16_t preamble_len, bool rx_duty_cycle,
|
||||
bool radio_ready, bool in_rx, bool tx_active);
|
||||
|
||||
/**
|
||||
* Update radio packet counters for display.
|
||||
*/
|
||||
void ui_set_radio_stats(uint32_t packets_rx, uint32_t packets_tx,
|
||||
uint32_t packets_err);
|
||||
|
||||
/**
|
||||
* Update GPS data for display.
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user