peripherals & power: GPS fixes, poweroff/pwrmgt, upstream SensorManager telemetry

- gps: servers honour gps_enabled at boot (was lost on reboot; zc.gps_set
  upgrades older prefs to on once); sleep lock only while acquiring;
  gps off works on boards without a power line (UART sleep); UI toggles
  and fixes handled on the main thread; configure only after a good init;
  observer parks its GPS; one interval clamp (clampGpsInterval); one
  nav-mode knob (room servers on Air530Z now stationary); GNSS UARTE from DT
- gps: split into ZephyrGPSManager / gps_power / gps_module_cfg
  (+gps_internal.h); dead driver branches, gps_get_diag, sats_* removed
- clock: ZephyrRTCClock writes the hardware RTC itself (clkreboot now
  resets it); MESH_EVENT_RTC_SAVE gone
- power: one power-off path (zephcore_power_off, ZephyrBoard::powerOff),
  poweroff/shutdown CLI, get pwrmgt.*, crash record kept across reboot,
  battery reading cached 10 s, fuel gauge on the current API
- telemetry: upstream SensorManager + CayenneLPP (rounded to nearest),
  one builder for all roles; per-sensor channels, MCU temp always,
  guest gating and inverse mask on servers; sensor list/get/set work;
  BME280/BMP280 also probed at 0x77
- CLI: gps in upstream's form, get gps detail, gps sync
This commit is contained in:
liquidraver committed 2026-09-25 17:56:34 +02:00
1 parent e6d6ecef80
commit d5bd805710
59 files changed
+3090 -3077

No files matched your search

-37
View File
@@ -11,7 +11,6 @@
#include <ZephyrDataStore.h>
#include <ZephyrBoard.h>
#include <adapters/radio/LoRaRadio.h>
#include <adapters/gps/ZephyrGPSManager.h>
#include "CompanionMesh.h"
class CompanionCLICallbacks : public CommonCLICallbacks {
@@ -134,42 +133,6 @@ public:
return _mesh.getMeshTimeSync();
}
/* GPS, through the GPS manager as on the repeater. */
bool setGpsEnabled(bool enabled) override {
if (!gps_is_available()) return false;
gps_enable(enabled);
return true;
}
bool isGpsEnabled() const override {
return gps_is_enabled();
}
void formatGpsStatsReply(char* reply) override {
if (!gps_is_enabled()) {
strcpy(reply, "off");
return;
}
struct gps_state_info gsi;
gps_get_state_info(&gsi);
static const char* const state_str[] = { "off", "standby", "acquiring" };
const char* state = gsi.state < 3 ? state_str[gsi.state] : "unknown";
struct gps_position pos;
bool has_pos = gps_get_last_known_position(&pos);
if (has_pos) {
snprintf(reply, CLI_REPLY_SIZE,
"on state=%s sats=%u fix=%us ago lat=%.6f lon=%.6f",
state, gsi.satellites, gsi.last_fix_age_s,
pos.latitude_ndeg / 1e9, pos.longitude_ndeg / 1e9);
} else if (gsi.next_search_s > 0) {
snprintf(reply, CLI_REPLY_SIZE,
"on state=%s sats=%u no fix next=%us",
state, gsi.satellites, gsi.next_search_s);
} else {
snprintf(reply, CLI_REPLY_SIZE,
"on state=%s sats=%u no fix",
state, gsi.satellites);
}
}
/* stats-core / stats-radio / stats-packets, as the repeater prints them. */
void formatStatsReply(char* reply) override {
StatsFormatHelper::formatCoreStats(reply, _board, _ms,
+20 -144
View File
@@ -13,13 +13,12 @@
#include <zephyr/logging/log.h>
#include <zephyr/sys/reboot.h>
#include <ZephyrSensorManager.h>
#include <adapters/sensors/SimpleLPP.h>
#include <math.h>
#if IS_ENABLED(CONFIG_BT)
#include <adapters/ble/ZephyrBLE.h>
#endif
#include <adapters/gps/ZephyrGPSManager.h>
#include <helpers/time_sync.h>
#include <adapters/clock/ZephyrRTCDiscover.h>
#if IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_BUTTON) || IS_ENABLED(CONFIG_ZEPHCORE_UI_DESIGN_JOYSTICK)
#include <ui_task.h>
#define ZEPHCORE_HAS_UI_TASK 1
@@ -1175,148 +1174,29 @@ void CompanionMesh::onChannelDataRecv(const mesh::GroupChannel &channel, mesh::P
pushMsgWaiting();
}
/* Upstream's telemetry (MyMesh::onContactRequest): the battery on the node's
* own channel, the sensors (sensors.querySensors: GPS, then a channel each),
* the MCU temperature on the node's channel. */
int CompanionMesh::appendSelfTelemetry(uint8_t *reply, uint8_t permissions)
{
int i = 0;
const uint8_t CH_SELF = 1;
CayenneLPP telemetry(MAX_PACKET_PAYLOAD - 4);
// Battery voltage: [channel][LPP_VOLTAGE=116][2-byte 0.01V big-endian]
uint16_t batt_mv = _batt_cb ? _batt_cb() : 0;
reply[i++] = CH_SELF;
reply[i++] = 116; // LPP_VOLTAGE
uint16_t batt_scaled = batt_mv / 10;
reply[i++] = (batt_scaled >> 8) & 0xFF;
reply[i++] = batt_scaled & 0xFF;
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)(_batt_cb ? _batt_cb() : 0) / 1000.0f);
sensors.querySensors(permissions, telemetry);
// GPS position if authorized and available
if (permissions & TELEM_PERM_LOCATION) {
struct gps_position pos;
if (gps_is_available() && gps_get_last_known_position(&pos)) {
reply[i++] = CH_SELF;
reply[i++] = 136; // LPP_GPS
int32_t lat = (int32_t)(pos.latitude_ndeg / 100000);
int32_t lon = (int32_t)(pos.longitude_ndeg / 100000);
int32_t alt = pos.altitude_mm / 10;
reply[i++] = (lat >> 16) & 0xFF;
reply[i++] = (lat >> 8) & 0xFF;
reply[i++] = lat & 0xFF;
reply[i++] = (lon >> 16) & 0xFF;
reply[i++] = (lon >> 8) & 0xFF;
reply[i++] = lon & 0xFF;
reply[i++] = (alt >> 16) & 0xFF;
reply[i++] = (alt >> 8) & 0xFF;
reply[i++] = alt & 0xFF;
} else if (prefs.node_lat != 0 || prefs.node_lon != 0) {
// Use configured position
reply[i++] = CH_SELF;
reply[i++] = 136; // LPP_GPS
int32_t lat = (int32_t)(prefs.node_lat * 10000);
int32_t lon = (int32_t)(prefs.node_lon * 10000);
int32_t alt = 0;
reply[i++] = (lat >> 16) & 0xFF;
reply[i++] = (lat >> 8) & 0xFF;
reply[i++] = lat & 0xFF;
reply[i++] = (lon >> 16) & 0xFF;
reply[i++] = (lon >> 8) & 0xFF;
reply[i++] = lon & 0xFF;
reply[i++] = (alt >> 16) & 0xFF;
reply[i++] = (alt >> 8) & 0xFF;
reply[i++] = alt & 0xFF;
}
float temperature = _board ? _board->getMCUTemperature() : NAN;
if (!isnan(temperature)) {
telemetry.addTemperature(TELEM_CHANNEL_SELF, temperature);
}
/* Sensors are read once here. External temp/humidity/pressure require
* TELEM_PERM_ENVIRONMENT, but the MCU die temperature is reported under
* base permission — matching Arduino MeshCore (15e259c5) and ZephCore's
* own repeater/room-server telemetry, neither of which gates it. */
struct env_data env;
bool env_ok = (env_sensors_read(&env) == 0);
bool temp_reported = false;
if (permissions & TELEM_PERM_ENVIRONMENT) {
if (env_ok) {
if (env.has_temperature) {
temp_reported = true;
reply[i++] = CH_SELF;
reply[i++] = LPP_TEMPERATURE;
int16_t temp = (int16_t)(env.temperature_c * 10);
reply[i++] = (temp >> 8) & 0xFF;
reply[i++] = temp & 0xFF;
}
if (env.has_humidity) {
reply[i++] = CH_SELF;
reply[i++] = LPP_RELATIVE_HUMIDITY;
reply[i++] = (uint8_t)(env.humidity_pct * 2);
}
if (env.has_pressure) {
reply[i++] = CH_SELF;
reply[i++] = LPP_BAROMETRIC_PRESSURE;
uint16_t press = (uint16_t)(env.pressure_hpa * 10);
reply[i++] = (press >> 8) & 0xFF;
reply[i++] = press & 0xFF;
}
if (env.has_luminosity) {
reply[i++] = CH_SELF;
reply[i++] = LPP_LUMINOSITY;
float lum = env.luminosity;
if (lum < 0.0f) lum = 0.0f;
if (lum > 65535.0f) lum = 65535.0f;
uint16_t lux = (uint16_t)lum;
reply[i++] = (lux >> 8) & 0xFF;
reply[i++] = lux & 0xFF;
}
}
// Power monitor telemetry (INA219/INA3221/ina2xx)
if (power_sensors_available()) {
struct power_data pwr;
if (power_sensors_read(&pwr) == 0) {
uint8_t ch = CH_SELF + 1;
for (int j = 0; j < pwr.num_channels; j++) {
if (pwr.channels[j].valid) {
// Voltage: [ch][LPP_VOLTAGE=116][2-byte 0.01V signed]
reply[i++] = ch;
reply[i++] = 116;
int16_t v = (int16_t)(pwr.channels[j].voltage_v * 100);
reply[i++] = (v >> 8) & 0xFF;
reply[i++] = v & 0xFF;
// Current: [ch][LPP_CURRENT=117][2-byte 0.001A signed]
// Signed: a bidirectional monitor (INA219) reports discharge
// as negative, and an unsigned cast saturates it to 0.
reply[i++] = ch;
reply[i++] = 117;
int16_t c = (int16_t)(pwr.channels[j].current_a * 1000);
reply[i++] = (c >> 8) & 0xFF;
reply[i++] = c & 0xFF;
// Power: [ch][LPP_POWER=128][2-byte 1W]
reply[i++] = ch;
reply[i++] = 128;
uint16_t p = (uint16_t)(pwr.channels[j].power_w);
reply[i++] = (p >> 8) & 0xFF;
reply[i++] = p & 0xFF;
ch++;
}
}
}
}
}
/* MCU die temperature — reported under base permission, but only when no
* external sensor already supplied a CH_SELF temperature (never emit two). */
if (!temp_reported && env_ok && env.has_mcu_temperature) {
reply[i++] = CH_SELF;
reply[i++] = LPP_TEMPERATURE;
int16_t temp = (int16_t)(env.mcu_temperature_c * 10);
reply[i++] = (temp >> 8) & 0xFF;
reply[i++] = temp & 0xFF;
}
// Trigger GPS wake for fresh fix on next request
if (gps_is_available() && gps_is_enabled()) {
/* ZEPHCORE: wake the GPS so the next request has a fresher fix. */
if ((permissions & TELEM_PERM_LOCATION) && gps_is_enabled()) {
gps_request_fresh_fix();
}
return i;
uint8_t tlen = telemetry.getSize();
memcpy(reply, telemetry.getBuffer(), tlen);
return tlen;
}
uint8_t CompanionMesh::onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp,
@@ -2310,7 +2190,6 @@ bool CompanionMesh::handleCmdSetDeviceTime(const uint8_t *data, size_t len)
if (secs >= curr) {
getRTCClock()->setCurrentTime(secs);
time_sync_report(TIME_SYNC_APP);
zephcore_rtc_save(secs); /* persist to hardware RTC */
_timesync.noteManualSync((uint32_t)(k_uptime_get() / 1000));
writeOKFrame();
/* App just gave us wall time — activate the deferred
@@ -2951,11 +2830,8 @@ bool CompanionMesh::handleCmdSendTelemetryReq(const uint8_t *data, size_t len)
// Self-telemetry request: return battery, GPS, and environment data
// Format: Cayenne LPP: [channel][type][data...]
// Response: [PUSH_CODE_TELEMETRY_RESPONSE][reserved][6-byte pubkey][telemetry_data]
// Worst-case size tracks POWER_MAX_CHANNELS so a future bump can't
// silently overflow this stack buffer. With current value 4:
// header(8) + batt(4) + gps(11) + env(temp4+hum3+press4+lum4=15)
// + power(POWER_MAX_CHANNELS * 12 = 48) + 8 byte safety pad = 94.
uint8_t rsp[8 + 4 + 11 + 15 + (12 * POWER_MAX_CHANNELS) + 8];
// header(8) + at most MAX_PACKET_PAYLOAD - 4 of telemetry.
uint8_t rsp[8 + MAX_PACKET_PAYLOAD - 4];
int i = 0;
rsp[i++] = PUSH_CODE_TELEMETRY_RESPONSE;
rsp[i++] = 0; // reserved
@@ -3308,9 +3184,9 @@ bool CompanionMesh::handleCmdSetCustomVar(const uint8_t *data, size_t len)
} else if (strcmp(key, "gps_interval") == 0) {
uint32_t interval = (uint32_t)atoi(val);
if (interval > 0 && interval <= 86400) {
gps_set_poll_interval_sec(interval);
/* Persist GPS interval across reboots */
prefs.gps_interval = interval;
/* Stored as the GPS will run it (5 s is 10 s). */
prefs.gps_interval = clampGpsInterval(interval);
gps_set_poll_interval_sec(prefs.gps_interval);
_store->savePrefs(prefs);
writeOKFrame();
} else {
+3 -5
View File
@@ -34,11 +34,6 @@
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
/* Telemetry permissions (upstream: helpers/SensorManager.h) */
#define TELEM_PERM_BASE 0x01
#define TELEM_PERM_LOCATION 0x02
#define TELEM_PERM_ENVIRONMENT 0x04
/* Auto-add config bitmask */
#define AUTO_ADD_OVERWRITE_OLDEST (1 << 0)
#define AUTO_ADD_CHAT (1 << 1)
@@ -116,6 +111,8 @@ public:
/* As upstream: every frame to and from the app goes through `serial` */
void startInterface(BaseSerialInterface &serial);
void setBatteryCallback(GetBatteryCallback cb) { _batt_cb = cb; }
/* For the MCU temperature in telemetry (upstream: the global `board`). */
void setBoard(mesh::MainBoard *board) { _board = board; }
void setRadioReconfigureCallback(RadioReconfigureCallback cb) { _radio_reconfig_cb = cb; }
void setPinChangeCallback(PinChangeCallback cb) { _pin_change_cb = cb; }
void setCLICallback(CompanionCLICallback cb) { _cli_exec_cb = cb; }
@@ -277,6 +274,7 @@ private:
ZephyrDataStore *_store;
BaseSerialInterface *_serial;
GetBatteryCallback _batt_cb;
mesh::MainBoard *_board = nullptr;
RadioReconfigureCallback _radio_reconfig_cb;
PinChangeCallback _pin_change_cb;
+3
View File
@@ -155,6 +155,9 @@ bool RepeaterDataStore::loadLegacyPrefs(const char* path, NodePrefs& prefs) {
if (prefs.gps_interval == CONFIG_ZEPHCORE_GPS_POLL_INTERVAL_SEC) {
prefs.gps_interval = CONFIG_ZEPHCORE_REPEATER_GPS_INTERVAL_SEC;
}
/* Likewise gps_enabled: that firmware ran the GPS whatever it said. */
prefs.gps_enabled = 1;
prefs.gps_enabled_set = 1;
return true;
}
+27 -138
View File
@@ -8,8 +8,7 @@
#include <helpers/TxtDataHelpers.h>
#include <helpers/MeshcoreJson.h>
#include <adapters/radio/LoRaRadio.h>
#include <adapters/sensors/SimpleLPP.h>
#include <adapters/sensors/ZephyrEnvSensors.h>
#include <adapters/sensors/ZephyrSensorManager.h>
#include <adapters/gps/ZephyrGPSManager.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
@@ -413,86 +412,33 @@ int RepeaterMesh::handleRequest(ClientInfo* sender, uint32_t sender_timestamp, u
}
if (payload[0] == REQ_TYPE_GET_TELEMETRY_DATA) {
/* CayenneLPP telemetry response using SimpleLPP encoder */
SimpleLPP lpp(&reply_data[4], sizeof(reply_data) - 4);
uint8_t perm_mask = ~(payload[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions
/* Battery voltage — channel 1 = TELEM_CHANNEL_SELF (matches Arduino) */
const uint8_t CH_SELF = 1;
uint16_t batt_mv = _board.getBattMilliVolts();
lpp.addVoltage(CH_SELF, batt_mv / 1000.0f);
telemetry.reset();
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)_board.getBattMilliVolts() / 1000.0f);
/* Environment sensors — prefer external, fallback to MCU die temp */
struct env_data env;
if (env_sensors_read(&env) == 0) {
if (env.has_temperature) {
lpp.addTemperature(CH_SELF, env.temperature_c);
} else if (env.has_mcu_temperature) {
lpp.addTemperature(CH_SELF, env.mcu_temperature_c);
} else {
/* Last resort: MCU temp from board API */
float mcu_temp = _board.getMCUTemperature();
if (!isnan(mcu_temp)) {
lpp.addTemperature(CH_SELF, mcu_temp);
}
}
if (env.has_humidity) {
lpp.addRelativeHumidity(CH_SELF, env.humidity_pct);
}
if (env.has_pressure) {
lpp.addBarometricPressure(CH_SELF, env.pressure_hpa);
}
if (env.has_luminosity) {
lpp.addLuminosity(CH_SELF, env.luminosity);
}
} else {
/* No env sensors at all — try MCU temp directly */
float mcu_temp = _board.getMCUTemperature();
if (!isnan(mcu_temp)) {
lpp.addTemperature(CH_SELF, mcu_temp);
}
// query other sensors -- target specific
if ((sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) {
perm_mask = 0x00; // just base telemetry allowed
}
sensors.querySensors(perm_mask, telemetry);
// This default temperature will be overridden by external sensors (if any)
float temperature = _board.getMCUTemperature();
if(!isnan(temperature)) { // Supported boards with built-in temperature sensor. ESP32-C3 may return NAN
telemetry.addTemperature(TELEM_CHANNEL_SELF, temperature); // Built-in MCU Temperature
}
/* Power monitors (INA219/INA3221/ina2xx) */
if (power_sensors_available()) {
struct power_data pwr;
if (power_sensors_read(&pwr) == 0) {
uint8_t ch = CH_SELF + 1;
for (int j = 0; j < pwr.num_channels; j++) {
if (pwr.channels[j].valid) {
lpp.addVoltage(ch, pwr.channels[j].voltage_v);
lpp.addCurrent(ch, pwr.channels[j].current_a);
lpp.addPower(ch, pwr.channels[j].power_w);
ch++;
}
}
}
// ZEPHCORE: wake the GPS (or extend its acquire window) so the next poll
// has a fresher fix. A server's GPS is normally asleep between its 48 h
// time syncs. Gated like the position, so a guest cannot keep it awake.
if ((perm_mask & TELEM_PERM_LOCATION) && gps_is_enabled()) {
gps_request_fresh_fix();
}
/* GPS precise position — admin-only, and only via telemetry, never
* adverts. Guests get the rest of the LPP payload but no position:
* adverts already publish the operator-set prefs coordinates, so
* there is no reason to hand a guest login the live fix as well. */
if (sender->isAdmin()) {
struct gps_position gpos;
if (gps_get_last_known_position(&gpos)) {
lpp.addGPS(CH_SELF,
(float)(gpos.latitude_ndeg / 1e9),
(float)(gpos.longitude_ndeg / 1e9),
gpos.altitude_mm / 1000.0f);
}
/* Wake GPS / extend acquire window so the next telemetry poll has
* a fresher fix. In repeater mode GPS is normally off between the
* 48h time-sync cycles — this opportunistically rearms acquire
* when someone actually cares about our position. No-op if GPS
* is disabled in prefs. Gated with the position itself so a guest
* can't hold the GPS awake by polling. */
if (gps_is_available() && gps_is_enabled()) {
gps_request_fresh_fix();
}
}
return 4 + lpp.getSize();
uint8_t tlen = telemetry.getSize();
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
return 4 + tlen; // reply_len
}
if (payload[0] == REQ_TYPE_GET_ACCESS_LIST && sender->isAdmin()) {
@@ -1082,7 +1028,7 @@ RepeaterMesh::RepeaterMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Mil
mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(), tables),
_board(board),
_cli(board, rtc, &region_map, &acl, &_prefs, this),
_cli(board, rtc, sensors, &region_map, &acl, &_prefs, this),
region_map(key_store), temp_map(key_store),
discover_limiter(4, 120),
anon_limiter(4, 180),
@@ -1091,7 +1037,8 @@ RepeaterMesh::RepeaterMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Mil
* attempts hit the cap quickly and trip the LOG_WRN below. Global rate
* (not per-sender) — trade-off documented in CRYPTO_AUDIT_INDEX.md
* Phase 4 (mitigation for upstream MeshCore#2556). */
login_fail_limiter(4, 180) {
login_fail_limiter(4, 180),
telemetry(MAX_PACKET_PAYLOAD - 4) {
_store = nullptr;
last_millis = 0;
@@ -1118,6 +1065,8 @@ RepeaterMesh::RepeaterMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Mil
_prefs.advert_loc_policy = ADVERT_LOC_PREFS; // Repeaters always advertise prefs coordinates
_prefs.loop_detect = LOOP_DETECT_MODERATE;
_prefs.gps_interval = CONFIG_ZEPHCORE_REPEATER_GPS_INTERVAL_SEC; // ZEPHCORE: 48 h, a fix for time sync
_prefs.gps_enabled = 1; // ZEPHCORE: GPS on for time sync (upstream: 0)
_prefs.gps_enabled_set = 1;
/* path_hash_mode = 1 moved into initNodePrefs() -- it is the default for
* every role, and a default listed only at one call site is invisible to
* the others (the companion never got it). */
@@ -1546,66 +1495,6 @@ void RepeaterMesh::loop() {
/* ---- ZEPHCORE: methods upstream does not have ---- */
double RepeaterMesh::getNodeLat() const {
struct gps_position pos;
if (gps_get_last_known_position(&pos)) {
return pos.latitude_ndeg / 1e9;
}
return _prefs.node_lat;
}
double RepeaterMesh::getNodeLon() const {
struct gps_position pos;
if (gps_get_last_known_position(&pos)) {
return pos.longitude_ndeg / 1e9;
}
return _prefs.node_lon;
}
bool RepeaterMesh::setGpsEnabled(bool enabled) {
if (!gps_is_available()) return false;
gps_enable(enabled);
return true;
}
bool RepeaterMesh::isGpsEnabled() const {
return gps_is_enabled();
}
void RepeaterMesh::formatGpsStatsReply(char* reply) {
if (!gps_is_enabled()) {
strcpy(reply, "off");
return;
}
struct gps_state_info gsi;
gps_get_state_info(&gsi);
static const char* const state_str[] = { "off", "standby", "acquiring" };
const char* state = gsi.state < 3 ? state_str[gsi.state] : "unknown";
struct gps_position pos;
bool has_pos = gps_get_last_known_position(&pos);
if (has_pos) {
snprintf(reply, CLI_REPLY_SIZE,
"on state=%s sats=%u fix=%us ago lat=%.6f lon=%.6f",
state, gsi.satellites, gsi.last_fix_age_s,
pos.latitude_ndeg / 1e9, pos.longitude_ndeg / 1e9);
} else if (gsi.next_search_s > 0) {
snprintf(reply, CLI_REPLY_SIZE,
"on state=%s sats=%u no fix next=%us",
state, gsi.satellites, gsi.next_search_s);
} else {
snprintf(reply, CLI_REPLY_SIZE,
"on state=%s sats=%u no fix",
state, gsi.satellites);
}
/* Per-constellation tally is deliberately NOT appended here — "get gps"
* has to fit a LoRa reply. It lives in "get gps diag" instead. */
}
void RepeaterMesh::savePrefs() {
if (_store) {
_store->savePrefs(_prefs);
+1 -5
View File
@@ -113,6 +113,7 @@ class RepeaterMesh : public mesh::Mesh, public CommonCLICallbacks {
#if MAX_NEIGHBOURS
NeighbourInfo neighbours[MAX_NEIGHBOURS];
#endif
CayenneLPP telemetry;
unsigned long set_radio_at, revert_radio_at;
float pending_freq;
float pending_bw;
@@ -283,11 +284,6 @@ public:
bool configSideDetectors(const uint8_t* sfs, uint8_t num) override;
// ZEPHCORE: CommonCLICallbacks additions.
double getNodeLat() const override;
double getNodeLon() const override;
bool setGpsEnabled(bool enabled) override;
bool isGpsEnabled() const override;
void formatGpsStatsReply(char* reply) override;
uint32_t getDefaultGpsIntervalSec() const override { return CONFIG_ZEPHCORE_REPEATER_GPS_INTERVAL_SEC; }
void freezeRadioParams(float freq, float bw, uint8_t sf, uint8_t cr) override;
bool setFemRxGain(bool enable) override;
+27 -135
View File
@@ -7,8 +7,7 @@
#include <helpers/TxtDataHelpers.h>
#include <helpers/MeshcoreJson.h>
#include <adapters/radio/LoRaRadio.h>
#include <adapters/sensors/SimpleLPP.h>
#include <adapters/sensors/ZephyrEnvSensors.h>
#include <adapters/sensors/ZephyrSensorManager.h>
#include <adapters/gps/ZephyrGPSManager.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
@@ -222,86 +221,33 @@ int RoomServerMesh::handleRequest(ClientInfo* sender, uint32_t sender_timestamp,
}
if (payload[0] == REQ_TYPE_GET_TELEMETRY_DATA) {
/* CayenneLPP telemetry response using SimpleLPP encoder */
SimpleLPP lpp(&reply_data[4], sizeof(reply_data) - 4);
uint8_t perm_mask = ~(payload[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions
/* Battery voltage — channel 1 = TELEM_CHANNEL_SELF (matches Arduino) */
const uint8_t CH_SELF = 1;
uint16_t batt_mv = _board.getBattMilliVolts();
lpp.addVoltage(CH_SELF, batt_mv / 1000.0f);
telemetry.reset();
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)_board.getBattMilliVolts() / 1000.0f);
/* Environment sensors — prefer external, fallback to MCU die temp */
struct env_data env;
if (env_sensors_read(&env) == 0) {
if (env.has_temperature) {
lpp.addTemperature(CH_SELF, env.temperature_c);
} else if (env.has_mcu_temperature) {
lpp.addTemperature(CH_SELF, env.mcu_temperature_c);
} else {
/* Last resort: MCU temp from board API */
float mcu_temp = _board.getMCUTemperature();
if (!isnan(mcu_temp)) {
lpp.addTemperature(CH_SELF, mcu_temp);
}
}
if (env.has_humidity) {
lpp.addRelativeHumidity(CH_SELF, env.humidity_pct);
}
if (env.has_pressure) {
lpp.addBarometricPressure(CH_SELF, env.pressure_hpa);
}
if (env.has_luminosity) {
lpp.addLuminosity(CH_SELF, env.luminosity);
}
} else {
/* No env sensors at all — try MCU temp directly */
float mcu_temp = _board.getMCUTemperature();
if (!isnan(mcu_temp)) {
lpp.addTemperature(CH_SELF, mcu_temp);
}
// query other sensors -- target specific
if ((sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) {
perm_mask = 0x00; // just base telemetry allowed
}
sensors.querySensors(perm_mask, telemetry);
// This default temperature will be overridden by external sensors (if any)
float temperature = _board.getMCUTemperature();
if(!isnan(temperature)) { // Supported boards with built-in temperature sensor. ESP32-C3 may return NAN
telemetry.addTemperature(TELEM_CHANNEL_SELF, temperature); // Built-in MCU Temperature
}
/* Power monitors (INA219/INA3221/ina2xx) */
if (power_sensors_available()) {
struct power_data pwr;
if (power_sensors_read(&pwr) == 0) {
uint8_t ch = CH_SELF + 1;
for (int j = 0; j < pwr.num_channels; j++) {
if (pwr.channels[j].valid) {
lpp.addVoltage(ch, pwr.channels[j].voltage_v);
lpp.addCurrent(ch, pwr.channels[j].current_a);
lpp.addPower(ch, pwr.channels[j].power_w);
ch++;
}
}
}
// ZEPHCORE: wake the GPS (or extend its acquire window) so the next poll
// has a fresher fix. A server's GPS is normally asleep between its 48 h
// time syncs. Gated like the position, so a guest cannot keep it awake.
if ((perm_mask & TELEM_PERM_LOCATION) && gps_is_enabled()) {
gps_request_fresh_fix();
}
/* GPS precise position — admin-only, and only via telemetry, never
* adverts. Guests get the rest of the LPP payload but no position:
* adverts already publish the operator-set prefs coordinates, so
* there is no reason to hand a guest login the live fix as well. */
if (sender->isAdmin()) {
struct gps_position gpos;
if (gps_get_last_known_position(&gpos)) {
lpp.addGPS(CH_SELF,
(float)(gpos.latitude_ndeg / 1e9),
(float)(gpos.longitude_ndeg / 1e9),
gpos.altitude_mm / 1000.0f);
}
/* Wake GPS / extend acquire window so the next telemetry poll has
* a fresher fix. In repeater mode GPS is normally off between the
* 48h time-sync cycles — this opportunistically rearms acquire
* when someone actually cares about our position. No-op if GPS
* is disabled in prefs. Gated with the position itself so a guest
* can't hold the GPS awake by polling. */
if (gps_is_available() && gps_is_enabled()) {
gps_request_fresh_fix();
}
}
return 4 + lpp.getSize();
uint8_t tlen = telemetry.getSize();
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
return 4 + tlen; // reply_len
}
if (payload[0] == REQ_TYPE_GET_ACCESS_LIST && sender->isAdmin()) {
@@ -748,12 +694,13 @@ RoomServerMesh::RoomServerMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh:
mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(), tables),
_board(board),
_cli(board, rtc, &region_map, &acl, &_prefs, this),
_cli(board, rtc, sensors, &region_map, &acl, &_prefs, this),
region_map(key_store), temp_map(key_store),
/* Failed-login rate limit: 4 wrong-password attempts per 180s. Global
* rate (not per-sender) — trade-off documented in CRYPTO_AUDIT_INDEX.md
* Phase 4 (mitigation for upstream MeshCore#2556). */
login_fail_limiter(4, 180) {
login_fail_limiter(4, 180),
telemetry(MAX_PACKET_PAYLOAD - 4) {
_store = nullptr;
last_millis = 0;
@@ -773,6 +720,8 @@ RoomServerMesh::RoomServerMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh:
/* path_hash_mode = 1 moved into initNodePrefs() -- see RepeaterMesh. */
_prefs.disable_fwd = 1; // a room server is an endpoint, never repeats
_prefs.gps_interval = CONFIG_ZEPHCORE_REPEATER_GPS_INTERVAL_SEC; // ZEPHCORE: as the repeater
_prefs.gps_enabled = 1; // ZEPHCORE: GPS on for time sync (upstream: 0)
_prefs.gps_enabled_set = 1;
/* Room server: circular post buffer + round-robin push state */
next_post_idx = 0;
@@ -1134,63 +1083,6 @@ void RoomServerMesh::loop() {
/* ---- ZEPHCORE: methods upstream does not have ---- */
double RoomServerMesh::getNodeLat() const {
struct gps_position pos;
if (gps_get_last_known_position(&pos)) {
return pos.latitude_ndeg / 1e9;
}
return _prefs.node_lat;
}
double RoomServerMesh::getNodeLon() const {
struct gps_position pos;
if (gps_get_last_known_position(&pos)) {
return pos.longitude_ndeg / 1e9;
}
return _prefs.node_lon;
}
bool RoomServerMesh::setGpsEnabled(bool enabled) {
if (!gps_is_available()) return false;
gps_enable(enabled);
return true;
}
bool RoomServerMesh::isGpsEnabled() const {
return gps_is_enabled();
}
void RoomServerMesh::formatGpsStatsReply(char* reply) {
if (!gps_is_enabled()) {
strcpy(reply, "off");
return;
}
struct gps_state_info gsi;
gps_get_state_info(&gsi);
static const char* const state_str[] = { "off", "standby", "acquiring" };
const char* state = gsi.state < 3 ? state_str[gsi.state] : "unknown";
struct gps_position pos;
bool has_pos = gps_get_last_known_position(&pos);
if (has_pos) {
snprintf(reply, CLI_REPLY_SIZE,
"on state=%s sats=%u fix=%us ago lat=%.6f lon=%.6f",
state, gsi.satellites, gsi.last_fix_age_s,
pos.latitude_ndeg / 1e9, pos.longitude_ndeg / 1e9);
} else if (gsi.next_search_s > 0) {
snprintf(reply, CLI_REPLY_SIZE,
"on state=%s sats=%u no fix next=%us",
state, gsi.satellites, gsi.next_search_s);
} else {
snprintf(reply, CLI_REPLY_SIZE,
"on state=%s sats=%u no fix",
state, gsi.satellites);
}
}
void RoomServerMesh::savePrefs() {
if (_store) {
_store->savePrefs(_prefs);
+1 -5
View File
@@ -99,6 +99,7 @@ class RoomServerMesh : public mesh::Mesh, public CommonCLICallbacks {
int next_client_idx; // for round-robin polling
int next_post_idx;
PostInfo posts[MAX_UNSYNCED_POSTS]; // cyclic queue
CayenneLPP telemetry;
unsigned long set_radio_at, revert_radio_at;
float pending_freq;
float pending_bw;
@@ -230,11 +231,6 @@ public:
void loop();
// ZEPHCORE: CommonCLICallbacks additions.
double getNodeLat() const override;
double getNodeLon() const override;
bool setGpsEnabled(bool enabled) override;
bool isGpsEnabled() const override;
void formatGpsStatsReply(char* reply) override;
uint32_t getDefaultGpsIntervalSec() const override { return CONFIG_ZEPHCORE_REPEATER_GPS_INTERVAL_SEC; }
void freezeRadioParams(float freq, float bw, uint8_t sf, uint8_t cr) override;
bool setFemRxGain(bool enable) override;
+1 -1
View File
@@ -380,7 +380,7 @@ bool CompanionMesh::vcontactHandleFrame(const uint8_t *data, size_t len)
if (tag == 0) tag = 1;
sendPacketSent(MSG_SEND_SENT_DIRECT, tag, 3000);
uint8_t rsp[8 + 4 + 11 + 15 + (12 * POWER_MAX_CHANNELS) + 8];
uint8_t rsp[8 + MAX_PACKET_PAYLOAD - 4]; /* header + telemetry */
int i = 0;
rsp[i++] = PUSH_CODE_TELEMETRY_RESPONSE;
rsp[i++] = 0; /* reserved */
+1
View File
@@ -369,6 +369,7 @@ int main(void)
* to change it, but a unit reflashed from a repeater build keeps the setting
* — and it still drives lora-tx-led on TX-capable boards. */
apply_boot_prefs(prefs, false);
gps_park(); /* unused here; see ZephyrGPSManager.h */
/* Initialize USB serial for CLI */
#if ZEPHCORE_USB_STACK && DT_HAS_COMPAT_STATUS_OKAY(zephyr_cdc_acm_uart)