mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 20:09:17 +00:00
display improvements
This commit is contained in:
@@ -698,6 +698,7 @@ endif()
|
||||
if(CONFIG_ZEPHCORE_UI_DESIGN_BUTTON)
|
||||
target_sources(app PRIVATE
|
||||
helpers/ui-button/ui_task.c
|
||||
helpers/ui-button/time_sync.c
|
||||
)
|
||||
target_include_directories(app PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/helpers/ui-button
|
||||
|
||||
@@ -620,6 +620,18 @@ config ZEPHCORE_UI_DISPLAY_AUTO_OFF_MS
|
||||
Time in milliseconds before the display turns off
|
||||
after the last user interaction. Set to 0 to disable.
|
||||
|
||||
config ZEPHCORE_UI_TIME_SOURCE_FRESH_HOURS
|
||||
int "Clock source tag freshness window (hours)"
|
||||
default 12
|
||||
help
|
||||
The top-bar clock shows a one-character source tag: G (GPS),
|
||||
A (app), N (network/SNTP). If the most recent sync from one of
|
||||
those sources is older than this many hours, the tag falls back
|
||||
to L (local) — the RTC is free-running on its own oscillator.
|
||||
Set to 0 to keep the last external source tag indefinitely
|
||||
(no freshness expiry). Drift on a 50 ppm crystal is ~4 s/day,
|
||||
so this is a trust/freshness signal, not an accuracy limit.
|
||||
|
||||
config ZEPHCORE_AUTO_SHUTDOWN_MILLIVOLTS
|
||||
int "Low-battery auto-shutdown threshold (mV, 0 = disabled)"
|
||||
default 3300 if SOC_SERIES_NRF52
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "CommonCLI.h"
|
||||
#include "battery_curve.h"
|
||||
#include <helpers/time_sync.h>
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include <helpers/AdvertDataHelpers.h>
|
||||
#include <adapters/board/ZephyrBoard.h>
|
||||
@@ -331,6 +332,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
uint32_t curr = getRTCClock()->getCurrentTime();
|
||||
if (sender_timestamp > curr) {
|
||||
getRTCClock()->setCurrentTime(sender_timestamp + 1);
|
||||
time_sync_report(TIME_SYNC_CLI);
|
||||
uint32_t now = getRTCClock()->getCurrentTime();
|
||||
time_t t = (time_t)now;
|
||||
struct tm *tm = gmtime(&t);
|
||||
@@ -350,6 +352,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
uint32_t curr = getRTCClock()->getCurrentTime();
|
||||
if (secs > curr) {
|
||||
getRTCClock()->setCurrentTime(secs);
|
||||
time_sync_report(TIME_SYNC_CLI);
|
||||
time_t t = (time_t)secs;
|
||||
struct tm *tm = gmtime(&t);
|
||||
snprintf(reply, CLI_REPLY_SIZE, "OK - clock set: %02d:%02d - %d/%d/%d UTC",
|
||||
|
||||
@@ -19,10 +19,14 @@ enum time_sync_source {
|
||||
TIME_SYNC_CLI,
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK)
|
||||
#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK) || \
|
||||
IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_BUTTON)
|
||||
|
||||
void time_sync_report(enum time_sync_source src);
|
||||
const char *time_sync_display_label(void);
|
||||
|
||||
/* Current effective time source (prefers live GPS sync). TIME_SYNC_NONE
|
||||
* if the RTC has never been set. */
|
||||
enum time_sync_source time_sync_get_source(void);
|
||||
|
||||
#else
|
||||
|
||||
@@ -31,7 +35,16 @@ static inline void time_sync_report(enum time_sync_source src)
|
||||
ARG_UNUSED(src);
|
||||
}
|
||||
|
||||
#endif /* CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK */
|
||||
static inline enum time_sync_source time_sync_get_source(void)
|
||||
{
|
||||
return TIME_SYNC_NONE;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK || _BUTTON */
|
||||
|
||||
#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK)
|
||||
const char *time_sync_display_label(void);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
* Button UI: track which source last set the RTC, for the top-bar clock tag.
|
||||
*
|
||||
* The tag reflects *freshness*: an external sync (GPS / app / network) is
|
||||
* shown only while it is recent (CONFIG_ZEPHCORE_UI_TIME_SOURCE_FRESH_HOURS).
|
||||
* Once it ages out — or after a reboot, before any sync — the source reads as
|
||||
* local (the RTC is free-running on its own oscillator).
|
||||
*/
|
||||
|
||||
#include <time_sync.h>
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
static enum time_sync_source s_last = TIME_SYNC_NONE;
|
||||
static int64_t s_last_uptime; /* k_uptime_get() at the last report */
|
||||
|
||||
void time_sync_report(enum time_sync_source src)
|
||||
{
|
||||
s_last = src;
|
||||
s_last_uptime = k_uptime_get();
|
||||
}
|
||||
|
||||
enum time_sync_source time_sync_get_source(void)
|
||||
{
|
||||
/* Local/manual (CLI) and "never synced" are both just local time. */
|
||||
if (s_last == TIME_SYNC_NONE || s_last == TIME_SYNC_CLI) {
|
||||
return TIME_SYNC_CLI;
|
||||
}
|
||||
|
||||
#if CONFIG_ZEPHCORE_UI_TIME_SOURCE_FRESH_HOURS > 0
|
||||
int64_t age_ms = k_uptime_get() - s_last_uptime;
|
||||
int64_t window_ms =
|
||||
(int64_t)CONFIG_ZEPHCORE_UI_TIME_SOURCE_FRESH_HOURS * 3600 * 1000;
|
||||
|
||||
if (age_ms > window_ms) {
|
||||
/* External sync has gone stale — fall back to local. */
|
||||
return TIME_SYNC_CLI;
|
||||
}
|
||||
#endif
|
||||
|
||||
return s_last; /* GPS / APP / WIFI, still fresh */
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "ui_task.h"
|
||||
#include "display.h"
|
||||
|
||||
#include <time_sync.h>
|
||||
#include <ZephyrSensorManager.h>
|
||||
|
||||
#include <zephyr/kernel.h>
|
||||
@@ -123,49 +124,72 @@ static uint8_t calc_battery_pct(uint16_t mv)
|
||||
|
||||
/* ========== Helper: Top Bar (Node Name + Battery) ========== */
|
||||
|
||||
/* One-char tag for where the displayed clock was last freshly synced from
|
||||
* (see time_sync_get_source() for the freshness window). Always returns a
|
||||
* letter — falls back to 'L' (local) when no recent external sync. */
|
||||
static char time_source_tag(enum time_sync_source src)
|
||||
{
|
||||
switch (src) {
|
||||
case TIME_SYNC_GPS: return 'G'; /* GPS fix */
|
||||
case TIME_SYNC_APP: return 'A'; /* phone/companion app */
|
||||
case TIME_SYNC_WIFI: return 'N'; /* network (SNTP) */
|
||||
default: return 'L'; /* local: manual/CLI or stale/none */
|
||||
}
|
||||
}
|
||||
|
||||
static void render_top_bar(void)
|
||||
{
|
||||
/* Node name on the left */
|
||||
if (state.node_name[0]) {
|
||||
/* Truncate to fit left side (leave room for battery) */
|
||||
char name[16];
|
||||
/* 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. */
|
||||
char right[16] = "";
|
||||
int pos = 0;
|
||||
|
||||
strncpy(name, state.node_name, sizeof(name) - 1);
|
||||
name[sizeof(name) - 1] = '\0';
|
||||
mc_display_text(0, TOP_BAR_Y, name, false);
|
||||
/* 24h clock from RTC epoch (only if time has been synced).
|
||||
* Before sync, getCurrentTime() returns bare uptime (~seconds),
|
||||
* so check for a sane epoch (after Jan 1 2025 = 1735689600). */
|
||||
if (state.rtc_epoch > 1735689600) {
|
||||
uint32_t day_sec = state.rtc_epoch % 86400;
|
||||
uint8_t hh = day_sec / 3600;
|
||||
uint8_t mm = (day_sec % 3600) / 60;
|
||||
|
||||
/* Source tag: G=GPS, A=app, N=network, L=local (always set). */
|
||||
char src = time_source_tag(time_sync_get_source());
|
||||
|
||||
pos = snprintf(right, sizeof(right), "%02u:%02u%c ", hh, mm, src);
|
||||
}
|
||||
|
||||
/* Right side: "HH:MM XX%" — clock then battery, right-aligned */
|
||||
{
|
||||
char right[16] = "";
|
||||
int pos = 0;
|
||||
/* Battery percentage */
|
||||
if (state.battery_mv > 0) {
|
||||
uint8_t pct = state.battery_pct;
|
||||
|
||||
/* 24h clock from RTC epoch (only if time has been synced).
|
||||
* Before sync, getCurrentTime() returns bare uptime (~seconds),
|
||||
* so check for a sane epoch (after Jan 1 2025 = 1735689600). */
|
||||
if (state.rtc_epoch > 1735689600) {
|
||||
uint32_t day_sec = state.rtc_epoch % 86400;
|
||||
uint8_t hh = day_sec / 3600;
|
||||
uint8_t mm = (day_sec % 3600) / 60;
|
||||
|
||||
pos = snprintf(right, sizeof(right), "%02u:%02u ", hh, mm);
|
||||
if (pct == 0) {
|
||||
pct = calc_battery_pct(state.battery_mv);
|
||||
}
|
||||
snprintf(right + pos, sizeof(right) - pos, "%u%%", pct);
|
||||
}
|
||||
|
||||
/* Battery percentage */
|
||||
if (state.battery_mv > 0) {
|
||||
uint8_t pct = state.battery_pct;
|
||||
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);
|
||||
}
|
||||
|
||||
if (pct == 0) {
|
||||
pct = calc_battery_pct(state.battery_mv);
|
||||
}
|
||||
snprintf(right + pos, sizeof(right) - pos, "%u%%", pct);
|
||||
/* 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;
|
||||
|
||||
if (max_chars > (int)sizeof(name) - 1) {
|
||||
max_chars = sizeof(name) - 1;
|
||||
}
|
||||
|
||||
if (right[0]) {
|
||||
int x = DISP_W - ((int)strlen(right) * FONT_W);
|
||||
|
||||
mc_display_text(x, TOP_BAR_Y, right, false);
|
||||
if (max_chars < 0) {
|
||||
max_chars = 0;
|
||||
}
|
||||
strncpy(name, state.node_name, max_chars);
|
||||
name[max_chars] = '\0';
|
||||
mc_display_text(0, TOP_BAR_Y, name, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
|
||||
#include <time_sync.h>
|
||||
#include <adapters/gps/ZephyrGPSManager.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static enum time_sync_source s_last = TIME_SYNC_NONE;
|
||||
static int64_t s_last_uptime; /* k_uptime_get() at the last report */
|
||||
|
||||
static const char *source_short_name(enum time_sync_source src)
|
||||
{
|
||||
@@ -22,6 +24,30 @@ static const char *source_short_name(enum time_sync_source src)
|
||||
void time_sync_report(enum time_sync_source src)
|
||||
{
|
||||
s_last = src;
|
||||
s_last_uptime = k_uptime_get();
|
||||
}
|
||||
|
||||
/* Freshness-windowed source for the top-bar clock tag. Local/manual (CLI)
|
||||
* and "never synced" both read as local; an external sync ages out after
|
||||
* CONFIG_ZEPHCORE_UI_TIME_SOURCE_FRESH_HOURS. (Distinct from the System >
|
||||
* Info > Time label below, which is not time-windowed.) */
|
||||
enum time_sync_source time_sync_get_source(void)
|
||||
{
|
||||
if (s_last == TIME_SYNC_NONE || s_last == TIME_SYNC_CLI) {
|
||||
return TIME_SYNC_CLI;
|
||||
}
|
||||
|
||||
#if CONFIG_ZEPHCORE_UI_TIME_SOURCE_FRESH_HOURS > 0
|
||||
int64_t age_ms = k_uptime_get() - s_last_uptime;
|
||||
int64_t window_ms =
|
||||
(int64_t)CONFIG_ZEPHCORE_UI_TIME_SOURCE_FRESH_HOURS * 3600 * 1000;
|
||||
|
||||
if (age_ms > window_ms) {
|
||||
return TIME_SYNC_CLI;
|
||||
}
|
||||
#endif
|
||||
|
||||
return s_last;
|
||||
}
|
||||
|
||||
const char *time_sync_display_label(void)
|
||||
|
||||
Reference in New Issue
Block a user