Merge pull request #14 from meshcore-dev/dev

merge Dev
This commit is contained in:
Quency-D
2026-06-26 17:16:23 +08:00
committed by GitHub
121 changed files with 2469 additions and 951 deletions
+43 -23
View File
@@ -67,24 +67,38 @@ void BaseChatMesh::bootstrapRTCfromContacts() {
}
}
ContactInfo* BaseChatMesh::allocateContactSlot() {
if (num_contacts < MAX_CONTACTS) {
return &contacts[num_contacts++];
} else if (shouldOverwriteWhenFull()) {
// Find oldest non-favourite contact by oldest lastmod timestamp
int oldest_idx = -1;
uint32_t oldest_lastmod = 0xFFFFFFFF;
for (int i = 0; i < num_contacts; i++) {
bool is_favourite = (contacts[i].flags & 0x01) != 0;
if (!is_favourite && contacts[i].lastmod < oldest_lastmod) {
ContactInfo* BaseChatMesh::allocateContactSlot(bool transient_only) {
int oldest_idx = -1;
uint32_t oldest_lastmod = 0xFFFFFFFF;
if (transient_only) {
// only allocate from first N
for (int i = 0; i < MAX_ANON_CONTACTS; i++) {
if (contacts[i].type == ADV_TYPE_NONE && contacts[i].lastmod < oldest_lastmod) {
oldest_lastmod = contacts[i].lastmod;
oldest_idx = i;
}
}
if (oldest_idx >= 0) {
onContactOverwrite(contacts[oldest_idx].id.pub_key);
// NOTE: do NOT call onContactOverwrite()
return &contacts[oldest_idx];
}
} else {
if (num_contacts < MAX_ANON_CONTACTS+MAX_CONTACTS) {
return &contacts[num_contacts++];
} else if (shouldOverwriteWhenFull()) {
// Find oldest non-favourite contact by oldest lastmod timestamp
for (int i = MAX_ANON_CONTACTS; i < num_contacts; i++) {
bool is_favourite = (contacts[i].flags & 0x01) != 0;
if (!is_favourite && contacts[i].lastmod < oldest_lastmod) {
oldest_lastmod = contacts[i].lastmod;
oldest_idx = i;
}
}
if (oldest_idx >= 0) {
onContactOverwrite(contacts[oldest_idx].id.pub_key);
return &contacts[oldest_idx];
}
}
}
return NULL; // no space, no overwrite or all contacts are all favourites
}
@@ -132,6 +146,11 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
packet->header = save;
}
if (from && from->type == ADV_TYPE_NONE) { // already in contacts, but from a temporary ANON_REQ ?
memset(from, 0, sizeof(*from)); // clear the anon/temp slot
from = NULL; // do normal 'add' flow
}
bool is_new = false; // true = not in contacts[], false = exists in contacts[]
if (from == NULL) {
if (!shouldAutoAddContactType(parser.getType())) {
@@ -164,16 +183,17 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
from->sync_since = 0;
from->shared_secret_valid = false;
}
// update
putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen);
StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name));
from->type = parser.getType();
if (parser.hasLatLon()) {
from->gps_lat = parser.getIntLat();
from->gps_lon = parser.getIntLon();
}
from->last_advert_timestamp = timestamp;
from->lastmod = getRTCClock()->getCurrentTime();
putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen);
StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name));
from->type = parser.getType();
if (parser.hasLatLon()) {
from->gps_lat = parser.getIntLat();
from->gps_lon = parser.getIntLon();
}
from->last_advert_timestamp = timestamp;
from->lastmod = getRTCClock()->getCurrentTime();
onDiscoveredContact(*from, is_new, packet->path_len, packet->path); // let UI know
}
@@ -829,7 +849,7 @@ ContactInfo* BaseChatMesh::lookupContactByPubKey(const uint8_t* pub_key, int pre
}
bool BaseChatMesh::addContact(const ContactInfo& contact) {
ContactInfo* dest = allocateContactSlot();
ContactInfo* dest = allocateContactSlot(contact.type == ADV_TYPE_NONE);
if (dest) {
*dest = contact;
dest->shared_secret_valid = false; // mark shared_secret as needing calculation
@@ -922,11 +942,11 @@ bool BaseChatMesh::getContactByIdx(uint32_t idx, ContactInfo& contact) {
}
ContactsIterator BaseChatMesh::startContactsIterator() {
return ContactsIterator();
return ContactsIterator(MAX_ANON_CONTACTS); // start at offset, skip the anon entries
}
bool ContactsIterator::hasNext(const BaseChatMesh* mesh, ContactInfo& dest) {
if (next_idx >= mesh->getNumContacts()) return false;
if (next_idx >= mesh->getTotalContactSlots()) return false;
dest = mesh->contacts[next_idx++];
return true;
+17 -8
View File
@@ -28,8 +28,9 @@ public:
class BaseChatMesh;
class ContactsIterator {
int next_idx = 0;
int next_idx;
public:
ContactsIterator(int start) { next_idx = start; }
bool hasNext(const BaseChatMesh* mesh, ContactInfo& dest);
};
@@ -37,6 +38,8 @@ public:
#define MAX_CONTACTS 32
#endif
#define MAX_ANON_CONTACTS 8
#ifndef MAX_CONNECTIONS
#define MAX_CONNECTIONS 16
#endif
@@ -58,9 +61,9 @@ class BaseChatMesh : public mesh::Mesh {
friend class ContactsIterator;
ContactInfo contacts[MAX_CONTACTS];
ContactInfo contacts[MAX_CONTACTS+MAX_ANON_CONTACTS];
int num_contacts;
int sort_array[MAX_CONTACTS];
int sort_array[MAX_CONTACTS+MAX_ANON_CONTACTS];
int matching_peer_indexes[MAX_SEARCH_RESULTS];
unsigned long txt_send_timeout;
#ifdef MAX_GROUP_CHANNELS
@@ -77,8 +80,9 @@ class BaseChatMesh : public mesh::Mesh {
protected:
BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables)
: mesh::Mesh(radio, ms, rng, rtc, mgr, tables)
{
num_contacts = 0;
{
resetContacts();
#ifdef MAX_GROUP_CHANNELS
memset(channels, 0, sizeof(channels));
num_channels = 0;
@@ -89,9 +93,13 @@ protected:
}
void bootstrapRTCfromContacts();
void resetContacts() { num_contacts = 0; }
void resetContacts() {
memset(contacts, 0, sizeof(contacts[0])*MAX_ANON_CONTACTS); // set all to have type = ADV_TYPE_NONE(0)
num_contacts = MAX_ANON_CONTACTS; // seed the first contacts for anon requests
}
void populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp);
ContactInfo* allocateContactSlot(); // helper to find slot for new contact
ContactInfo* allocateContactSlot(bool transient_only=false); // helper to find slot for new contact
// 'UI' concepts, for sub-classes to implement
virtual bool isAutoAddEnabled() const { return true; }
@@ -164,7 +172,8 @@ public:
ContactInfo* lookupContactByPubKey(const uint8_t* pub_key, int prefix_len);
bool removeContact(ContactInfo& contact);
bool addContact(const ContactInfo& contact);
int getNumContacts() const { return num_contacts; }
int getTotalContactSlots() const { return num_contacts; }
int getNumContacts() const { return num_contacts - MAX_ANON_CONTACTS; } // don't include the reserved slots at start
bool getContactByIdx(uint32_t idx, ContactInfo& contact);
ContactsIterator startContactsIterator();
ChannelDetails* addChannel(const char* name, const char* psk_base64);
+1 -1
View File
@@ -2,7 +2,7 @@
#include <Arduino.h>
#define MAX_FRAME_SIZE 172
#define MAX_FRAME_SIZE 176 // +4 for transport codes (region scoping)
class BaseSerialInterface {
protected:
+85 -13
View File
@@ -88,8 +88,12 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
// next: 291
file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
file.read((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291
file.read((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 292
file.read((uint8_t *)&_prefs->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); // 293
file.read((uint8_t *)&_prefs->cad_enabled, sizeof(_prefs->cad_enabled)); // 294
// next: 295
// sanitise bad pref values
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
@@ -119,6 +123,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
// sanitise settings
_prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean
_prefs->radio_fem_rxgain = constrain(_prefs->radio_fem_rxgain, 0, 1); // boolean
_prefs->cad_enabled = constrain(_prefs->cad_enabled, 0, 1); // boolean
file.close();
}
@@ -179,8 +185,12 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
// next: 291
file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
file.write((uint8_t *)&_prefs->flood_max_unscoped, sizeof(_prefs->flood_max_unscoped)); // 291
file.write((uint8_t *)&_prefs->flood_max_advert, sizeof(_prefs->flood_max_advert)); // 292
file.write((uint8_t *)&_prefs->radio_fem_rxgain, sizeof(_prefs->radio_fem_rxgain)); // 293
file.write((uint8_t *)&_prefs->cad_enabled, sizeof(_prefs->cad_enabled)); // 294
// next: 295
file.close();
}
@@ -428,13 +438,23 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
}
#endif
} else if (memcmp(command, "powersaving on", 14) == 0) {
#if defined(NRF52_PLATFORM)
_prefs->powersaving_enabled = 1;
savePrefs();
strcpy(reply, "ok"); // TODO: to return Not supported if required
strcpy(reply, "on - Immediate effect");
#elif defined(ESP32) && !defined(WITH_BRIDGE)
_prefs->powersaving_enabled = 1;
savePrefs();
strcpy(reply, "on - After 2 minutes");
#elif defined(WITH_BRIDGE)
strcpy(reply, "Bridge not supported");
#else
strcpy(reply, "Board not supported");
#endif
} else if (memcmp(command, "powersaving off", 15) == 0) {
_prefs->powersaving_enabled = 0;
savePrefs();
strcpy(reply, "ok");
strcpy(reply, "off");
} else if (memcmp(command, "powersaving", 11) == 0) {
if (_prefs->powersaving_enabled) {
strcpy(reply, "on");
@@ -486,6 +506,10 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
_prefs->interference_threshold = atoi(&config[11]);
savePrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "cad ", 4) == 0) {
_prefs->cad_enabled = memcmp(&config[4], "on", 2) == 0;
savePrefs();
strcpy(reply, "OK");
} else if (memcmp(config, "agc.reset.interval ", 19) == 0) {
_prefs->agc_reset_interval = atoi(&config[19]) / 4;
savePrefs();
@@ -554,6 +578,28 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
savePrefs();
_callbacks->setRxBoostedGain(_prefs->rx_boosted_gain);
#endif
} else if (memcmp(config, "radio.fem.rxgain ", 17) == 0) {
if (!_board->canControlLoRaFemLna()) {
strcpy(reply, "Error: unsupported");
} else if (memcmp(&config[17], "on", 2) == 0) {
if (_board->setLoRaFemLnaEnabled(true)) {
_prefs->radio_fem_rxgain = 1;
savePrefs();
strcpy(reply, "OK - LoRa FEM RX gain on");
} else {
strcpy(reply, "Error: failed to apply LoRa FEM RX gain");
}
} else if (memcmp(&config[17], "off", 3) == 0) {
if (_board->setLoRaFemLnaEnabled(false)) {
_prefs->radio_fem_rxgain = 0;
savePrefs();
strcpy(reply, "OK - LoRa FEM RX gain off");
} else {
strcpy(reply, "Error: failed to apply LoRa FEM RX gain");
}
} else {
strcpy(reply, "Error: state must be on or off");
}
} else if (memcmp(config, "radio ", 6) == 0) {
strcpy(tmp, &config[6]);
const char *parts[4];
@@ -598,6 +644,24 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
} else {
strcpy(reply, "Error, must be 0-2");
}
} else if (memcmp(config, "flood.max.unscoped ", 19) == 0) {
uint8_t m = atoi(&config[19]);
if (m <= 64) {
_prefs->flood_max_unscoped = m;
savePrefs();
strcpy(reply, "OK");
} else {
strcpy(reply, "Error, max 64");
}
} else if (memcmp(config, "flood.max.advert ", 17) == 0) {
uint8_t m = atoi(&config[17]);
if (m <= 64) {
_prefs->flood_max_advert = m;
savePrefs();
strcpy(reply, "OK");
} else {
strcpy(reply, "Error, max 64");
}
} else if (memcmp(config, "flood.max ", 10) == 0) {
uint8_t m = atoi(&config[10]);
if (m <= 64) {
@@ -725,7 +789,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
}
} else {
_prefs->adc_multiplier = 0.0f;
strcpy(reply, "Error: unsupported by this board");
strcpy(reply, "Error: unsupported");
};
} else {
strcpy(reply, "unknown config: ");
@@ -744,6 +808,8 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->airtime_factor));
} else if (memcmp(config, "int.thresh", 10) == 0) {
sprintf(reply, "> %d", (uint32_t) _prefs->interference_threshold);
} else if (memcmp(config, "cad", 3) == 0) {
sprintf(reply, "> %s", _prefs->cad_enabled ? "on" : "off");
} else if (memcmp(config, "agc.reset.interval", 18) == 0) {
sprintf(reply, "> %d", ((uint32_t) _prefs->agc_reset_interval) * 4);
} else if (memcmp(config, "multi.acks", 10) == 0) {
@@ -773,6 +839,12 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
} else if (memcmp(config, "radio.rxgain", 12) == 0) {
sprintf(reply, "> %s", _prefs->rx_boosted_gain ? "on" : "off");
#endif
} else if (memcmp(config, "radio.fem.rxgain", 16) == 0) {
if (!_board->canControlLoRaFemLna()) {
strcpy(reply, "Error: unsupported");
} else {
sprintf(reply, "> %s", _board->isLoRaFemLnaEnabled() ? "on" : "off");
}
} else if (memcmp(config, "radio", 5) == 0) {
char freq[16], bw[16];
strcpy(freq, StrHelper::ftoa(_prefs->freq));
@@ -782,6 +854,10 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->rx_delay_base));
} else if (memcmp(config, "txdelay", 7) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->tx_delay_factor));
} else if (memcmp(config, "flood.max.advert", 16) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->flood_max_advert);
} else if (memcmp(config, "flood.max.unscoped", 18) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->flood_max_unscoped);
} else if (memcmp(config, "flood.max", 9) == 0) {
sprintf(reply, "> %d", (uint32_t)_prefs->flood_max);
} else if (memcmp(config, "direct.txdelay", 14) == 0) {
@@ -854,12 +930,12 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
strcpy(reply, "> unknown");
}
#else
strcpy(reply, "ERROR: unsupported");
strcpy(reply, "Error: unsupported");
#endif
} else if (memcmp(config, "adc.multiplier", 14) == 0) {
float adc_mult = _board->getAdcMultiplier();
if (adc_mult == 0.0f) {
strcpy(reply, "Error: unsupported by this board");
strcpy(reply, "Error: unsupported");
} else {
sprintf(reply, "> %.3f", adc_mult);
}
@@ -877,13 +953,9 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
strcpy(reply, "ERROR: Power management not supported");
#endif
} else if (memcmp(config, "pwrmgt.bootreason", 17) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
sprintf(reply, "> Reset: %s; Shutdown: %s",
_board->getResetReasonString(_board->getResetReason()),
_board->getShutdownReasonString(_board->getShutdownReason()));
#else
strcpy(reply, "ERROR: Power management not supported");
#endif
} else if (memcmp(config, "pwrmgt.bootmv", 13) == 0) {
#ifdef NRF52_POWER_MANAGEMENT
sprintf(reply, "> %u mV", _board->getBootVoltage());
+4
View File
@@ -40,6 +40,8 @@ struct NodePrefs { // persisted to file
uint8_t multi_acks;
float bw;
uint8_t flood_max;
uint8_t flood_max_unscoped;
uint8_t flood_max_advert;
uint8_t interference_threshold;
uint8_t agc_reset_interval; // secs / 4
// Bridge settings
@@ -59,8 +61,10 @@ struct NodePrefs { // persisted to file
float adc_multiplier;
char owner_info[120];
uint8_t rx_boosted_gain; // power settings
uint8_t radio_fem_rxgain; // LoRa FEM RX gain setting
uint8_t path_hash_mode; // which path mode to use when sending
uint8_t loop_detect;
uint8_t cad_enabled; // hardware Channel Activity Detection before TX (boolean)
};
class CommonCLICallbacks {
+84 -23
View File
@@ -12,17 +12,19 @@
#include <rom/rtc.h>
#include <sys/time.h>
#include <Wire.h>
#include "driver/rtc_io.h"
#include "soc/rtc.h"
#include "esp_system.h"
class ESP32Board : public mesh::MainBoard {
protected:
uint8_t startup_reason;
bool inhibit_sleep = false;
static inline portMUX_TYPE sleepMux = portMUX_INITIALIZER_UNLOCKED;
public:
void begin() {
// for future use, sub-classes SHOULD call this from their begin()
startup_reason = BD_STARTUP_NORMAL;
startup_reason = BD_STARTUP_NORMAL;
#ifdef ESP32_CPU_FREQ
setCpuFrequencyMhz(ESP32_CPU_FREQ);
@@ -45,7 +47,7 @@ public:
#endif
#else
Wire.begin();
#endif
#endif
}
// Temperature from ESP32 MCU
@@ -60,25 +62,48 @@ public:
return raw / 4;
}
void enterLightSleep(uint32_t secs) {
#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(P_LORA_DIO_1) // Supported ESP32 variants
if (rtc_gpio_is_valid_gpio((gpio_num_t)P_LORA_DIO_1)) { // Only enter sleep mode if P_LORA_DIO_1 is RTC pin
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // To wake up when receiving a LoRa packet
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000); // To wake up every hour to do periodically jobs
}
esp_light_sleep_start(); // CPU enters light sleep
}
#endif
uint32_t getIRQGpio() override {
return P_LORA_DIO_1; // default for SX1262
}
void sleep(uint32_t secs) override {
if (!inhibit_sleep) {
enterLightSleep(secs); // To wake up after "secs" seconds or when receiving a LoRa packet
// Skip if not allow to sleep
if (inhibit_sleep) {
delay(1); // Give MCU to OTA to run
return;
}
// Set GPIO wakeup
gpio_num_t wakeupPin = (gpio_num_t)getIRQGpio();
// Configure timer wakeup
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000ULL); // Wake up periodically to do scheduled jobs
}
// Disable CPU interrupt servicing
portENTER_CRITICAL(&sleepMux);
// Skip sleep if there is a LoRa packet
if (gpio_get_level(wakeupPin) == HIGH) {
portEXIT_CRITICAL(&sleepMux);
delay(1);
return;
}
// Configure GPIO wakeup
esp_sleep_enable_gpio_wakeup();
gpio_wakeup_enable((gpio_num_t)wakeupPin, GPIO_INTR_HIGH_LEVEL); // Wake up when receiving a LoRa packet
// MCU enters light sleep
esp_light_sleep_start();
// Avoid ISR flood during wakeup due to HIGH LEVEL interrupt
gpio_wakeup_disable(wakeupPin);
gpio_set_intr_type(wakeupPin, GPIO_INTR_POSEDGE);
// Enable CPU interrupt servicing
portEXIT_CRITICAL(&sleepMux);
}
uint8_t getStartupReason() const override { return startup_reason; }
@@ -102,7 +127,7 @@ public:
#endif
uint16_t getBattMilliVolts() override {
#ifdef PIN_VBAT_READ
#ifdef PIN_VBAT_READ
analogReadResolution(12);
uint32_t raw = 0;
@@ -130,6 +155,42 @@ public:
void setInhibitSleep(bool inhibit) {
inhibit_sleep = inhibit;
}
uint32_t getResetReason() const override {
return esp_reset_reason();
}
// https://docs.espressif.com/projects/esp-idf/en/v4.4.7/esp32/api-reference/system/system.html
const char* getResetReasonString(uint32_t reason) {
switch (reason) {
case ESP_RST_UNKNOWN:
return "Unknown or first boot";
case ESP_RST_POWERON:
return "Power-on reset";
case ESP_RST_EXT:
return "External reset";
case ESP_RST_SW:
return "Software reset";
case ESP_RST_PANIC:
return "Panic / exception reset";
case ESP_RST_INT_WDT:
return "Interrupt watchdog reset";
case ESP_RST_TASK_WDT:
return "Task watchdog reset";
case ESP_RST_WDT:
return "Other watchdog reset";
case ESP_RST_DEEPSLEEP:
return "Wake from deep sleep";
case ESP_RST_BROWNOUT:
return "Brownout (low voltage)";
case ESP_RST_SDIO:
return "SDIO reset";
default:
static char buf[40];
snprintf(buf, sizeof(buf), "Unknown reset reason (%d)", reason);
return buf;
}
}
};
class ESP32RTCClock : public mesh::RTCClock {
@@ -141,16 +202,16 @@ public:
// start with some date/time in the recent past
struct timeval tv;
tv.tv_sec = 1715770351; // 15 May 2024, 8:50pm
tv.tv_usec = 0;
settimeofday(&tv, NULL);
}
tv.tv_usec = 0;
settimeofday(&tv, NULL);
}
}
uint32_t getCurrentTime() override {
time_t _now;
time(&_now);
return _now;
}
void setCurrentTime(uint32_t time) override {
void setCurrentTime(uint32_t time) override {
struct timeval tv;
tv.tv_sec = time;
tv.tv_usec = 0;
+14 -14
View File
@@ -66,20 +66,6 @@ void NRF52Board::initPowerMgr() {
}
}
bool NRF52Board::isExternalPowered() {
// Check if SoftDevice is enabled before using its API
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) {
uint32_t usb_status;
sd_power_usbregstatus_get(&usb_status);
return (usb_status & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0;
} else {
return (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0;
}
}
const char* NRF52Board::getResetReasonString(uint32_t reason) {
if (reason & POWER_RESETREAS_RESETPIN_Msk) return "Reset Pin";
if (reason & POWER_RESETREAS_DOG_Msk) return "Watchdog";
@@ -251,6 +237,20 @@ void NRF52BoardDCDC::begin() {
}
}
bool NRF52Board::isExternalPowered() {
// Check if SoftDevice is enabled before using its API
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) {
uint32_t usb_status;
sd_power_usbregstatus_get(&usb_status);
return (usb_status & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0;
} else {
return (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0;
}
}
void NRF52Board::sleep(uint32_t secs) {
// Clear FPU interrupt flags to avoid insomnia
// see errata 87 for details https://docs.nordicsemi.com/bundle/errata_nRF52840_Rev3/page/ERR/nRF52840/Rev3/latest/anomaly_840_87.html
+1 -1
View File
@@ -53,9 +53,9 @@ public:
virtual bool getBootloaderVersion(char* version, size_t max_len) override;
virtual bool startOTAUpdate(const char *id, char reply[]) override;
virtual void sleep(uint32_t secs) override;
bool isExternalPowered() override;
#ifdef NRF52_POWER_MANAGEMENT
bool isExternalPowered() override;
uint16_t getBootVoltage() override { return boot_voltage_mv; }
virtual uint32_t getResetReason() const override { return reset_reason; }
uint8_t getShutdownReason() const override { return shutdown_reason; }
+7 -7
View File
@@ -59,13 +59,13 @@
// uint32_t P_LORA_BUSY = 0; //shared, so define at run
// uint32_t P_LORA_DIO_2 = 0; //SX1276 only, so define at run
#define P_LORA_DIO_0 26
#define P_LORA_DIO_1 33
#define P_LORA_NSS 18
#define P_LORA_RESET 23
#define P_LORA_SCLK 5
#define P_LORA_MISO 19
#define P_LORA_MOSI 27
// #define P_LORA_DIO_0 26
// #define P_LORA_DIO_1 33
// #define P_LORA_NSS 18
// #define P_LORA_RESET 23
// #define P_LORA_SCLK 5
// #define P_LORA_MISO 19
// #define P_LORA_MOSI 27
// #define PIN_GPS_RX 34
// #define PIN_GPS_TX 12
+20 -3
View File
@@ -36,6 +36,7 @@ void RadioLibWrapper::begin() {
_noise_floor = 0;
_threshold = 0;
_cad_enabled = false;
// start average out some samples
_num_floor_samples = 0;
@@ -178,10 +179,26 @@ void RadioLibWrapper::onSendFinished() {
state = STATE_IDLE;
}
int16_t RadioLibWrapper::performChannelScan() {
return _radio->scanChannel();
}
bool RadioLibWrapper::isChannelActive() {
return _threshold == 0
? false // interference check is disabled
: getCurrentRSSI() > _noise_floor + _threshold;
// int.thresh: RSSI-based interference detection (relative to noise floor)
if (_threshold != 0 && getCurrentRSSI() > _noise_floor + _threshold) return true;
// cad: hardware channel activity detection
if (_cad_enabled) {
int16_t result = performChannelScan();
// scanChannel() triggers DIO interrupt (CAD done) which sets STATE_INT_READY
// via setFlag() ISR. Clear it before restarting RX so recvRaw() doesn't
// try to read a non-existent packet and count a spurious recv error.
state = STATE_IDLE;
startRecv();
if (result != RADIOLIB_CHANNEL_FREE) return true;
}
return false;
}
float RadioLibWrapper::getLastRSSI() const {
+4 -1
View File
@@ -9,6 +9,7 @@ protected:
mesh::MainBoard* _board;
uint32_t n_recv, n_sent, n_recv_errors;
int16_t _noise_floor, _threshold;
bool _cad_enabled;
uint16_t _num_floor_samples;
int32_t _floor_sample_sum;
uint8_t _preamble_sf;
@@ -32,7 +33,7 @@ public:
bool isInRecvMode() const override;
bool isChannelActive();
bool isReceiving() override {
bool isReceiving() override {
if (isReceivingPacket()) return true;
return isChannelActive();
@@ -46,9 +47,11 @@ public:
virtual uint8_t getSpreadingFactor() const { return LORA_SF; }
static uint16_t preambleLengthForSF(uint8_t sf) { return sf <= 8 ? 32 : 16; }
void updatePreamble(uint8_t sf) { _preamble_sf = sf; _radio->setPreambleLength(preambleLengthForSF(sf)); }
virtual int16_t performChannelScan();
int getNoiseFloor() const override { return _noise_floor; }
void triggerNoiseFloorCalibrate(int threshold) override;
void setCADEnabled(bool enable) override { _cad_enabled = enable; }
void resetAGC() override;
void loop() override;
@@ -12,6 +12,31 @@
// Sensor library includes and static driver instances
// ============================================================
#if ENV_INCLUDE_BME680_BSEC
#ifndef TELEM_BME680_ADDRESS
#define TELEM_BME680_ADDRESS 0x76
#endif
#define TELEM_BME680_SEALEVELPRESSURE_HPA (1013.25)
#include <bsec.h>
#include <Adafruit_LittleFS.h>
#include <InternalFileSystem.h>
static const uint8_t bsec_config_iaq[] = {
#include "config/generic_33v_3s_28d/bsec_iaq.txt" // 3.3v, LP, 28 day background calibration window
};
static Bsec bsec_iaq;
static float bsec_temperature = 0;
static float bsec_humidity = 0;
static float bsec_pressure_hpa = 0;
static float bsec_iaq_val = 0;
static uint8_t bsec_accuracy = 0;
static bool bsec_active = false;
static bool bsec_data_ready = false;
static bool bsec_first_save_done = false;
static uint32_t bsec_last_save_ms = 0;
#define BSEC_STATE_FILE "/bsec_state.bin"
#define BSEC_SAVE_INTERVAL_MS (8UL * 60 * 60 * 1000) // 8 hour state-save interval
#endif
#ifdef ENV_INCLUDE_BME680
#ifndef TELEM_BME680_ADDRESS
#define TELEM_BME680_ADDRESS 0x76
@@ -235,9 +260,10 @@ static void query_bme680(uint8_t ch, uint8_t, CayenneLPP& lpp) {
if (BME680.performReading()) {
lpp.addTemperature(ch, BME680.temperature);
lpp.addRelativeHumidity(ch, BME680.humidity);
lpp.addBarometricPressure(ch, BME680.pressure / 100);
lpp.addAltitude(ch, 44330.0 * (1.0 - pow((BME680.pressure / 100) / TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903)));
lpp.addAnalogInput(ch, BME680.gas_resistance);
const float pressure_hpa = BME680.pressure / 100.0f;
lpp.addBarometricPressure(ch, pressure_hpa);
lpp.addAltitude(ch, 44330.0f * (1.0f - powf(pressure_hpa / (float)TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903f)));
lpp.addGenericSensor(ch, BME680.gas_resistance);
}
}
#endif
@@ -449,6 +475,62 @@ static void query_rak12035(uint8_t ch, uint8_t sub_ch, CayenneLPP& lpp) {
}
#endif
#if ENV_INCLUDE_BME680_BSEC
static void bsec_load_state() {
using namespace Adafruit_LittleFS_Namespace;
File f = InternalFS.open(BSEC_STATE_FILE, FILE_O_READ);
if (!f) return;
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
f.read(state, BSEC_MAX_STATE_BLOB_SIZE);
f.close();
bsec_iaq.setState(state);
}
static void bsec_save_state() {
using namespace Adafruit_LittleFS_Namespace;
uint8_t state[BSEC_MAX_STATE_BLOB_SIZE];
bsec_iaq.getState(state);
InternalFS.remove(BSEC_STATE_FILE);
File f = InternalFS.open(BSEC_STATE_FILE, FILE_O_WRITE);
if (!f) return;
f.write(state, BSEC_MAX_STATE_BLOB_SIZE);
f.close();
}
static uint8_t init_bme680_bsec(TwoWire* wire, uint8_t addr) {
bsec_iaq.begin(addr, *wire);
if (bsec_iaq.bsecStatus != BSEC_OK) return 0;
bsec_iaq.setConfig(bsec_config_iaq);
if (bsec_iaq.bsecStatus != BSEC_OK) return 0;
bsec_virtual_sensor_t outputs[] = {
BSEC_OUTPUT_IAQ,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE,
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY,
BSEC_OUTPUT_RAW_PRESSURE,
BSEC_OUTPUT_STABILIZATION_STATUS,
BSEC_OUTPUT_RUN_IN_STATUS,
};
bsec_iaq.updateSubscription(outputs, 6, BSEC_SAMPLE_RATE_LP);
if (bsec_iaq.bsecStatus != BSEC_OK) return 0;
bsec_load_state();
bsec_active = true;
return 1;
}
static void query_bme680_bsec(uint8_t ch, uint8_t, CayenneLPP& lpp) {
if (!bsec_data_ready) return;
lpp.addTemperature(ch, bsec_temperature);
lpp.addRelativeHumidity(ch, bsec_humidity);
lpp.addBarometricPressure(ch, bsec_pressure_hpa);
lpp.addAltitude(ch, 44330.0f * (1.0f - powf(bsec_pressure_hpa / (float)TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903f)));
lpp.addGenericSensor(ch, (uint16_t)bsec_iaq_val);
lpp.addAnalogInput(ch, (float)bsec_accuracy);
}
#endif
// ============================================================
// Sensor descriptor table
//
@@ -476,6 +558,9 @@ static const SensorDef SENSOR_TABLE[] = {
#ifdef ENV_INCLUDE_BME680
{ TELEM_BME680_ADDRESS, "BME680", init_bme680, query_bme680 },
#endif
#if ENV_INCLUDE_BME680_BSEC
{ TELEM_BME680_ADDRESS, "BME680+BSEC", init_bme680_bsec, query_bme680_bsec },
#endif
#if ENV_INCLUDE_BME280
{ TELEM_BME280_ADDRESS, "BME280", init_bme280, query_bme280 },
#endif
@@ -798,11 +883,13 @@ void EnvironmentSensorManager::stop_gps() {
MESH_DEBUG_PRINTLN("Stop GPS is N/A on this board. Actual GPS state unchanged");
#endif
}
#endif // ENV_INCLUDE_GPS
#if ENV_INCLUDE_GPS || defined(ENV_INCLUDE_BME680_BSEC)
void EnvironmentSensorManager::loop() {
static long next_gps_update = 0;
#if ENV_INCLUDE_GPS
static long next_gps_update = 0;
if (gps_active) {
_location->loop();
}
@@ -830,5 +917,27 @@ void EnvironmentSensorManager::loop() {
next_gps_update = millis() + (gps_update_interval_sec * 1000);
}
#endif
#if ENV_INCLUDE_BME680_BSEC
if (bsec_active && bsec_iaq.run()) {
uint8_t prev_accuracy = bsec_accuracy;
bsec_temperature = bsec_iaq.temperature;
bsec_humidity = bsec_iaq.humidity;
bsec_pressure_hpa = bsec_iaq.pressure / 100.0f;
bsec_iaq_val = bsec_iaq.iaq;
bsec_accuracy = bsec_iaq.iaqAccuracy;
bsec_data_ready = true;
if (bsec_accuracy == 3) {
if (!bsec_first_save_done) {
bsec_save_state();
bsec_last_save_ms = millis();
bsec_first_save_done = true;
} else if ((millis() - bsec_last_save_ms) >= BSEC_SAVE_INTERVAL_MS) {
bsec_save_state();
bsec_last_save_ms = millis();
}
}
}
#endif // ENV_INCLUDE_BME680_BSEC
}
#endif
#endif // ENV_INCLUDE_GPS || ENV_INCLUDE_BME680_BSEC
@@ -43,7 +43,7 @@ public:
#endif
bool begin() override;
bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override;
#if ENV_INCLUDE_GPS
#if ENV_INCLUDE_GPS || defined(ENV_INCLUDE_BME680_BSEC)
void loop() override;
#endif
int getNumSettings() const override;
@@ -69,7 +69,7 @@ void RAK12035_SoilMoisture::setup(TwoWire &i2c)
//
// *** This code does not support three sensors ***
// The RAK12023 has three connectors, but each of the sensors attached must
// all have a different I2C addresses.
// all have different I2C addresses.
// This code has a function to set the I2C address of a sensor
// and currently only supports one address 0x20 (the default).
// To support three sensors, EnvironmentSensorManager would need to be modified
@@ -242,7 +242,7 @@ bool RAK12035_SoilMoisture::sensor_sleep() //Command 06
// Optional: turn off sensor power AFTER successful sleep command
// This has been commented out due to a pin name conflict with the Heltec v3
// This will need to be resolved if this funstion is to be utilized in the future
// This will need to be resolved if this function is to be utilized in the future
/*
digitalWrite(WB_IO2, LOW);
*/
@@ -376,7 +376,7 @@ uint16_t RAK12035_SoilMoisture::get_humidity_zero() //Command 0B
* getEvent() - High-level function to read both moisture and temperature in one call. *
*------------------------------------------------------------------------------------------*
* This function reads the moisture percentage and temperature from the sensor and returns *
* them via output parameters. This may be used for the telemerty delivery in the MeshCore *
* them via output parameters. This may be used for the telemetry delivery in the MeshCore *
* firmware, with a single function to get all sensor data. *
* *
* The function returns true if both readings were successfully obtained, or false if any *
@@ -417,7 +417,7 @@ bool RAK12035_SoilMoisture::sensor_on()
{
uint8_t data;
// This has been commented out due to a pin name conflict with the Heltec v3
// This will need to be resolved if this funstion is to be utilized in the future
// This will need to be resolved if this function is to be utilized in the future
/*
pinMode(WB_IO2, OUTPUT);
@@ -460,7 +460,7 @@ bool RAK12035_SoilMoisture::reset()
// But might be needed if power is ever switched off. Here is tested code.
// This has been commented out due to a pin name conflict with the Heltec v3
// This will need to be resolved if this funstion is to be utilized in the future
// This will need to be resolved if this function is to be utilized in the future
/*
pinMode(WB_IO4, OUTPUT); //Set IO4 Pin to Output (connected to *reset on sensor)
+1
View File
@@ -14,6 +14,7 @@ public:
int height() const { return _h; }
virtual bool isOn() = 0;
virtual bool isEink() { return false; } // default to non-eink, override in eink drivers
virtual void turnOn() = 0;
virtual void turnOff() = 0;
virtual void clear() = 0;
+1
View File
@@ -26,6 +26,7 @@ public:
}
bool begin();
bool isOn() override { return _isOn; }
bool isEink() override { return true; }
void turnOn() override;
void turnOff() override;
void clear() override;
+1
View File
@@ -22,6 +22,7 @@ public:
bool begin();
bool isOn() override { return _isOn; }
bool isEink() override { return true; }
void turnOn() override;
void turnOff() override;
void clear() override;
+2 -1
View File
@@ -46,7 +46,8 @@ public:
bool begin();
bool isOn() override {return _isOn;};
bool isOn() override { return _isOn; }
bool isEink() override { return true; }
void turnOn() override;
void turnOff() override;
void clear() override;
+2 -2
View File
@@ -842,7 +842,7 @@ void OLEDDisplay::drawLogBuffer(uint16_t xMove, uint16_t yMove) {
uint16_t lastPos = 0;
for (uint16_t i=0;i<this->logBufferFilled;i++){
// Everytime we have a \n print
// Every time we have a \n print
if (this->logBuffer[i] == 10) {
length++;
// Draw string on line `line` from lastPos to length
@@ -1155,7 +1155,7 @@ void OLEDDisplay::setFontTableLookupFunction(FontTableLookupFunction function) {
char DefaultFontTableLookup(const uint8_t ch) {
// UTF-8 to font table index converter
// Code form http://playground.arduino.cc/Main/Utf8ascii
// Code from http://playground.arduino.cc/Main/Utf8ascii
static uint8_t LASTCHAR;
if (ch < 128) { // Standard ASCII-set 0..0x7F handling
+1 -1
View File
@@ -5,7 +5,7 @@
/* class abstracts underlying RTTTL library
Just a simple imlementation to start. At the moment use same
Just a simple implementation to start. At the moment use same
melody for message and discovery
Suggest enum type for different sounds
- on message