mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
feat(nav): WaypointStore — persistent /waypoints table
Phase 3. Fixed 16-entry table of (lat, lon, ts, label[12]) persisted to /waypoints with a magic+version header (mirrors TrailStore's format). Unlike the RAM-only trail, waypoints are loaded in UITask::begin() and rewritten on change via UITask::saveWaypoints(), so they survive reboots. add/remove/rename/clear operate on the in-RAM table; the screen layer (phase 5) drives persistence after each edit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
104
examples/companion_radio/Waypoint.h
Normal file
104
examples/companion_radio/Waypoint.h
Normal file
@@ -0,0 +1,104 @@
|
||||
#pragma once
|
||||
// Saved GPS waypoints — small fixed table persisted to /waypoints. Unlike the
|
||||
// trail (a RAM ring), waypoints are long-lived: loaded at boot, rewritten on
|
||||
// every add / delete / rename. Used by the navigation feature to mark a spot
|
||||
// (car, camp, water…) and later get bearing + distance back to it.
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <string.h>
|
||||
|
||||
static const uint8_t WAYPOINT_LABEL_LEN = 12; // incl. NUL → 11 visible chars
|
||||
|
||||
struct Waypoint {
|
||||
int32_t lat_1e6, lon_1e6;
|
||||
uint32_t ts; // RTC time when marked
|
||||
char label[WAYPOINT_LABEL_LEN];
|
||||
};
|
||||
|
||||
class WaypointStore {
|
||||
public:
|
||||
static const int CAPACITY = 16;
|
||||
|
||||
int count() const { return _count; }
|
||||
bool full() const { return _count >= CAPACITY; }
|
||||
const Waypoint& at(int i) const { return _wp[i]; }
|
||||
|
||||
// Add a waypoint; label may be empty (caller can auto-name). Returns false
|
||||
// if the table is full. Trims/copies the label safely.
|
||||
bool add(int32_t lat, int32_t lon, uint32_t ts, const char* label) {
|
||||
if (_count >= CAPACITY) return false;
|
||||
Waypoint& w = _wp[_count++];
|
||||
w.lat_1e6 = lat; w.lon_1e6 = lon; w.ts = ts;
|
||||
setLabel(w, label);
|
||||
return true;
|
||||
}
|
||||
|
||||
void remove(int i) {
|
||||
if (i < 0 || i >= _count) return;
|
||||
for (int j = i; j < _count - 1; j++) _wp[j] = _wp[j + 1];
|
||||
_count--;
|
||||
}
|
||||
|
||||
void rename(int i, const char* label) {
|
||||
if (i < 0 || i >= _count) return;
|
||||
setLabel(_wp[i], label);
|
||||
}
|
||||
|
||||
void clear() { _count = 0; }
|
||||
|
||||
// ── persistence ──────────────────────────────────────────────────────────
|
||||
// Layout: 4-byte magic "WYPT", uint8 version, uint8 reserved, uint16 count,
|
||||
// then `count` raw Waypoint records.
|
||||
static const uint32_t SAVE_MAGIC = 0x54505957; // "WYPT" little-endian
|
||||
static const uint8_t SAVE_VERSION = 1;
|
||||
|
||||
template <typename F>
|
||||
bool writeTo(F& file) {
|
||||
uint32_t magic = SAVE_MAGIC;
|
||||
uint8_t ver = SAVE_VERSION, res = 0;
|
||||
uint16_t cnt = (uint16_t)_count;
|
||||
if (file.write((uint8_t*)&magic, sizeof(magic)) != sizeof(magic)) return false;
|
||||
if (file.write(&ver, 1) != 1) return false;
|
||||
if (file.write(&res, 1) != 1) return false;
|
||||
if (file.write((uint8_t*)&cnt, sizeof(cnt)) != sizeof(cnt)) return false;
|
||||
for (int i = 0; i < _count; i++) {
|
||||
if (file.write((uint8_t*)&_wp[i], sizeof(Waypoint)) != sizeof(Waypoint)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
bool readFrom(F& file) {
|
||||
uint32_t magic = 0;
|
||||
if (file.read((uint8_t*)&magic, sizeof(magic)) != (int)sizeof(magic)) return false;
|
||||
if (magic != SAVE_MAGIC) return false;
|
||||
uint8_t ver = 0, res = 0;
|
||||
uint16_t cnt = 0;
|
||||
file.read(&ver, 1);
|
||||
file.read(&res, 1);
|
||||
file.read((uint8_t*)&cnt, sizeof(cnt));
|
||||
if (ver != SAVE_VERSION) return false;
|
||||
if (cnt > CAPACITY) cnt = CAPACITY;
|
||||
_count = 0;
|
||||
for (uint16_t i = 0; i < cnt; i++) {
|
||||
Waypoint w;
|
||||
if (file.read((uint8_t*)&w, sizeof(Waypoint)) != (int)sizeof(Waypoint)) break;
|
||||
w.label[WAYPOINT_LABEL_LEN - 1] = '\0'; // defensive
|
||||
_wp[_count++] = w;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
Waypoint _wp[CAPACITY];
|
||||
int _count = 0;
|
||||
|
||||
static void setLabel(Waypoint& w, const char* label) {
|
||||
if (label && label[0]) {
|
||||
strncpy(w.label, label, WAYPOINT_LABEL_LEN - 1);
|
||||
w.label[WAYPOINT_LABEL_LEN - 1] = '\0';
|
||||
} else {
|
||||
w.label[0] = '\0';
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1161,6 +1161,15 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
_alert_expiry = 0;
|
||||
_batt_mv = AbstractUITask::getBattMilliVolts(); // seed EMA with first reading
|
||||
|
||||
// Load persisted waypoints (table survives reboots, unlike the RAM trail).
|
||||
{
|
||||
DataStore* ds = the_mesh.getDataStore();
|
||||
if (ds) {
|
||||
File f = ds->openRead("/waypoints");
|
||||
if (f) { _waypoints.readFrom(f); f.close(); }
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize ping state
|
||||
_ping_active = false;
|
||||
_ping_tag = 0;
|
||||
@@ -1962,6 +1971,15 @@ bool UITask::currentCourse(int& deg_out) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
void UITask::saveWaypoints() {
|
||||
DataStore* ds = the_mesh.getDataStore();
|
||||
if (!ds) return;
|
||||
File f = ds->openWrite("/waypoints");
|
||||
if (!f) return;
|
||||
_waypoints.writeTo(f);
|
||||
f.close();
|
||||
}
|
||||
|
||||
char UITask::checkDisplayOn(char c) {
|
||||
if (_display != NULL) {
|
||||
if (!_display->isOn()) {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "../AbstractUITask.h"
|
||||
#include "../NodePrefs.h"
|
||||
#include "../Trail.h"
|
||||
#include "../Waypoint.h"
|
||||
|
||||
class UITask : public AbstractUITask {
|
||||
DisplayDriver* _display;
|
||||
@@ -78,6 +79,7 @@ class UITask : public AbstractUITask {
|
||||
UIScreen* curr;
|
||||
CayenneLPP _dash_lpp;
|
||||
TrailStore _trail;
|
||||
WaypointStore _waypoints;
|
||||
uint32_t _next_trail_sample_ms = 0;
|
||||
|
||||
// Course-over-ground ring — a heading source independent of trail recording.
|
||||
@@ -143,6 +145,8 @@ public:
|
||||
void gotoAutoAdvertScreen();
|
||||
void gotoTrailScreen();
|
||||
TrailStore& trail() { return _trail; }
|
||||
WaypointStore& waypoints() { return _waypoints; }
|
||||
void saveWaypoints(); // persist the table to /waypoints
|
||||
// Current course over ground in degrees (0..359), or false if not enough
|
||||
// recent movement to derive a stable heading. Independent of trail logging.
|
||||
bool currentCourse(int& deg_out) const;
|
||||
|
||||
Reference in New Issue
Block a user