mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-10-24 04:32:19 +00:00
Add opt-in battery alert flood text
This commit is contained in:
1 parent
c0e3860c3c
commit
f693bdb212
5 files changed
+158
-39
No files matched your search
@@ -126,6 +126,23 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
|
||||
---
|
||||
|
||||
### View or change automatic low-battery alerts to `#repeaters`
|
||||
|
||||
**Usage:**
|
||||
- `get battery.alert`
|
||||
- `set battery.alert <state>`
|
||||
|
||||
**Parameters:**
|
||||
- `state`: `on` (enable) or `off` (disable)
|
||||
|
||||
**Default:** `off`
|
||||
|
||||
**Notes:**
|
||||
- When enabled, sends a `#repeaters` flood text warning if voltage is above `1 V` and the battery estimate is below `20%`.
|
||||
- Warnings repeat every `24` hours, or every `12` hours below `10%`.
|
||||
|
||||
---
|
||||
|
||||
### Get or set recent repeater fallback prefix/SNR
|
||||
**Usage:**
|
||||
- `get recent.repeater`
|
||||
|
||||
@@ -66,6 +66,19 @@
|
||||
#ifndef REPEATERS_CHANNEL_KEY_HEX
|
||||
#define REPEATERS_CHANNEL_KEY_HEX "89db441e2814dccf0dbd2e8cc5f501a3"
|
||||
#endif
|
||||
#ifndef BATT_MIN_MILLIVOLTS
|
||||
#define BATT_MIN_MILLIVOLTS 3000
|
||||
#endif
|
||||
#ifndef BATT_MAX_MILLIVOLTS
|
||||
#define BATT_MAX_MILLIVOLTS 4200
|
||||
#endif
|
||||
|
||||
#define LOW_BATTERY_MIN_VALID_MV 1000
|
||||
#define LOW_BATTERY_WARN_PERCENT 20
|
||||
#define LOW_BATTERY_CRITICAL_PERCENT 10
|
||||
#define LOW_BATTERY_CHECK_INTERVAL (60UL * 1000UL)
|
||||
#define LOW_BATTERY_WARN_INTERVAL (24UL * 60UL * 60UL * 1000UL)
|
||||
#define LOW_BATTERY_CRITICAL_INTERVAL (12UL * 60UL * 60UL * 1000UL)
|
||||
|
||||
static void formatRecentRepeaterPrefix(const SimpleMeshTables::RecentRepeaterInfo* info, char* out, size_t out_len) {
|
||||
if (out == NULL || out_len == 0) {
|
||||
@@ -560,6 +573,17 @@ static bool buildRepeatersChannel(mesh::GroupChannel& channel) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint8_t batteryPercentFromMilliVolts(uint16_t batt_mv) {
|
||||
const int min_mv = BATT_MIN_MILLIVOLTS;
|
||||
const int max_mv = BATT_MAX_MILLIVOLTS;
|
||||
if (max_mv <= min_mv) return 100;
|
||||
|
||||
int pct = (((int)batt_mv - min_mv) * 100) / (max_mv - min_mv);
|
||||
if (pct < 0) return 0;
|
||||
if (pct > 100) return 100;
|
||||
return (uint8_t)pct;
|
||||
}
|
||||
|
||||
mesh::Packet* MyMesh::createPacketCopy(const mesh::Packet* packet, const char* caller) {
|
||||
if (packet == NULL) return NULL;
|
||||
|
||||
@@ -1970,6 +1994,9 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
last_millis = 0;
|
||||
uptime_millis = 0;
|
||||
next_local_advert = next_flood_advert = 0;
|
||||
next_battery_alert_check = 0;
|
||||
last_battery_alert_sent = 0;
|
||||
battery_alert_sent = false;
|
||||
dirty_contacts_expiry = 0;
|
||||
set_radio_at = revert_radio_at = 0;
|
||||
_logging = false;
|
||||
@@ -1996,6 +2023,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.flood_retry_path_gate = 2;
|
||||
_prefs.flood_retry_bridge_enabled = 0;
|
||||
_prefs.flood_retry_advert_enabled = 0;
|
||||
_prefs.battery_alert_enabled = 0;
|
||||
_prefs.direct_retry_cr4_snr_x4 = DIRECT_RETRY_CR4_MIN_SNR_X4_DEFAULT;
|
||||
_prefs.direct_retry_cr5_snr_x4 = DIRECT_RETRY_CR5_MIN_SNR_X4_DEFAULT;
|
||||
_prefs.direct_retry_cr7_snr_x4 = DIRECT_RETRY_CR7_MIN_SNR_X4_DEFAULT;
|
||||
@@ -2114,6 +2142,82 @@ void MyMesh::sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint3
|
||||
}
|
||||
}
|
||||
|
||||
bool MyMesh::sendRepeatersFloodText(const char* text) {
|
||||
if (text == NULL || *text == 0) return false;
|
||||
|
||||
mesh::GroupChannel channel;
|
||||
if (!buildRepeatersChannel(channel)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t temp[MAX_PACKET_PAYLOAD];
|
||||
uint32_t timestamp = getRTCClock()->getCurrentTimeUnique();
|
||||
memcpy(temp, ×tamp, 4);
|
||||
temp[4] = (TXT_TYPE_PLAIN << 2);
|
||||
|
||||
const size_t max_data_len = MAX_PACKET_PAYLOAD - CIPHER_BLOCK_SIZE;
|
||||
const size_t prefix_cap = max_data_len > 5 ? max_data_len - 5 + 1 : 0;
|
||||
int prefix_written = prefix_cap > 0
|
||||
? snprintf((char*)&temp[5], prefix_cap, "%s: ", _prefs.node_name)
|
||||
: -1;
|
||||
if (prefix_written < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t prefix_len = (size_t)prefix_written;
|
||||
if (prefix_len >= prefix_cap) {
|
||||
prefix_len = prefix_cap - 1;
|
||||
}
|
||||
|
||||
size_t text_len = strlen(text);
|
||||
size_t max_text_len = max_data_len - 5 - prefix_len;
|
||||
if (text_len > max_text_len) {
|
||||
text_len = max_text_len;
|
||||
}
|
||||
memcpy(&temp[5 + prefix_len], text, text_len);
|
||||
|
||||
auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, channel, temp, 5 + prefix_len + text_len);
|
||||
if (pkt == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sendFloodScoped(default_scope, pkt, 0, _prefs.path_hash_mode + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MyMesh::checkBatteryAlert() {
|
||||
if (!_prefs.battery_alert_enabled) {
|
||||
battery_alert_sent = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (next_battery_alert_check && !millisHasNowPassed(next_battery_alert_check)) {
|
||||
return;
|
||||
}
|
||||
next_battery_alert_check = futureMillis(LOW_BATTERY_CHECK_INTERVAL);
|
||||
|
||||
uint16_t batt_mv = board.getBattMilliVolts();
|
||||
uint8_t batt_pct = batteryPercentFromMilliVolts(batt_mv);
|
||||
if (batt_mv <= LOW_BATTERY_MIN_VALID_MV || batt_pct >= LOW_BATTERY_WARN_PERCENT) {
|
||||
battery_alert_sent = false;
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned long interval = batt_pct < LOW_BATTERY_CRITICAL_PERCENT
|
||||
? LOW_BATTERY_CRITICAL_INTERVAL
|
||||
: LOW_BATTERY_WARN_INTERVAL;
|
||||
if (battery_alert_sent && !millisHasNowPassed(last_battery_alert_sent + interval)) {
|
||||
return;
|
||||
}
|
||||
|
||||
char text[96];
|
||||
snprintf(text, sizeof(text), "LOW BATTERY %u%% (%u mV)", (uint32_t)batt_pct, (uint32_t)batt_mv);
|
||||
if (sendRepeatersFloodText(text)) {
|
||||
battery_alert_sent = true;
|
||||
last_battery_alert_sent = millis();
|
||||
}
|
||||
}
|
||||
|
||||
void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) {
|
||||
set_radio_at = futureMillis(2000); // give CLI reply some time to be sent back, before applying temp radio params
|
||||
pending_freq = freq;
|
||||
@@ -2519,45 +2623,27 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char *
|
||||
char* text = trimSpaces(command + 16);
|
||||
if (*text == 0) {
|
||||
strcpy(reply, "Err - usage: send text.flood <message>");
|
||||
} else if (sendRepeatersFloodText(text)) {
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
mesh::GroupChannel channel;
|
||||
if (!buildRepeatersChannel(channel)) {
|
||||
strcpy(reply, "Err - invalid #repeaters key");
|
||||
} else {
|
||||
uint8_t temp[MAX_PACKET_PAYLOAD];
|
||||
uint32_t timestamp = getRTCClock()->getCurrentTimeUnique();
|
||||
memcpy(temp, ×tamp, 4);
|
||||
temp[4] = (TXT_TYPE_PLAIN << 2);
|
||||
|
||||
const size_t max_data_len = MAX_PACKET_PAYLOAD - CIPHER_BLOCK_SIZE;
|
||||
const size_t prefix_cap = max_data_len > 5 ? max_data_len - 5 + 1 : 0;
|
||||
int prefix_written = prefix_cap > 0
|
||||
? snprintf((char*)&temp[5], prefix_cap, "%s: ", _prefs.node_name)
|
||||
: -1;
|
||||
if (prefix_written < 0) {
|
||||
strcpy(reply, "Err - unable to create message");
|
||||
} else {
|
||||
size_t prefix_len = (size_t)prefix_written;
|
||||
if (prefix_len >= prefix_cap) {
|
||||
prefix_len = prefix_cap - 1;
|
||||
}
|
||||
|
||||
size_t text_len = strlen(text);
|
||||
size_t max_text_len = max_data_len - 5 - prefix_len;
|
||||
if (text_len > max_text_len) {
|
||||
text_len = max_text_len;
|
||||
}
|
||||
memcpy(&temp[5 + prefix_len], text, text_len);
|
||||
|
||||
auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, channel, temp, 5 + prefix_len + text_len);
|
||||
if (pkt) {
|
||||
sendFloodScoped(default_scope, pkt, 0, _prefs.path_hash_mode + 1);
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Err - unable to create packet");
|
||||
}
|
||||
}
|
||||
}
|
||||
strcpy(reply, "Err - unable to create packet");
|
||||
}
|
||||
} else if (strcmp(command, "get battery.alert") == 0) {
|
||||
sprintf(reply, "> %s", _prefs.battery_alert_enabled ? "on" : "off");
|
||||
} else if (strncmp(command, "set battery.alert ", 18) == 0) {
|
||||
const char* value = command + 18;
|
||||
if (strcmp(value, "on") == 0) {
|
||||
_prefs.battery_alert_enabled = 1;
|
||||
next_battery_alert_check = 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (strcmp(value, "off") == 0) {
|
||||
_prefs.battery_alert_enabled = 0;
|
||||
battery_alert_sent = false;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Err - usage: set battery.alert <on|off>");
|
||||
}
|
||||
} else if (strncmp(command, "get recent.repeater", 19) == 0
|
||||
|| strncmp(command, "set recent.repeater", 19) == 0
|
||||
@@ -2759,6 +2845,7 @@ void MyMesh::loop() {
|
||||
#endif
|
||||
|
||||
mesh::Mesh::loop();
|
||||
checkBatteryAlert();
|
||||
|
||||
if (next_flood_advert && millisHasNowPassed(next_flood_advert)) {
|
||||
mesh::Packet *pkt = createSelfAdvert();
|
||||
|
||||
@@ -85,6 +85,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
uint32_t last_millis;
|
||||
uint64_t uptime_millis;
|
||||
unsigned long next_local_advert, next_flood_advert;
|
||||
unsigned long next_battery_alert_check;
|
||||
unsigned long last_battery_alert_sent;
|
||||
bool battery_alert_sent;
|
||||
bool _logging;
|
||||
NodePrefs _prefs;
|
||||
ClientACL acl;
|
||||
@@ -163,6 +166,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
uint8_t handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
|
||||
int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len);
|
||||
mesh::Packet* createSelfAdvert();
|
||||
bool sendRepeatersFloodText(const char* text);
|
||||
void checkBatteryAlert();
|
||||
|
||||
File openAppend(const char* fname);
|
||||
bool isLooped(const mesh::Packet* packet, const uint8_t max_counters[]);
|
||||
|
||||
@@ -556,6 +556,9 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
sizeof(_prefs->direct_retry_cr7_snr_x4)); // 662
|
||||
retry_cr_read += file.read((uint8_t *)&_prefs->direct_retry_cr8_snr_x4,
|
||||
sizeof(_prefs->direct_retry_cr8_snr_x4)); // 663
|
||||
_prefs->battery_alert_enabled = 0;
|
||||
size_t battery_alert_read = file.read((uint8_t *)&_prefs->battery_alert_enabled,
|
||||
sizeof(_prefs->battery_alert_enabled)); // 664
|
||||
// PowerSaving-only prefs stored radio_fem_rxgain at 291, before direct retry timing existed.
|
||||
if (radio_fem_rxgain_read != sizeof(_prefs->radio_fem_rxgain)
|
||||
&& legacy_retry_attempts_read == sizeof(legacy_retry_attempts_or_radio_fem_rxgain)
|
||||
@@ -654,6 +657,11 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
} else {
|
||||
sanitizeDirectRetryCrThresholds(_prefs);
|
||||
}
|
||||
if (battery_alert_read != sizeof(_prefs->battery_alert_enabled)) {
|
||||
_prefs->battery_alert_enabled = 0;
|
||||
} else {
|
||||
_prefs->battery_alert_enabled = constrain(_prefs->battery_alert_enabled, 0, 1);
|
||||
}
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -739,7 +747,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
file.write((uint8_t *)&_prefs->direct_retry_cr5_snr_x4, sizeof(_prefs->direct_retry_cr5_snr_x4)); // 661
|
||||
file.write((uint8_t *)&_prefs->direct_retry_cr7_snr_x4, sizeof(_prefs->direct_retry_cr7_snr_x4)); // 662
|
||||
file.write((uint8_t *)&_prefs->direct_retry_cr8_snr_x4, sizeof(_prefs->direct_retry_cr8_snr_x4)); // 663
|
||||
// next: 664
|
||||
file.write((uint8_t *)&_prefs->battery_alert_enabled, sizeof(_prefs->battery_alert_enabled)); // 664
|
||||
// next: 665
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@ struct NodePrefs { // persisted to file
|
||||
int8_t direct_retry_cr5_snr_x4;
|
||||
int8_t direct_retry_cr7_snr_x4;
|
||||
int8_t direct_retry_cr8_snr_x4;
|
||||
uint8_t battery_alert_enabled;
|
||||
};
|
||||
|
||||
class CommonCLICallbacks {
|
||||
|
||||
Reference in new issue
Block a user