Compare commits

..

10 Commits

Author SHA1 Message Date
viric
d18f3f7a65 Allow deepsleep in rak4630 and make it restart well when power comes back (#7882)
* Make RAK4631 nodes power back on deep sleep

The devices will hang if the VBAT goes under 1.7V (Brown-out reset) and
they will never come back unless power supply goes completely off.

This kills unattended nodes.

Using the SystemOff the LPCOMP we can get the nodes back again when
power comes back, even if VBAT goes under 1.7V, which moreover is more
unlikely because the device is off.

* Adding support for heltec t114

And moved particularities to variant.h

* Remove old cpp comment that belongs to variant.h

It was a leftover.

* Trunk fix

---------

Co-authored-by: Tom Fifield <tom@tomfifield.net>
2025-11-18 11:23:39 -06:00
Jonathan Bennett
6c09cf9d3d Gps reset detect (#8302)
* Properly format timestamp in log message

* Better formatting of GPS_DEBUG logging in gps probe

* Reset GPS after serial speed change, and look for magic string to identify chip

* Add UC6580 to boot message detection, for Heltec Tracker

* Add L76K detect from boot string, for Heltec-v4

* Slightly more useful GPS debugging

* Back out detection of L76K via startup messages.

* Ignore PIN_GPS_RESET = -1 and rename passive_detect array.

---------

Co-authored-by: Tom Fifield <tom@tomfifield.net>
2025-11-18 15:04:44 +08:00
omgbebebe
79a91578b7 mqtt: do not try to send packets when it disconnected (#8658) 2025-11-17 16:54:02 -06:00
Jason P
17cd83085b Remove gating for Display Options (#8651) 2025-11-16 22:05:24 -06:00
Chloe Bethel
b7bdcbe43e Address review comments 2025-11-16 17:18:27 -06:00
Chloe Bethel
df063f40ff Try to look for a config file based on the HAT vendor/product for autoconfig 2025-11-16 17:18:27 -06:00
Jason P
84bb1e33a6 Add code for preserving favorites, also move to Home screen before reseting (#8647) 2025-11-16 14:18:16 -06:00
Jason P
955347bf50 Remove fixed scaling in Digital Clock (#8620)
* Update digital clock draw to auto scale to correct size; no more fixed scaling

* Static scale calcuation to improve performance

* Update src/graphics/draw/ClockRenderer.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Back off for width or height exceeds

* Fixes for some calcuations

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-16 08:42:51 -06:00
Dane Evans
4284fc2aec Feat/6704 neighbor info on demand (#8523)
* full thing. works

* works

* minimal changes

* roll back previous changes, move to using the alloc() overrride

* clean up comments

* format

* run clang-format manually.
Trunk may be the absolute worst formatter in existance

* format on WSL to fix trunks awfulness

* add a 3 minute cooldown to prevent messages going back and forth

* add ignoring the dummy neighbor.

* fix or.

* fix spelling, increase logging

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2025-11-15 19:49:46 -06:00
github-actions[bot]
034aaa376a Automated version bumps (#8626)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2025-11-14 06:09:25 -06:00
22 changed files with 287 additions and 174 deletions

View File

@@ -33,8 +33,6 @@ lib_deps =
adafruit/Adafruit seesaw Library@1.7.9 adafruit/Adafruit seesaw Library@1.7.9
# renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main # renovate: datasource=git-refs depName=RAK12034-BMX160 packageName=https://github.com/RAKWireless/RAK12034-BMX160 gitBranch=main
https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip https://github.com/RAKWireless/RAK12034-BMX160/archive/dcead07ffa267d3c906e9ca4a1330ab989e957e2.zip
# renovate: datasource=custom.pio depName=adafruit/Adafruit BME680 Library packageName=adafruit/library/Adafruit BME680
adafruit/Adafruit BME680 Library@^2.0.5
build_flags = build_flags =
${arduino_base.build_flags} ${arduino_base.build_flags}

View File

@@ -87,6 +87,9 @@
</screenshots> </screenshots>
<releases> <releases>
<release version="2.7.15" date="2025-11-13">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.15</url>
</release>
<release version="2.7.14" date="2025-11-03"> <release version="2.7.14" date="2025-11-03">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.14</url> <url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.14</url>
</release> </release>

6
debian/changelog vendored
View File

@@ -1,3 +1,9 @@
meshtasticd (2.7.15.0) unstable; urgency=medium
* Version 2.7.15
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Thu, 13 Nov 2025 12:31:57 +0000
meshtasticd (2.7.14.0) unstable; urgency=medium meshtasticd (2.7.14.0) unstable; urgency=medium
* Version 2.7.14 * Version 2.7.14

View File

@@ -194,7 +194,7 @@ static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level se
#ifdef BATTERY_PIN #ifdef BATTERY_PIN
static void adcEnable() void battery_adcEnable()
{ {
#ifdef ADC_CTRL // enable adc voltage divider when we need to read #ifdef ADC_CTRL // enable adc voltage divider when we need to read
#ifdef ADC_USE_PULLUP #ifdef ADC_USE_PULLUP
@@ -214,7 +214,7 @@ static void adcEnable()
#endif #endif
} }
static void adcDisable() static void battery_adcDisable()
{ {
#ifdef ADC_CTRL // disable adc voltage divider when we need to read #ifdef ADC_CTRL // disable adc voltage divider when we need to read
#ifdef ADC_USE_PULLUP #ifdef ADC_USE_PULLUP
@@ -320,7 +320,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
uint32_t raw = 0; uint32_t raw = 0;
float scaled = 0; float scaled = 0;
adcEnable(); battery_adcEnable();
#ifdef ARCH_ESP32 // ADC block for espressif platforms #ifdef ARCH_ESP32 // ADC block for espressif platforms
raw = espAdcRead(); raw = espAdcRead();
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs); scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
@@ -332,7 +332,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
raw = raw / BATTERY_SENSE_SAMPLES; raw = raw / BATTERY_SENSE_SAMPLES;
scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw; scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw;
#endif #endif
adcDisable(); battery_adcDisable();
if (!initial_read_done) { if (!initial_read_done) {
// Flush the smoothing filter with an ADC reading, if the reading is plausibly correct // Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
@@ -906,13 +906,8 @@ void Power::readPowerStatus()
low_voltage_counter++; low_voltage_counter++;
LOG_DEBUG("Low voltage counter: %d/10", low_voltage_counter); LOG_DEBUG("Low voltage counter: %d/10", low_voltage_counter);
if (low_voltage_counter > 10) { if (low_voltage_counter > 10) {
#ifdef ARCH_NRF52
// We can't trigger deep sleep on NRF52, it's freezing the board
LOG_DEBUG("Low voltage detected, but not trigger deep sleep");
#else
LOG_INFO("Low voltage detected, trigger deep sleep"); LOG_INFO("Low voltage detected, trigger deep sleep");
powerFSM.trigger(EVENT_LOW_BATTERY); powerFSM.trigger(EVENT_LOW_BATTERY);
#endif
} }
} else { } else {
low_voltage_counter = 0; low_voltage_counter = 0;
@@ -1552,4 +1547,4 @@ bool Power::meshSolarInit()
{ {
return false; return false;
} }
#endif #endif

View File

@@ -410,18 +410,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#endif #endif
#endif #endif
// BME680 BSEC2 support detection
#if !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
#if defined(RAK_4631) || defined(TBEAM_V10)
#define MESHTASTIC_BME680_BSEC2_SUPPORTED 1
#define MESHTASTIC_BME680_HEADER <bsec2.h>
#else
#define MESHTASTIC_BME680_BSEC2_SUPPORTED 0
#define MESHTASTIC_BME680_HEADER <Adafruit_BME680.h>
#endif // defined(RAK_4631)
#endif // !defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Global switches to turn off features for a minimized build // Global switches to turn off features for a minimized build
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------

View File

@@ -240,6 +240,9 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
buffer[bytesRead] = b; buffer[bytesRead] = b;
bytesRead++; bytesRead++;
if ((bytesRead == 767) || (b == '\r')) { if ((bytesRead == 767) || (b == '\r')) {
#ifdef GPS_DEBUG
LOG_DEBUG(debugmsg.c_str());
#endif
if (strnstr((char *)buffer, message, bytesRead) != nullptr) { if (strnstr((char *)buffer, message, bytesRead) != nullptr) {
#ifdef GPS_DEBUG #ifdef GPS_DEBUG
LOG_DEBUG("Found: %s", message); // Log the found message LOG_DEBUG("Found: %s", message); // Log the found message
@@ -247,9 +250,6 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
return GNSS_RESPONSE_OK; return GNSS_RESPONSE_OK;
} else { } else {
bytesRead = 0; bytesRead = 0;
#ifdef GPS_DEBUG
LOG_DEBUG(debugmsg.c_str());
#endif
} }
} }
} }
@@ -1275,6 +1275,24 @@ GnssModel_t GPS::probe(int serialSpeed)
memset(&ublox_info, 0, sizeof(ublox_info)); memset(&ublox_info, 0, sizeof(ublox_info));
delay(100); delay(100);
#if defined(PIN_GPS_RESET) && PIN_GPS_RESET != -1
digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms
delay(10);
digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
// attempt to detect the chip based on boot messages
std::vector<ChipInfo> passive_detect = {
{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
{"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
{"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352},
{"UC6580", "UC6580", GNSS_MODEL_UC6580},
// as L76K is sort of a last ditch effort, we won't attempt to detect it by startup messages for now.
/*{"L76K", "SW=URANUS", GNSS_MODEL_MTK}*/};
GnssModel_t detectedDriver = getProbeResponse(500, passive_detect, serialSpeed);
if (detectedDriver != GNSS_MODEL_UNKNOWN) {
return detectedDriver;
}
#endif
// Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices) // Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
_serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n"); _serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
delay(20); delay(20);
@@ -1473,12 +1491,12 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipI
} }
if (c == ',' || (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) { if (c == ',' || (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) {
#ifdef GPS_DEBUG
LOG_DEBUG(response);
#endif
// check if we can see our chips // check if we can see our chips
for (const auto &chipInfo : responseMap) { for (const auto &chipInfo : responseMap) {
if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) { if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
#ifdef GPS_DEBUG
LOG_DEBUG(response);
#endif
LOG_INFO("%s detected", chipInfo.chipName.c_str()); LOG_INFO("%s detected", chipInfo.chipName.c_str());
delete[] response; // Cleanup before return delete[] response; // Cleanup before return
return chipInfo.driver; return chipInfo.driver;
@@ -1486,6 +1504,9 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipI
} }
} }
if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') { if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') {
#ifdef GPS_DEBUG
LOG_DEBUG(response);
#endif
// Reset the response buffer for the next potential message // Reset the response buffer for the next potential message
responseLen = 0; responseLen = 0;
response[0] = '\0'; response[0] = '\0';
@@ -1572,8 +1593,6 @@ GPS *GPS::createGps()
#ifdef PIN_GPS_RESET #ifdef PIN_GPS_RESET
pinMode(PIN_GPS_RESET, OUTPUT); pinMode(PIN_GPS_RESET, OUTPUT);
digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms
delay(10);
digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE); digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
#endif #endif

View File

@@ -310,7 +310,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
#ifdef BUILD_EPOCH #ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) { if (tv.tv_sec < BUILD_EPOCH) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) { if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH); LOG_WARN("Ignore time (%lu) before build epoch (%lu)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis(); lastTimeValidationWarning = millis();
} }
return RTCSetResultInvalidTime; return RTCSetResultInvalidTime;
@@ -319,7 +319,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
// Calculate max allowed time safely to avoid overflow in logging // Calculate max allowed time safely to avoid overflow in logging
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS; uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime; uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, LOG_WARN("Ignore time (%lu) too far in the future (build epoch: %lu, max allowed: %lu)!", printableEpoch,
(uint32_t)BUILD_EPOCH, maxAllowedPrintable); (uint32_t)BUILD_EPOCH, maxAllowedPrintable);
lastTimeValidationWarning = millis(); lastTimeValidationWarning = millis();
} }

View File

@@ -194,17 +194,12 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
graphics::drawCommonHeader(display, x, y, titleStr, true, true); graphics::drawCommonHeader(display, x, y, titleStr, true, true);
int line = 0; int line = 0;
#ifdef T_WATCH_S3
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
graphics::ClockRenderer::drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14);
}
#endif
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
char timeString[16]; char timeString[16];
int hour = 0; int hour = 0;
int minute = 0; int minute = 0;
int second = 0; int second = 0;
if (rtc_sec > 0) { if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY; long hms = rtc_sec % SEC_PER_DAY;
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY; hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
@@ -215,11 +210,11 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
} }
bool isPM = hour >= 12; bool isPM = hour >= 12;
// hour = hour > 12 ? hour - 12 : hour;
if (config.display.use_12h_clock) { if (config.display.use_12h_clock) {
hour %= 12; hour %= 12;
if (hour == 0) if (hour == 0) {
hour = 12; hour = 12;
}
snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute); snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute);
} else { } else {
snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute); snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute);
@@ -229,24 +224,56 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
char secondString[8]; char secondString[8];
snprintf(secondString, sizeof(secondString), "%02d", second); snprintf(secondString, sizeof(secondString), "%02d", second);
#ifdef T_WATCH_S3 static bool scaleInitialized = false;
float scale = 1.5; static float scale = 0.75f;
#elif defined(CHATTER_2) static float segmentWidth = SEGMENT_WIDTH * 0.75f;
float scale = 1.1; static float segmentHeight = SEGMENT_HEIGHT * 0.75f;
#else
float scale = 0.75;
if (isHighResolution) {
scale = 1.5;
}
#endif
uint16_t segmentWidth = SEGMENT_WIDTH * scale; if (!scaleInitialized) {
uint16_t segmentHeight = SEGMENT_HEIGHT * scale; float screenwidth_target_ratio = 0.80f; // Target 80% of display width (adjustable)
float max_scale = 3.5f; // Safety limit to avoid runaway scaling
float step = 0.05f; // Step increment per iteration
float target_width = display->getWidth() * screenwidth_target_ratio;
float target_height =
display->getHeight() -
(isHighResolution
? 46
: 33); // Be careful adjusting this number, we have to account for header and the text under the time
float calculated_width_size = 0.0f;
float calculated_height_size = 0.0f;
while (true) {
segmentWidth = SEGMENT_WIDTH * scale;
segmentHeight = SEGMENT_HEIGHT * scale;
calculated_width_size = segmentHeight + ((segmentWidth + (segmentHeight * 2) + 4) * 4);
calculated_height_size = segmentHeight + ((segmentHeight + (segmentHeight * 2) + 4) * 2);
if (calculated_width_size >= target_width || calculated_height_size >= target_height || scale >= max_scale) {
break;
}
scale += step;
}
// If we overshot width, back off one step and recompute segment sizes
if (calculated_width_size > target_width || calculated_height_size > target_height) {
scale -= step;
segmentWidth = SEGMENT_WIDTH * scale;
segmentHeight = SEGMENT_HEIGHT * scale;
}
scaleInitialized = true;
}
size_t len = strlen(timeString);
// calculate hours:minutes string width // calculate hours:minutes string width
uint16_t timeStringWidth = strlen(timeString) * 5; uint16_t timeStringWidth = len * 5; // base spacing between characters
for (uint8_t i = 0; i < strlen(timeString); i++) { for (size_t i = 0; i < len; i++) {
char character = timeString[i]; char character = timeString[i];
if (character == ':') { if (character == ':') {
@@ -257,19 +284,21 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
} }
uint16_t hourMinuteTextX = (display->getWidth() / 2) - (timeStringWidth / 2); uint16_t hourMinuteTextX = (display->getWidth() / 2) - (timeStringWidth / 2);
uint16_t startingHourMinuteTextX = hourMinuteTextX; uint16_t startingHourMinuteTextX = hourMinuteTextX;
uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2); uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2) + 2;
// iterate over characters in hours:minutes string and draw segmented characters // iterate over characters in hours:minutes string and draw segmented characters
for (uint8_t i = 0; i < strlen(timeString); i++) { for (size_t i = 0; i < len; i++) {
char character = timeString[i]; char character = timeString[i];
if (character == ':') { if (character == ':') {
drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale); drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale);
hourMinuteTextX += segmentHeight + 6; hourMinuteTextX += segmentHeight + 6;
if (scale >= 2.0f) {
hourMinuteTextX += (uint16_t)(4.5f * scale);
}
} else { } else {
drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character - '0', scale); drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character - '0', scale);
@@ -279,38 +308,29 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
hourMinuteTextX += 5; hourMinuteTextX += 5;
} }
// draw seconds string // draw seconds string + AM/PM
display->setFont(FONT_SMALL); display->setFont(FONT_SMALL);
int xOffset = (isHighResolution) ? 0 : -1; int xOffset = (isHighResolution) ? 0 : -1;
if (hour >= 10) { if (hour >= 10) {
xOffset += (isHighResolution) ? 32 : 18; xOffset += (isHighResolution) ? 32 : 18;
} }
int yOffset = (isHighResolution) ? 3 : 1;
#ifdef SENSECAP_INDICATOR
yOffset -= 3;
#endif
#ifdef T_DECK
yOffset -= 5;
#endif
if (config.display.use_12h_clock) { if (config.display.use_12h_clock) {
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2, display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - 1, isPM ? "pm" : "am");
isPM ? "pm" : "am");
} }
#ifndef USE_EINK #ifndef USE_EINK
xOffset = (isHighResolution) ? 18 : 10; xOffset = (isHighResolution) ? 18 : 10;
display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset, if (scale >= 2.0f) {
xOffset -= (int)(4.5f * scale);
}
display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - 1,
secondString); secondString);
#endif #endif
graphics::drawCommonFooter(display, x, y); graphics::drawCommonFooter(display, x, y);
} }
void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
{
display->drawFastImage(x, y, 18, 14, bluetoothConnectedIcon);
}
// Draw an analog clock // Draw an analog clock
void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{ {
@@ -321,11 +341,6 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
graphics::drawCommonHeader(display, x, y, titleStr, true, true); graphics::drawCommonHeader(display, x, y, titleStr, true, true);
int line = 0; int line = 0;
#ifdef T_WATCH_S3
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14);
}
#endif
// clock face center coordinates // clock face center coordinates
int16_t centerX = display->getWidth() / 2; int16_t centerX = display->getWidth() / 2;
int16_t centerY = display->getHeight() / 2; int16_t centerY = display->getHeight() / 2;

View File

@@ -24,7 +24,6 @@ void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int heig
// UI elements for clock displays // UI elements for clock displays
// void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode = true, float scale = 1); // void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode = true, float scale = 1);
void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y);
} // namespace ClockRenderer } // namespace ClockRenderer

View File

@@ -581,11 +581,8 @@ void menuHandler::systemBaseMenu()
optionsArray[options] = "Notifications"; optionsArray[options] = "Notifications";
optionsEnumArray[options++] = Notifications; optionsEnumArray[options++] = Notifications;
#if defined(ST7789_CS) || defined(ST7796_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || \
defined(USE_SH1107) || defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
optionsArray[options] = "Display Options"; optionsArray[options] = "Display Options";
optionsEnumArray[options++] = ScreenOptions; optionsEnumArray[options++] = ScreenOptions;
#endif
#if defined(M5STACK_UNITC6L) #if defined(M5STACK_UNITC6L)
optionsArray[options] = "Bluetooth"; optionsArray[options] = "Bluetooth";
@@ -785,17 +782,24 @@ void menuHandler::nodeNameLengthMenu()
void menuHandler::resetNodeDBMenu() void menuHandler::resetNodeDBMenu()
{ {
static const char *optionsArray[] = {"Back", "Confirm"}; static const char *optionsArray[] = {"Back", "Reset All", "Preserve Favorites"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Confirm Reset NodeDB"; bannerOptions.message = "Confirm Reset NodeDB";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2; bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) { if (selected == 1 || selected == 2) {
disableBluetooth(); disableBluetooth();
screen->setFrames(Screen::FOCUS_DEFAULT);
}
if (selected == 1) {
LOG_INFO("Initiate node-db reset"); LOG_INFO("Initiate node-db reset");
nodeDB->resetNodes(); nodeDB->resetNodes();
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
} else if (selected == 2) {
LOG_INFO("Initiate node-db reset but keeping favorites");
nodeDB->resetNodes(1);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
} }
}; };
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);

View File

@@ -34,7 +34,8 @@ void NeighborInfoModule::printNodeDBNeighbors()
} }
} }
/* Send our initial owner announcement 35 seconds after we start (to give network time to setup) */ /* Send our initial owner announcement 35 seconds after we start (to give
* network time to setup) */
NeighborInfoModule::NeighborInfoModule() NeighborInfoModule::NeighborInfoModule()
: ProtobufModule("neighborinfo", meshtastic_PortNum_NEIGHBORINFO_APP, &meshtastic_NeighborInfo_msg), : ProtobufModule("neighborinfo", meshtastic_PortNum_NEIGHBORINFO_APP, &meshtastic_NeighborInfo_msg),
concurrency::OSThread("NeighborInfo") concurrency::OSThread("NeighborInfo")
@@ -53,8 +54,8 @@ NeighborInfoModule::NeighborInfoModule()
} }
/* /*
Collect neighbor info from the nodeDB's history, capping at a maximum number of entries and max time Collect neighbor info from the nodeDB's history, capping at a maximum number of
Assumes that the neighborInfo packet has been allocated entries and max time Assumes that the neighborInfo packet has been allocated
@returns the number of entries collected @returns the number of entries collected
*/ */
uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighborInfo) uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighborInfo)
@@ -71,8 +72,8 @@ uint32_t NeighborInfoModule::collectNeighborInfo(meshtastic_NeighborInfo *neighb
if ((neighborInfo->neighbors_count < MAX_NUM_NEIGHBORS) && (nbr.node_id != my_node_id)) { if ((neighborInfo->neighbors_count < MAX_NUM_NEIGHBORS) && (nbr.node_id != my_node_id)) {
neighborInfo->neighbors[neighborInfo->neighbors_count].node_id = nbr.node_id; neighborInfo->neighbors[neighborInfo->neighbors_count].node_id = nbr.node_id;
neighborInfo->neighbors[neighborInfo->neighbors_count].snr = nbr.snr; neighborInfo->neighbors[neighborInfo->neighbors_count].snr = nbr.snr;
// Note: we don't set the last_rx_time and node_broadcast_intervals_secs here, because we don't want to send this over // Note: we don't set the last_rx_time and node_broadcast_intervals_secs
// the mesh // here, because we don't want to send this over the mesh
neighborInfo->neighbors_count++; neighborInfo->neighbors_count++;
} }
} }
@@ -88,8 +89,9 @@ void NeighborInfoModule::cleanUpNeighbors()
uint32_t now = getTime(); uint32_t now = getTime();
NodeNum my_node_id = nodeDB->getNodeNum(); NodeNum my_node_id = nodeDB->getNodeNum();
for (auto it = neighbors.rbegin(); it != neighbors.rend();) { for (auto it = neighbors.rbegin(); it != neighbors.rend();) {
// We will remove a neighbor if we haven't heard from them in twice the broadcast interval // We will remove a neighbor if we haven't heard from them in twice the
// cannot use isWithinTimespanMs() as it->last_rx_time is seconds since 1970 // broadcast interval cannot use isWithinTimespanMs() as it->last_rx_time is
// seconds since 1970
if ((now - it->last_rx_time > it->node_broadcast_interval_secs * 2) && (it->node_id != my_node_id)) { if ((now - it->last_rx_time > it->node_broadcast_interval_secs * 2) && (it->node_id != my_node_id)) {
LOG_DEBUG("Remove neighbor with node ID 0x%x", it->node_id); LOG_DEBUG("Remove neighbor with node ID 0x%x", it->node_id);
it = std::vector<meshtastic_Neighbor>::reverse_iterator( it = std::vector<meshtastic_Neighbor>::reverse_iterator(
@@ -132,25 +134,55 @@ int32_t NeighborInfoModule::runOnce()
return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_neighbor_info_broadcast_secs); return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_neighbor_info_broadcast_secs);
} }
meshtastic_MeshPacket *NeighborInfoModule::allocReply()
{
LOG_INFO("NeighborInfoRequested.");
if (lastSentReply && Throttle::isWithinTimespanMs(lastSentReply, 3 * 60 * 1000)) {
LOG_DEBUG("Skip Neighbors reply since we sent a reply <3min ago");
ignoreRequest = true; // Mark it as ignored for MeshModule
return nullptr;
}
meshtastic_NeighborInfo neighborInfo = meshtastic_NeighborInfo_init_zero;
collectNeighborInfo(&neighborInfo);
meshtastic_MeshPacket *reply = allocDataProtobuf(neighborInfo);
if (reply) {
lastSentReply = millis(); // Track when we sent this reply
}
return reply;
}
/* /*
Collect a received neighbor info packet from another node Collect a received neighbor info packet from another node
Pass it to an upper client; do not persist this data on the mesh Pass it to an upper client; do not persist this data on the mesh
*/ */
bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *np) bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *np)
{ {
LOG_DEBUG("NeighborInfo: handleReceivedProtobuf");
if (np) { if (np) {
printNeighborInfo("RECEIVED", np); printNeighborInfo("RECEIVED", np);
updateNeighbors(mp, np); // Ignore dummy/interceptable packets: single neighbor with nodeId 0 and snr 0
if (np->neighbors_count != 1 || np->neighbors[0].node_id != 0 || np->neighbors[0].snr != 0.0f) {
LOG_DEBUG(" Updating neighbours");
updateNeighbors(mp, np);
} else {
LOG_DEBUG(" Ignoring dummy neighbor info packet (single neighbor with nodeId 0, snr 0)");
}
} else if (mp.hop_start != 0 && mp.hop_start == mp.hop_limit) { } else if (mp.hop_start != 0 && mp.hop_start == mp.hop_limit) {
LOG_DEBUG("Get or create neighbor: %u with snr %f", mp.from, mp.rx_snr);
// If the hopLimit is the same as hopStart, then it is a neighbor // If the hopLimit is the same as hopStart, then it is a neighbor
getOrCreateNeighbor(mp.from, mp.from, 0, mp.rx_snr); // Set the broadcast interval to 0, as we don't know it getOrCreateNeighbor(mp.from, mp.from, 0,
mp.rx_snr); // Set the broadcast interval to 0, as we don't know it
} }
// Allow others to handle this packet // Allow others to handle this packet
return false; return false;
} }
/* /*
Copy the content of a current NeighborInfo packet into a new one and update the last_sent_by_id to our NodeNum Copy the content of a current NeighborInfo packet into a new one and update the
last_sent_by_id to our NodeNum
*/ */
void NeighborInfoModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_NeighborInfo *n) void NeighborInfoModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_NeighborInfo *n)
{ {
@@ -168,8 +200,10 @@ void NeighborInfoModule::resetNeighbors()
void NeighborInfoModule::updateNeighbors(const meshtastic_MeshPacket &mp, const meshtastic_NeighborInfo *np) void NeighborInfoModule::updateNeighbors(const meshtastic_MeshPacket &mp, const meshtastic_NeighborInfo *np)
{ {
// The last sent ID will be 0 if the packet is from the phone, which we don't count as LOG_DEBUG("updateNeighbors");
// an edge. So we assume that if it's zero, then this packet is from our node. // The last sent ID will be 0 if the packet is from the phone, which we don't
// count as an edge. So we assume that if it's zero, then this packet is from
// our node.
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) { if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
getOrCreateNeighbor(mp.from, np->last_sent_by_id, np->node_broadcast_interval_secs, mp.rx_snr); getOrCreateNeighbor(mp.from, np->last_sent_by_id, np->node_broadcast_interval_secs, mp.rx_snr);
} }
@@ -188,7 +222,8 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen
// if found, update it // if found, update it
neighbors[i].snr = snr; neighbors[i].snr = snr;
neighbors[i].last_rx_time = getTime(); neighbors[i].last_rx_time = getTime();
// Only if this is the original sender, the broadcast interval corresponds to it // Only if this is the original sender, the broadcast interval corresponds
// to it
if (originalSender == n && node_broadcast_interval_secs != 0) if (originalSender == n && node_broadcast_interval_secs != 0)
neighbors[i].node_broadcast_interval_secs = node_broadcast_interval_secs; neighbors[i].node_broadcast_interval_secs = node_broadcast_interval_secs;
return &neighbors[i]; return &neighbors[i];
@@ -200,10 +235,12 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen
new_nbr.node_id = n; new_nbr.node_id = n;
new_nbr.snr = snr; new_nbr.snr = snr;
new_nbr.last_rx_time = getTime(); new_nbr.last_rx_time = getTime();
// Only if this is the original sender, the broadcast interval corresponds to it // Only if this is the original sender, the broadcast interval corresponds to
// it
if (originalSender == n && node_broadcast_interval_secs != 0) if (originalSender == n && node_broadcast_interval_secs != 0)
new_nbr.node_broadcast_interval_secs = node_broadcast_interval_secs; new_nbr.node_broadcast_interval_secs = node_broadcast_interval_secs;
else // Assume the same broadcast interval as us for the neighbor if we don't know it else // Assume the same broadcast interval as us for the neighbor if we don't
// know it
new_nbr.node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval; new_nbr.node_broadcast_interval_secs = moduleConfig.neighbor_info.update_interval;
if (neighbors.size() < MAX_NUM_NEIGHBORS) { if (neighbors.size() < MAX_NUM_NEIGHBORS) {

View File

@@ -28,6 +28,10 @@ class NeighborInfoModule : public ProtobufModule<meshtastic_NeighborInfo>, priva
*/ */
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *nb) override; virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *nb) override;
/* Messages can be received that have the want_response bit set. If set, this callback will be invoked
* so that subclasses can (optionally) send a response back to the original sender. */
virtual meshtastic_MeshPacket *allocReply() override;
/* /*
* Collect neighbor info from the nodeDB's history, capping at a maximum number of entries and max time * Collect neighbor info from the nodeDB's history, capping at a maximum number of entries and max time
* @return the number of entries collected * @return the number of entries collected
@@ -66,5 +70,8 @@ class NeighborInfoModule : public ProtobufModule<meshtastic_NeighborInfo>, priva
/* These are for debugging only */ /* These are for debugging only */
void printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np); void printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np);
void printNodeDBNeighbors(); void printNodeDBNeighbors();
private:
uint32_t lastSentReply = 0; // Last time we sent a position reply (used for reply throttling only)
}; };
extern NeighborInfoModule *neighborInfoModule; extern NeighborInfoModule *neighborInfoModule;

View File

@@ -53,7 +53,7 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
#include "Sensor/LTR390UVSensor.h" #include "Sensor/LTR390UVSensor.h"
#endif #endif
#if __has_include(MESHTASTIC_BME680_HEADER) #if __has_include(<bsec2.h>)
#include "Sensor/BME680Sensor.h" #include "Sensor/BME680Sensor.h"
#endif #endif
@@ -214,7 +214,7 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
#if __has_include(<Adafruit_LTR390.h>) #if __has_include(<Adafruit_LTR390.h>)
addSensor<LTR390UVSensor>(i2cScanner, ScanI2C::DeviceType::LTR390UV); addSensor<LTR390UVSensor>(i2cScanner, ScanI2C::DeviceType::LTR390UV);
#endif #endif
#if __has_include(MESHTASTIC_BME680_HEADER) #if __has_include(<bsec2.h>)
addSensor<BME680Sensor>(i2cScanner, ScanI2C::DeviceType::BME_680); addSensor<BME680Sensor>(i2cScanner, ScanI2C::DeviceType::BME_680);
#endif #endif
#if __has_include(<Adafruit_BMP280.h>) #if __has_include(<Adafruit_BMP280.h>)

View File

@@ -1,6 +1,6 @@
#include "configuration.h" #include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER) #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(<bsec2.h>)
#include "../mesh/generated/meshtastic/telemetry.pb.h" #include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "BME680Sensor.h" #include "BME680Sensor.h"
@@ -10,7 +10,6 @@
BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {} BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {}
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
int32_t BME680Sensor::runOnce() int32_t BME680Sensor::runOnce()
{ {
if (!bme680.run()) { if (!bme680.run()) {
@@ -18,13 +17,10 @@ int32_t BME680Sensor::runOnce()
} }
return 35; return 35;
} }
#endif // defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{ {
status = 0; status = 0;
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
if (!bme680.begin(dev->address.address, *bus)) if (!bme680.begin(dev->address.address, *bus))
checkStatus("begin"); checkStatus("begin");
@@ -46,25 +42,12 @@ bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
if (status == 0) if (status == 0)
LOG_DEBUG("BME680Sensor::runOnce: bme680.status %d", bme680.status); LOG_DEBUG("BME680Sensor::runOnce: bme680.status %d", bme680.status);
#else
bme680 = makeBME680(bus);
if (!bme680->begin(dev->address.address)) {
LOG_ERROR("Init sensor: %s failed at begin()", sensorName);
return status;
}
status = 1;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
initI2CSensor(); initI2CSensor();
return status; return status;
} }
bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement) bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
{ {
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0) if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0)
return false; return false;
@@ -82,27 +65,9 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
// Check if we need to save state to filesystem (every STATE_SAVE_PERIOD ms) // Check if we need to save state to filesystem (every STATE_SAVE_PERIOD ms)
measurement->variant.environment_metrics.iaq = bme680.getData(BSEC_OUTPUT_IAQ).signal; measurement->variant.environment_metrics.iaq = bme680.getData(BSEC_OUTPUT_IAQ).signal;
updateState(); updateState();
#else
if (!bme680->performReading()) {
LOG_ERROR("BME680Sensor::getMetrics: performReading failed");
return false;
}
measurement->variant.environment_metrics.has_temperature = true;
measurement->variant.environment_metrics.has_relative_humidity = true;
measurement->variant.environment_metrics.has_barometric_pressure = true;
measurement->variant.environment_metrics.has_gas_resistance = true;
measurement->variant.environment_metrics.temperature = bme680->readTemperature();
measurement->variant.environment_metrics.relative_humidity = bme680->readHumidity();
measurement->variant.environment_metrics.barometric_pressure = bme680->readPressure() / 100.0F;
measurement->variant.environment_metrics.gas_resistance = bme680->readGas() / 1000.0;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
return true; return true;
} }
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
void BME680Sensor::loadState() void BME680Sensor::loadState()
{ {
#ifdef FSCom #ifdef FSCom
@@ -179,6 +144,5 @@ void BME680Sensor::checkStatus(const char *functionName)
else if (bme680.sensor.status > BME68X_OK) else if (bme680.sensor.status > BME68X_OK)
LOG_WARN("%s BME68X code: %d", functionName, bme680.sensor.status); LOG_WARN("%s BME68X code: %d", functionName, bme680.sensor.status);
} }
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#endif #endif

View File

@@ -1,40 +1,23 @@
#include "configuration.h" #include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(MESHTASTIC_BME680_HEADER) #if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include(<bsec2.h>)
#include "../mesh/generated/meshtastic/telemetry.pb.h" #include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h" #include "TelemetrySensor.h"
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
#include <bme68xLibrary.h>
#include <bsec2.h> #include <bsec2.h>
#else
#include <Adafruit_BME680.h>
#include <memory>
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) // That's 6 hours worth of millis() #define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) // That's 6 hours worth of millis()
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
const uint8_t bsec_config[] = { const uint8_t bsec_config[] = {
#include "config/bme680/bme680_iaq_33v_3s_4d/bsec_iaq.txt" #include "config/bme680/bme680_iaq_33v_3s_4d/bsec_iaq.txt"
}; };
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
class BME680Sensor : public TelemetrySensor class BME680Sensor : public TelemetrySensor
{ {
private: private:
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
Bsec2 bme680; Bsec2 bme680;
#else
using BME680Ptr = std::unique_ptr<Adafruit_BME680>;
static BME680Ptr makeBME680(TwoWire *bus) { return std::make_unique<Adafruit_BME680>(bus); }
BME680Ptr bme680;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
protected: protected:
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
const char *bsecConfigFileName = "/prefs/bsec.dat"; const char *bsecConfigFileName = "/prefs/bsec.dat";
uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0}; uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0};
uint8_t accuracy = 0; uint8_t accuracy = 0;
@@ -51,13 +34,10 @@ class BME680Sensor : public TelemetrySensor
void loadState(); void loadState();
void updateState(); void updateState();
void checkStatus(const char *functionName); void checkStatus(const char *functionName);
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
public: public:
BME680Sensor(); BME680Sensor();
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
virtual int32_t runOnce() override; virtual int32_t runOnce() override;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
virtual bool getMetrics(meshtastic_Telemetry *measurement) override; virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override; virtual bool initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev) override;
}; };

View File

@@ -51,6 +51,7 @@ constexpr int reconnectMax = 5;
static uint8_t bytes[meshtastic_MqttClientProxyMessage_size + 30]; // 12 for channel name and 16 for nodeid static uint8_t bytes[meshtastic_MqttClientProxyMessage_size + 30]; // 12 for channel name and 16 for nodeid
static bool isMqttServerAddressPrivate = false; static bool isMqttServerAddressPrivate = false;
static bool isConnected = false;
inline void onReceiveProto(char *topic, byte *payload, size_t length) inline void onReceiveProto(char *topic, byte *payload, size_t length)
{ {
@@ -320,8 +321,10 @@ bool connectPubSub(const PubSubConfig &config, PubSubClient &pubSub, Client &cli
std::string nodeId = nodeDB->getNodeId(); std::string nodeId = nodeDB->getNodeId();
const bool connected = pubSub.connect(nodeId.c_str(), config.mqttUsername, config.mqttPassword); const bool connected = pubSub.connect(nodeId.c_str(), config.mqttUsername, config.mqttPassword);
if (connected) { if (connected) {
isConnected = true;
LOG_INFO("MQTT connected"); LOG_INFO("MQTT connected");
} else { } else {
isConnected = false;
LOG_WARN("Failed to connect to MQTT server"); LOG_WARN("Failed to connect to MQTT server");
} }
return connected; return connected;
@@ -507,6 +510,7 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo
void MQTT::reconnect() void MQTT::reconnect()
{ {
isConnected = false;
if (wantsLink()) { if (wantsLink()) {
if (moduleConfig.mqtt.proxy_to_client_enabled) { if (moduleConfig.mqtt.proxy_to_client_enabled) {
LOG_INFO("MQTT connect via client proxy instead"); LOG_INFO("MQTT connect via client proxy instead");
@@ -534,7 +538,7 @@ void MQTT::reconnect()
runASAP = true; runASAP = true;
reconnectCount = 0; reconnectCount = 0;
isMqttServerAddressPrivate = isPrivateIpAddress(clientConnection->remoteIP()); isMqttServerAddressPrivate = isPrivateIpAddress(clientConnection->remoteIP());
isConnected = true;
publishNodeInfo(); publishNodeInfo();
sendSubscriptions(); sendSubscriptions();
} else { } else {
@@ -688,7 +692,7 @@ void MQTT::publishNodeInfo()
} }
void MQTT::publishQueuedMessages() void MQTT::publishQueuedMessages()
{ {
if (mqttQueue.isEmpty()) if (mqttQueue.isEmpty() || !isConnected)
return; return;
LOG_DEBUG("Publish enqueued MQTT message"); LOG_DEBUG("Publish enqueued MQTT message");
@@ -895,4 +899,4 @@ void MQTT::perhapsReportToMap()
// Update the last report time // Update the last report time
last_report_to_map = millis(); last_report_to_map = millis();
} }

View File

@@ -14,6 +14,9 @@
#include "error.h" #include "error.h"
#include "main.h" #include "main.h"
#include "meshUtils.h" #include "meshUtils.h"
#include "power.h"
#include <hal/nrf_lpcomp.h>
#ifdef BQ25703A_ADDR #ifdef BQ25703A_ADDR
#include "BQ25713.h" #include "BQ25713.h"
@@ -389,6 +392,23 @@ void cpuDeepSleep(uint32_t msecToWake)
nrf_gpio_cfg_sense_set(BUTTON_PIN, sense); // Apply SENSE to wake up the device from the deep sleep nrf_gpio_cfg_sense_set(BUTTON_PIN, sense); // Apply SENSE to wake up the device from the deep sleep
#endif #endif
#ifdef BATTERY_LPCOMP_INPUT
// Wake up if power rises again
nrf_lpcomp_config_t c;
c.reference = BATTERY_LPCOMP_THRESHOLD;
c.detection = NRF_LPCOMP_DETECT_UP;
c.hyst = NRF_LPCOMP_HYST_NOHYST;
nrf_lpcomp_configure(NRF_LPCOMP, &c);
nrf_lpcomp_input_select(NRF_LPCOMP, BATTERY_LPCOMP_INPUT);
nrf_lpcomp_enable(NRF_LPCOMP);
battery_adcEnable();
nrf_lpcomp_task_trigger(NRF_LPCOMP, NRF_LPCOMP_TASK_START);
while (!nrf_lpcomp_event_check(NRF_LPCOMP, NRF_LPCOMP_EVENT_READY))
;
#endif
auto ok = sd_power_system_off(); auto ok = sd_power_system_off();
if (ok != NRF_SUCCESS) { if (ok != NRF_SUCCESS) {
LOG_ERROR("FIXME: Ignoring soft device (EasyDMA pending?) and forcing system-off!"); LOG_ERROR("FIXME: Ignoring soft device (EasyDMA pending?) and forcing system-off!");
@@ -420,4 +440,4 @@ void enterDfuMode()
#else #else
enterUf2Dfu(); enterUf2Dfu();
#endif #endif
} }

View File

@@ -146,6 +146,20 @@ void getMacAddr(uint8_t *dmac)
} }
} }
std::string cleanupNameForAutoconf(std::string name)
{
// Convert spaces -> dashes, lowercase
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) {
if (c == ' ') {
return '-';
}
return (char)std::tolower(c);
});
return name;
}
/** apps run under portduino can optionally define a portduinoSetup() to /** apps run under portduino can optionally define a portduinoSetup() to
* use portduino specific init code (such as gpioBind) to setup portduino on their host machine, * use portduino specific init code (such as gpioBind) to setup portduino on their host machine,
* before running 'arduino' code. * before running 'arduino' code.
@@ -218,6 +232,11 @@ void portduinoSetup()
// If LoRa `Module: auto` (default in config.yaml), // If LoRa `Module: auto` (default in config.yaml),
// attempt to auto config based on Product Strings // attempt to auto config based on Product Strings
if (portduino_config.lora_module == use_autoconf) { if (portduino_config.lora_module == use_autoconf) {
bool found_hat = false;
bool found_rak_eeprom = false;
bool found_ch341 = false;
char hat_vendor[96] = {0};
char autoconf_product[96] = {0}; char autoconf_product[96] = {0};
// Try CH341 // Try CH341
try { try {
@@ -227,21 +246,32 @@ void portduinoSetup()
ch341Hal->getProductString(autoconf_product, 95); ch341Hal->getProductString(autoconf_product, 95);
delete ch341Hal; delete ch341Hal;
std::cout << "autoconf: Found CH341 device " << autoconf_product << std::endl; std::cout << "autoconf: Found CH341 device " << autoconf_product << std::endl;
found_ch341 = true;
} catch (...) { } catch (...) {
std::cout << "autoconf: Could not locate CH341 device" << std::endl; std::cout << "autoconf: Could not locate CH341 device" << std::endl;
} }
// Try Pi HAT+ // Try Pi HAT+
if (strlen(autoconf_product) < 6) { if (strlen(autoconf_product) < 6) {
std::cout << "autoconf: Looking for Pi HAT+..." << std::endl; std::cout << "autoconf: Looking for Pi HAT+..." << std::endl;
if (access("/proc/device-tree/hat/vendor", R_OK) == 0) {
std::ifstream hatVendorFile("/proc/device-tree/hat/vendor");
if (hatVendorFile.is_open()) {
hatVendorFile.read(hat_vendor, 95);
hatVendorFile.close();
}
}
if (access("/proc/device-tree/hat/product", R_OK) == 0) { if (access("/proc/device-tree/hat/product", R_OK) == 0) {
std::ifstream hatProductFile("/proc/device-tree/hat/product"); std::ifstream hatProductFile("/proc/device-tree/hat/product");
if (hatProductFile.is_open()) { if (hatProductFile.is_open()) {
hatProductFile.read(autoconf_product, 95); hatProductFile.read(autoconf_product, 95);
hatProductFile.close(); hatProductFile.close();
} }
std::cout << "autoconf: Found Pi HAT+ " << autoconf_product << " at /proc/device-tree/hat/product" << std::endl; std::cout << "autoconf: Found Pi HAT+ " << hat_vendor << " " << autoconf_product << " at /proc/device-tree/hat"
<< std::endl;
found_hat = true;
} else { } else {
std::cout << "autoconf: Could not locate Pi HAT+ at /proc/device-tree/hat/product" << std::endl; std::cout << "autoconf: Could not locate Pi HAT+ at /proc/device-tree/hat" << std::endl;
} }
} }
// attempt to load autoconf data from an EEPROM on 0x50 // attempt to load autoconf data from an EEPROM on 0x50
@@ -297,6 +327,7 @@ void portduinoSetup()
autoconf_product[0] = 0x0; autoconf_product[0] = 0x0;
} else { } else {
std::cout << "autoconf: Found eeprom data " << autoconf_raw << std::endl; std::cout << "autoconf: Found eeprom data " << autoconf_raw << std::endl;
found_rak_eeprom = true;
if (mac_start != nullptr) { if (mac_start != nullptr) {
std::cout << "autoconf: Found mac data " << mac_start << std::endl; std::cout << "autoconf: Found mac data " << mac_start << std::endl;
if (strlen(mac_start) == 12) if (strlen(mac_start) == 12)
@@ -325,12 +356,29 @@ void portduinoSetup()
if (strlen(autoconf_product) > 0) { if (strlen(autoconf_product) > 0) {
// From configProducts map in PortduinoGlue.h // From configProducts map in PortduinoGlue.h
std::string product_config = ""; std::string product_config = "";
try {
if (configProducts.find(autoconf_product) != configProducts.end()) {
product_config = configProducts.at(autoconf_product); product_config = configProducts.at(autoconf_product);
} catch (std::out_of_range &e) { } else {
std::cerr << "autoconf: Unable to find config for " << autoconf_product << std::endl; if (found_hat) {
exit(EXIT_FAILURE); product_config =
cleanupNameForAutoconf("lora-hat-" + std::string(hat_vendor) + "-" + autoconf_product + ".yaml");
} else if (found_ch341) {
product_config = cleanupNameForAutoconf("lora-usb-" + std::string(autoconf_product) + ".yaml");
}
// Don't try to automatically find config for a device with RAK eeprom.
if (found_rak_eeprom) {
std::cerr << "autoconf: Found unknown RAK product " << autoconf_product << std::endl;
exit(EXIT_FAILURE);
}
if (access((portduino_config.available_directory + product_config).c_str(), R_OK) != 0) {
std::cerr << "autoconf: Unable to find config for " << autoconf_product << "(tried " << product_config << ")"
<< std::endl;
exit(EXIT_FAILURE);
}
} }
if (loadConfig((portduino_config.available_directory + product_config).c_str())) { if (loadConfig((portduino_config.available_directory + product_config).c_str())) {
std::cout << "autoconf: Using " << product_config << " as config file for " << autoconf_product << std::endl; std::cout << "autoconf: Using " << product_config << " as config file for " << autoconf_product << std::endl;
} else { } else {

View File

@@ -144,4 +144,6 @@ class Power : private concurrency::OSThread
#endif #endif
}; };
void battery_adcEnable();
extern Power *power; extern Power *power;

View File

@@ -210,6 +210,16 @@ No longer populated on PCB
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0
#define ADC_MULTIPLIER (4.916F) #define ADC_MULTIPLIER (4.916F)
// rf52840 AIN2 = Pin 4
#define BATTERY_LPCOMP_INPUT NRF_LPCOMP_INPUT_2
// We have AIN2 with a VBAT divider so AIN2 = VBAT * (100/490)
// We have the device going deep sleep under 3.1V, which is AIN2 = 0.63V
// So we can wake up when VBAT>=VDD is restored to 3.3V, where AIN2 = 0.67V
// Ratio 0.67/3.3 = 0.20, so we can pick a bit higher, 2/8 VDD, which means
// VBAT=4.04V
#define BATTERY_LPCOMP_THRESHOLD NRF_LPCOMP_REF_SUPPLY_2_8
#define HAS_RTC 0 #define HAS_RTC 0
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -267,6 +267,20 @@ SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 #define VBAT_AR_INTERNAL AR_INTERNAL_3_0
#define ADC_MULTIPLIER 1.73 #define ADC_MULTIPLIER 1.73
// RAK4630 AIN0 = nrf52840 AIN3 = Pin 5
#define BATTERY_LPCOMP_INPUT NRF_LPCOMP_INPUT_3
// We have AIN3 with a VBAT divider so AIN3 = VBAT * (1.5/2.5)
// We have the device going deep sleep under 3.1V, which is AIN3 = 1.86V
// So we can wake up when VBAT>=VDD is restored to 3.3V, where AIN3 = 1.98V
// 1.98/3.3 = 6/10, but that's close to the VBAT divider, so we
// pick 6/8VDD, which means VBAT=4.1V.
// Reference:
// VDD=3.3V AIN3=5/8*VDD=2.06V VBAT=1.66*AIN3=3.41V
// VDD=3.3V AIN3=11/16*VDD=2.26V VBAT=1.66*AIN3=3.76V
// VDD=3.3V AIN3=6/8*VDD=2.47V VBAT=1.66*AIN3=4.1V
#define BATTERY_LPCOMP_THRESHOLD NRF_LPCOMP_REF_SUPPLY_11_16
#define HAS_RTC 1 #define HAS_RTC 1
#define HAS_ETHERNET 1 #define HAS_ETHERNET 1

View File

@@ -1,4 +1,4 @@
[VERSION] [VERSION]
major = 2 major = 2
minor = 7 minor = 7
build = 14 build = 15