2026-06-03 08:23:11 +02:00
|
|
|
#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>
|
2026-06-14 13:37:03 +02:00
|
|
|
#include "Persist.h"
|
2026-06-03 08:23:11 +02:00
|
|
|
|
|
|
|
|
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) {
|
2026-06-14 13:37:03 +02:00
|
|
|
if (!persist::writeHeader(file, SAVE_MAGIC, SAVE_VERSION, (uint16_t)_count)) return false;
|
2026-06-03 08:23:11 +02:00
|
|
|
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) {
|
|
|
|
|
uint16_t cnt = 0;
|
2026-06-14 13:37:03 +02:00
|
|
|
if (!persist::readHeader(file, SAVE_MAGIC, SAVE_VERSION, cnt)) return false;
|
2026-06-03 08:23:11 +02:00
|
|
|
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';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|