mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-10-23 17:13:23 +00:00
upstream dev bump
This commit is contained in:
1 parent
c89ea27ae8
commit
6f7f807e82
20 files changed
+278
-58
No files matched your search
@@ -33,7 +33,7 @@ list(APPEND BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
# Service advertises (CONFIG_BT_DIS_FW_REV_STR, generated from it below), what the
|
||||
# GitHub release is tagged/named, and what the Mesh America catalog uses as its
|
||||
# version key. The release workflow reads this line directly — keep its form.
|
||||
set(ZEPHCORE_FIRMWARE_VERSION "1.18.0-zephcore")
|
||||
set(ZEPHCORE_FIRMWARE_VERSION "1.18.1-zephcore")
|
||||
|
||||
# ========== Helpers ==========
|
||||
# Append FILE to the overlay list once, keeping its FIRST position. Position is
|
||||
|
||||
@@ -1125,7 +1125,9 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, const char* command, cha
|
||||
} else if (memcmp(config, "input.rotate", 12) == 0) {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "> %d", zephcore_input_is_flipped() ? 1 : 0);
|
||||
} else if (memcmp(config, "tz.offset", 9) == 0) {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "> %d", (int)_prefs->tz_offset);
|
||||
char hours[TZ_OFFSET_TEXT_LEN];
|
||||
tz_offset_format_hours(tz_offset_minutes(_prefs->tz_offset), hours, sizeof(hours));
|
||||
snprintf(reply, CLI_REPLY_SIZE, "> %s", hours);
|
||||
} else if (memcmp(config, "gps diag", 8) == 0) {
|
||||
/* What the last module-configuration attempt actually did.
|
||||
* Self-limits on the remote path like cad.stats/meshtimesync:
|
||||
@@ -1714,19 +1716,23 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, const char* command, cha
|
||||
strcpy(reply, "Error: must be 0, 1, on, or off");
|
||||
}
|
||||
} else if (memcmp(config, "tz.offset ", 10) == 0) {
|
||||
// Whole-hour offset from UTC for the on-device clock display only; the RTC,
|
||||
// `clock` and `time <epoch>` stay UTC. Whole hours as upstream.
|
||||
long val;
|
||||
if (!cliNum(&config[10], cliDefaults()->tz_offset, &val)) {
|
||||
// Hours from UTC in quarter-hour steps, for the on-device clock display
|
||||
// only; the RTC, `clock` and `time <epoch>` stay UTC.
|
||||
float val;
|
||||
if (!cliFloat(&config[10], cliDefaults()->tz_offset, &val)) {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "Error: expected %d..%d or default",
|
||||
TZ_OFFSET_MIN, TZ_OFFSET_MAX);
|
||||
} else if (val < TZ_OFFSET_MIN || val > TZ_OFFSET_MAX) {
|
||||
snprintf(reply, CLI_REPLY_SIZE, "Error: offset range is %d..%d",
|
||||
TZ_OFFSET_MIN, TZ_OFFSET_MAX);
|
||||
} else if (!tz_offset_valid(val)) {
|
||||
strcpy(reply, "Error: offset must be whole quarter hours (e.g. 5.5, 5.75)");
|
||||
} else {
|
||||
_prefs->tz_offset = (int8_t)val;
|
||||
char hours[TZ_OFFSET_TEXT_LEN];
|
||||
_prefs->tz_offset = tz_offset_sanitize(val);
|
||||
savePrefs();
|
||||
snprintf(reply, CLI_REPLY_SIZE, "OK - tz.offset=%d", (int)val);
|
||||
tz_offset_format_hours(tz_offset_minutes(_prefs->tz_offset), hours, sizeof(hours));
|
||||
snprintf(reply, CLI_REPLY_SIZE, "OK - tz.offset=%s", hours);
|
||||
}
|
||||
} else if (memcmp(config, "gps diag", 8) == 0) {
|
||||
// set gps diag <0|1|on|off> — arm module-configuration reporting.
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
* and this header is C++; see the note there. */
|
||||
#include "led_gate.h"
|
||||
|
||||
/* TZ_OFFSET_MIN / TZ_OFFSET_MAX and the quarter-hour helpers for tz_offset. */
|
||||
#include "tz_offset.h"
|
||||
|
||||
/* ── Accepted radio parameter ranges ──
|
||||
* One definition for everything that accepts or validates a radio preset
|
||||
* (CLIs, the companion protocol, the load-time checks): a setter must never
|
||||
@@ -62,11 +65,6 @@
|
||||
* ConfigureSideDetectors limit of 3. */
|
||||
#define EXTRA_SF_MAX 3
|
||||
|
||||
/* Display timezone offset range, whole hours from UTC. Matches upstream
|
||||
* MeshCore's `set tz.offset` so the same command works against both trees. */
|
||||
#define TZ_OFFSET_MIN (-12)
|
||||
#define TZ_OFFSET_MAX 14
|
||||
|
||||
struct NodePrefs {
|
||||
/* ---- Common fields (both roles) ---- */
|
||||
float airtime_factor;
|
||||
@@ -149,10 +147,10 @@ struct NodePrefs {
|
||||
uint8_t display_rotate; // 1 = panel rotated 180 degrees
|
||||
uint8_t input_rotate; // 1 = joystick up/down and left/right swapped
|
||||
|
||||
/* Whole-hour offset from UTC, -12..+14, applied only when formatting a clock
|
||||
* for the local display. It must never reach RTCClock or any timestamp that
|
||||
* leaves this node. */
|
||||
int8_t tz_offset;
|
||||
/* Hours from UTC, -12..+14 in quarter hours (float, as upstream), applied
|
||||
* only when formatting a clock for the local display. It must never reach
|
||||
* RTCClock or any timestamp that leaves this node. See tz_offset.h. */
|
||||
float tz_offset;
|
||||
|
||||
/* The family base detPeak that cad_offset was learned against (0 = never
|
||||
* stored). Lets the firmware re-anchor the offset at boot when the driver's
|
||||
@@ -354,8 +352,9 @@ static inline void sanitizeNodePrefs(NodePrefs* p) {
|
||||
/* Neutral, not the boundary — a garbage offset is not a request to run
|
||||
* the CAD staircase at one end of its range. */
|
||||
if (p->cad_offset < CAD_OFFSET_MIN || p->cad_offset > CAD_OFFSET_MAX) p->cad_offset = 0;
|
||||
/* UTC, not a boundary zone, for a byte that carries no user intent. */
|
||||
if (p->tz_offset < TZ_OFFSET_MIN || p->tz_offset > TZ_OFFSET_MAX) p->tz_offset = 0;
|
||||
/* UTC, not a boundary zone, for a value that carries no user intent;
|
||||
* an in-range value snaps to the nearest quarter hour. */
|
||||
p->tz_offset = tz_offset_sanitize(p->tz_offset);
|
||||
if (p->probe_interval != 0 && p->probe_interval < 10) p->probe_interval = 10;
|
||||
|
||||
/* 6-digit BLE passkey — anything else is rejected at pairing time and
|
||||
|
||||
@@ -17,6 +17,9 @@ struct Wr {
|
||||
size_t off;
|
||||
void put(const void *src, size_t n) { memcpy(b + off, src, n); off += n; }
|
||||
void zeros(size_t n) { memset(b + off, 0, n); off += n; }
|
||||
/* tz_offset: both layouts hold signed whole hours in one byte, so a
|
||||
* fractional offset is truncated toward zero. */
|
||||
void tz(float hours) { int8_t v = (int8_t)tz_offset_sanitize(hours); put(&v, 1); }
|
||||
};
|
||||
|
||||
struct Rd {
|
||||
@@ -36,6 +39,13 @@ struct Rd {
|
||||
}
|
||||
off += n;
|
||||
}
|
||||
/* The one-byte whole-hours tz_offset into the float field. */
|
||||
void tz(float &hours) {
|
||||
if (off < len) {
|
||||
hours = (int8_t)b[off];
|
||||
}
|
||||
off += 1;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -103,7 +113,7 @@ size_t companionPrefsEncode(const NodePrefs &p, uint8_t *buf, size_t cap)
|
||||
w.put(&p.display_rotate, 1);
|
||||
w.put(&p.input_rotate, 1);
|
||||
w.put(&p.cad_base, 1);
|
||||
w.put(&p.tz_offset, 1);
|
||||
w.tz(p.tz_offset);
|
||||
w.put(&p.leds_radio_mode, 1);
|
||||
w.put(&p.leds_hb_mode, 1);
|
||||
w.put(&p.wifi_enabled, 1);
|
||||
@@ -184,7 +194,7 @@ bool companionPrefsDecode(NodePrefs &prefs, const uint8_t *buf, size_t len)
|
||||
/* cad_base 0 = "no base recorded": setCadParams() then leaves the stored
|
||||
* offset alone. */
|
||||
r.take(&p.cad_base, 1);
|
||||
r.take(&p.tz_offset, 1);
|
||||
r.tz(p.tz_offset);
|
||||
r.take(&p.leds_radio_mode, 1);
|
||||
r.take(&p.leds_hb_mode, 1);
|
||||
r.take(&p.wifi_enabled, 1);
|
||||
@@ -276,7 +286,7 @@ size_t serverPrefsEncode(const NodePrefs &prefs, uint8_t *buf, size_t cap)
|
||||
w.put(&prefs.cad_base, sizeof(prefs.cad_base));
|
||||
/* Display timezone offset (offset 308) — signed whole hours from UTC,
|
||||
* applied only when formatting the on-device clock */
|
||||
w.put(&prefs.tz_offset, sizeof(prefs.tz_offset));
|
||||
w.tz(prefs.tz_offset);
|
||||
/* LED activity/heartbeat modes (offsets 309-310) */
|
||||
w.put(&prefs.leds_radio_mode, sizeof(prefs.leds_radio_mode));
|
||||
w.put(&prefs.leds_hb_mode, sizeof(prefs.leds_hb_mode));
|
||||
@@ -352,7 +362,7 @@ void serverPrefsDecode(NodePrefs &prefs, const uint8_t *buf, size_t len)
|
||||
* offset alone, since a node upgrading across a table change cannot know
|
||||
* which base its offset came from. */
|
||||
r.get(&prefs.cad_base, sizeof(prefs.cad_base));
|
||||
r.get(&prefs.tz_offset, sizeof(prefs.tz_offset));
|
||||
r.tz(prefs.tz_offset);
|
||||
r.get(&prefs.leds_radio_mode, sizeof(prefs.leds_radio_mode));
|
||||
r.get(&prefs.leds_hb_mode, sizeof(prefs.leds_hb_mode));
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
* Display timezone offset: validation and text, shared by the CLI, both UIs
|
||||
* and the prefs loaders (C and C++).
|
||||
*
|
||||
* NodePrefs::tz_offset holds float hours from UTC, upstream MeshCore's type and
|
||||
* prefs.json value. ZephCore accepts quarter hours only and every consumer
|
||||
* works in whole minutes. The offset is applied only when a clock is formatted
|
||||
* for the local display; it never reaches RTCClock or a timestamp.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/* Range in hours, as upstream's `set tz.offset`. */
|
||||
#define TZ_OFFSET_MIN (-12)
|
||||
#define TZ_OFFSET_MAX 14
|
||||
|
||||
/* Buffer size for tz_offset_format_hours() and tz_offset_format_label(). */
|
||||
#define TZ_OFFSET_TEXT_LEN 12
|
||||
|
||||
/* Nearest quarter hour; 0 (UTC) for NaN or a value outside the range. */
|
||||
static inline float tz_offset_sanitize(float hours)
|
||||
{
|
||||
if (hours != hours || hours < TZ_OFFSET_MIN || hours > TZ_OFFSET_MAX) {
|
||||
return 0.0f;
|
||||
}
|
||||
float q = hours * 4.0f;
|
||||
return (float)(int32_t)(q < 0.0f ? q - 0.5f : q + 0.5f) * 0.25f;
|
||||
}
|
||||
|
||||
/* In range and a whole number of quarter hours (within 0.0001 h). */
|
||||
static inline bool tz_offset_valid(float hours)
|
||||
{
|
||||
if (hours != hours || hours < TZ_OFFSET_MIN || hours > TZ_OFFSET_MAX) {
|
||||
return false;
|
||||
}
|
||||
float d = hours - tz_offset_sanitize(hours);
|
||||
return d > -0.0001f && d < 0.0001f;
|
||||
}
|
||||
|
||||
/* Offset in whole minutes, from a sanitized value. */
|
||||
static inline int16_t tz_offset_minutes(float hours)
|
||||
{
|
||||
float m = tz_offset_sanitize(hours) * 60.0f;
|
||||
return (int16_t)(m < 0.0f ? m - 0.5f : m + 0.5f);
|
||||
}
|
||||
|
||||
/* Decimal hours, exact for every quarter hour: "0.0", "2.0", "-3.5", "5.75".
|
||||
* Same text as upstream's "%.1f" wherever that is exact. */
|
||||
static inline void tz_offset_format_hours(int16_t minutes, char *buf, size_t n)
|
||||
{
|
||||
static const char *const frac[4] = { "0", "25", "5", "75" };
|
||||
unsigned a = (unsigned)(minutes < 0 ? -minutes : minutes);
|
||||
|
||||
snprintf(buf, n, "%s%u.%s", minutes < 0 ? "-" : "", a / 60, frac[(a % 60) / 15]);
|
||||
}
|
||||
|
||||
/* Zone label for a clock display, at most 8 characters: "UTC", "UTC+2",
|
||||
* "UTC-3:30", and "UTC+1245" where the h:mm form would be longer. */
|
||||
static inline void tz_offset_format_label(int16_t minutes, char *buf, size_t n)
|
||||
{
|
||||
unsigned a = (unsigned)(minutes < 0 ? -minutes : minutes);
|
||||
char sign = minutes < 0 ? '-' : '+';
|
||||
|
||||
if (a == 0) {
|
||||
snprintf(buf, n, "UTC");
|
||||
} else if (a % 60 == 0) {
|
||||
snprintf(buf, n, "UTC%c%u", sign, a / 60);
|
||||
} else if (a / 60 < 10) {
|
||||
snprintf(buf, n, "UTC%c%u:%02u", sign, a / 60, a % 60);
|
||||
} else {
|
||||
snprintf(buf, n, "UTC%c%02u%02u", sign, a / 60, a % 60);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "ui_mesh_actions.h"
|
||||
#include "display.h"
|
||||
#include <helpers/buzzer_gate.h>
|
||||
#include <helpers/tz_offset.h>
|
||||
|
||||
#include <time_sync.h>
|
||||
#include <ZephyrGPSManager.h>
|
||||
@@ -200,19 +201,17 @@ static int current_page_idx;
|
||||
* silent mesh-wide mute. Never write these values back into state. */
|
||||
static uint32_t local_epoch(void)
|
||||
{
|
||||
return state.rtc_epoch + (int32_t)state.tz_offset * 3600;
|
||||
return state.rtc_epoch + (int32_t)state.tz_offset_min * 60;
|
||||
}
|
||||
|
||||
/* The zone the displayed digits are actually in: "UTC", "UTC+2", "UTC-11".
|
||||
/* The zone the displayed digits are actually in: "UTC", "UTC+2", "UTC-3:30",
|
||||
* at most 8 characters (tz_offset_format_label()).
|
||||
* Static buffer -- every caller is on the UI thread inside ui_pages_render(). */
|
||||
static const char *tz_label(void)
|
||||
{
|
||||
static char label[8];
|
||||
static char label[TZ_OFFSET_TEXT_LEN];
|
||||
|
||||
if (state.tz_offset == 0) {
|
||||
return "UTC";
|
||||
}
|
||||
snprintf(label, sizeof(label), "UTC%+d", (int)state.tz_offset);
|
||||
tz_offset_format_label(state.tz_offset_min, label, sizeof(label));
|
||||
return label;
|
||||
}
|
||||
|
||||
@@ -1713,8 +1712,8 @@ static void render_status(void)
|
||||
uint8_t mm = (day_sec % 3600) / 60;
|
||||
uint8_t ss = day_sec % 60;
|
||||
|
||||
/* "T:" rather than "Time:" so a two-digit zone still fits the
|
||||
* 20-column budget of the 200x200 e-paper boards, which pair
|
||||
/* "T:" rather than "Time:" so an 8-character zone label still fits
|
||||
* the 20-column budget of the 200x200 e-paper boards, which pair
|
||||
* CONFIG_ZEPHCORE_DISPLAY_LARGE_FONT (10x16) with this page. */
|
||||
snprintf(buf, sizeof(buf), "T: %02u:%02u:%02u %s", hh, mm, ss,
|
||||
tz_label());
|
||||
|
||||
@@ -45,7 +45,7 @@ struct ui_state {
|
||||
uint16_t battery_mv;
|
||||
uint8_t battery_pct;
|
||||
uint32_t rtc_epoch; /* Unix epoch from RTC, ALWAYS UTC (0 = not set) */
|
||||
int8_t tz_offset; /* whole hours from UTC, applied at format time
|
||||
int16_t tz_offset_min; /* minutes from UTC, applied at format time
|
||||
* only -- rtc_epoch itself is never shifted */
|
||||
|
||||
/* Messages page */
|
||||
|
||||
@@ -1035,14 +1035,14 @@ void ui_set_clock(uint32_t epoch)
|
||||
}
|
||||
}
|
||||
|
||||
void ui_set_tz(int8_t hours)
|
||||
void ui_set_tz(int16_t minutes)
|
||||
{
|
||||
struct ui_state *s = get_state();
|
||||
|
||||
if (s->tz_offset == hours) {
|
||||
if (s->tz_offset_min == minutes) {
|
||||
return;
|
||||
}
|
||||
s->tz_offset = hours;
|
||||
s->tz_offset_min = minutes;
|
||||
|
||||
/* EPD only redraws on demand, so a timezone change would otherwise not
|
||||
* appear until something else forced a repaint. */
|
||||
|
||||
@@ -260,7 +260,7 @@ extern "C" void ui_set_ble_enabled(bool enabled)
|
||||
extern "C" void ui_set_gps_data(bool, uint8_t, int32_t, int32_t, int32_t) {}
|
||||
extern "C" void ui_set_clock(uint32_t) {}
|
||||
/* The joystick UI reads tz_offset straight off NodePrefs at render time. */
|
||||
extern "C" void ui_set_tz(int8_t) {}
|
||||
extern "C" void ui_set_tz(int16_t) {}
|
||||
extern "C" void ui_add_recent(const char *, int16_t, uint32_t) {}
|
||||
extern "C" void ui_set_node_name(const char *) {}
|
||||
extern "C" void ui_clear_recent(void) {}
|
||||
|
||||
@@ -349,25 +349,21 @@ int SystemTimeScreen::render(JoystickDisplay &display)
|
||||
/* The RTC is UTC; the offset is applied here, at format time only. It
|
||||
* must never reach the clock itself -- see NodePrefs::tz_offset. */
|
||||
NodePrefs *prefs = _task->getPrefs();
|
||||
int8_t tz = prefs ? prefs->tz_offset : 0;
|
||||
int16_t tz_min = prefs ? tz_offset_minutes(prefs->tz_offset) : 0;
|
||||
uint32_t epoch = _rtc->getCurrentTime();
|
||||
|
||||
/* Before a sync getCurrentTime() returns bare uptime, and a negative
|
||||
* offset would underflow it into a garbage far-future date. Same
|
||||
* "time is set" threshold (2025-01-01) the other UI uses. */
|
||||
if (epoch > 1735689600) {
|
||||
epoch += (int32_t)tz * 3600;
|
||||
epoch += (int32_t)tz_min * 60;
|
||||
} else {
|
||||
tz = 0;
|
||||
tz_min = 0;
|
||||
}
|
||||
|
||||
char timeText[16], dateText[16], tzText[8];
|
||||
char timeText[16], dateText[16], tzText[TZ_OFFSET_TEXT_LEN];
|
||||
formatUnixDateTime(epoch, timeText, sizeof(timeText), dateText, sizeof(dateText));
|
||||
if (tz == 0) {
|
||||
strcpy(tzText, "UTC");
|
||||
} else {
|
||||
snprintf(tzText, sizeof(tzText), "UTC%+d", (int)tz);
|
||||
}
|
||||
tz_offset_format_label(tz_min, tzText, sizeof(tzText));
|
||||
|
||||
const char *labels[4] = { "Time:", "Date:", "TZ:", "Source:" };
|
||||
const char *values[4] = { timeText, dateText, tzText,
|
||||
|
||||
@@ -77,9 +77,9 @@ WEAK void ui_set_clock(uint32_t epoch)
|
||||
ARG_UNUSED(epoch);
|
||||
}
|
||||
|
||||
WEAK void ui_set_tz(int8_t hours)
|
||||
WEAK void ui_set_tz(int16_t minutes)
|
||||
{
|
||||
ARG_UNUSED(hours);
|
||||
ARG_UNUSED(minutes);
|
||||
}
|
||||
|
||||
WEAK void ui_add_recent(const char *name, int16_t rssi, uint32_t age_s)
|
||||
|
||||
@@ -352,7 +352,7 @@ extern "C" void mesh_housekeeping_ui_refresh(void)
|
||||
* display offset is a separate push so nothing downstream is tempted to
|
||||
* bake a timezone into a timestamp (see NodePrefs::tz_offset). */
|
||||
ui_set_clock(s_rtc_clock->getCurrentTime());
|
||||
ui_set_tz(s_mesh->prefs.tz_offset);
|
||||
ui_set_tz(tz_offset_minutes(s_mesh->prefs.tz_offset));
|
||||
|
||||
ui_push_radio_state(*s_lora_radio);
|
||||
/* The app and the CLI change it too. */
|
||||
|
||||
@@ -119,13 +119,14 @@ void ui_set_battery(uint16_t mv, uint8_t pct);
|
||||
void ui_set_clock(uint32_t epoch);
|
||||
|
||||
/**
|
||||
* Set the whole-hour offset from UTC used when formatting the on-device
|
||||
* clock. Display only: the epoch handed to ui_set_clock() stays UTC, and
|
||||
* nothing here may ever reach RTCClock or a packet timestamp.
|
||||
* Set the offset from UTC used when formatting the on-device clock. Display
|
||||
* only: the epoch handed to ui_set_clock() stays UTC, and nothing here may
|
||||
* ever reach RTCClock or a packet timestamp.
|
||||
*
|
||||
* @param hours Offset from UTC, TZ_OFFSET_MIN..TZ_OFFSET_MAX (0 = UTC)
|
||||
* @param minutes Offset from UTC in minutes, a multiple of 15 within
|
||||
* TZ_OFFSET_MIN..TZ_OFFSET_MAX hours (0 = UTC)
|
||||
*/
|
||||
void ui_set_tz(int8_t hours);
|
||||
void ui_set_tz(int16_t minutes);
|
||||
|
||||
/**
|
||||
* Record a recently heard contact for the "recent" page.
|
||||
|
||||
@@ -563,6 +563,10 @@ static void server_event_loop(void)
|
||||
|
||||
#if ZEPHCORE_HAS_UI
|
||||
ui_set_clock(rtc_clock.getCurrentTime());
|
||||
/* Display offset only; the epoch above stays UTC. */
|
||||
if (server_role) {
|
||||
ui_set_tz(tz_offset_minutes(server_role->prefs->tz_offset));
|
||||
}
|
||||
|
||||
#ifdef ZEPHCORE_LORA
|
||||
/* Refresh live radio state (noise floor, TX power
|
||||
|
||||
Reference in new issue
Block a user