mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-10-23 02:41:27 +00:00
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:
1 parent
e6d6ecef80
commit
d5bd805710
59 files changed
+3090
-3077
No files matched your search
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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, ®ion_map, &acl, &_prefs, this),
|
||||
_cli(board, rtc, sensors, ®ion_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);
|
||||
|
||||
@@ -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
@@ -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, ®ion_map, &acl, &_prefs, this),
|
||||
_cli(board, rtc, sensors, ®ion_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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in new issue
Block a user