Compare commits

..

11 Commits

Author SHA1 Message Date
HarukiToreda
89a35e302d TomThumb Font 2025-11-19 01:54:26 -05:00
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
32 changed files with 829 additions and 176 deletions

View File

@@ -33,8 +33,6 @@ lib_deps =
adafruit/Adafruit seesaw Library@1.7.9
# 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
# renovate: datasource=custom.pio depName=adafruit/Adafruit BME680 Library packageName=adafruit/library/Adafruit BME680
adafruit/Adafruit BME680 Library@^2.0.5
build_flags =
${arduino_base.build_flags}

View File

@@ -87,6 +87,9 @@
</screenshots>
<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">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.14</url>
</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
* Version 2.7.14

View File

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

View File

@@ -410,18 +410,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#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
// -----------------------------------------------------------------------------

View File

@@ -240,6 +240,9 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
buffer[bytesRead] = b;
bytesRead++;
if ((bytesRead == 767) || (b == '\r')) {
#ifdef GPS_DEBUG
LOG_DEBUG(debugmsg.c_str());
#endif
if (strnstr((char *)buffer, message, bytesRead) != nullptr) {
#ifdef GPS_DEBUG
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;
} else {
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));
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)
_serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
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')) {
#ifdef GPS_DEBUG
LOG_DEBUG(response);
#endif
// check if we can see our chips
for (const auto &chipInfo : responseMap) {
if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
#ifdef GPS_DEBUG
LOG_DEBUG(response);
#endif
LOG_INFO("%s detected", chipInfo.chipName.c_str());
delete[] response; // Cleanup before return
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') {
#ifdef GPS_DEBUG
LOG_DEBUG(response);
#endif
// Reset the response buffer for the next potential message
responseLen = 0;
response[0] = '\0';
@@ -1572,8 +1593,6 @@ GPS *GPS::createGps()
#ifdef PIN_GPS_RESET
pinMode(PIN_GPS_RESET, OUTPUT);
digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms
delay(10);
digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
#endif

View File

@@ -310,7 +310,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
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();
}
return RTCSetResultInvalidTime;
@@ -319,7 +319,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
// Calculate max allowed time safely to avoid overflow in logging
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
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);
lastTimeValidationWarning = millis();
}

View File

@@ -103,3 +103,44 @@
#define FONT_HEIGHT_SMALL _fontHeight(FONT_SMALL)
#define FONT_HEIGHT_MEDIUM _fontHeight(FONT_MEDIUM)
#define FONT_HEIGHT_LARGE _fontHeight(FONT_LARGE)
// ============================================================================
// FINAL OVERRIDE: Force TomThumb font
// ============================================================================
#ifdef DISPLAY_FORCE_TOMTHUMB_FONT
#include "graphics/fonts/OLEDDisplayFontsTomThumb.h"
// -----------------------------
// Replace all Meshtastic fonts
// -----------------------------
#undef FONT_SMALL_LOCAL
#undef FONT_MEDIUM_LOCAL
#undef FONT_LARGE_LOCAL
#undef FONT_SMALL
#undef FONT_MEDIUM
#undef FONT_LARGE
#define FONT_SMALL_LOCAL TomThumb4x6
#define FONT_MEDIUM_LOCAL TomThumb4x6
#define FONT_LARGE_LOCAL TomThumb4x6
#define FONT_SMALL TomThumb4x6
#define FONT_MEDIUM TomThumb4x6
#define FONT_LARGE TomThumb4x6
// -------------------------------------------------------
// Override the *line height used for spacing*, NOT glyphs
// TomThumb is 6 px tall → we give it 11 px layout height
// -------------------------------------------------------
#undef FONT_HEIGHT_SMALL
#undef FONT_HEIGHT_MEDIUM
#undef FONT_HEIGHT_LARGE
#define FONT_HEIGHT_SMALL 6
#define FONT_HEIGHT_MEDIUM 6
#define FONT_HEIGHT_LARGE 6
#endif
// ============================================================================

View File

@@ -380,6 +380,17 @@ const int *getTextPositions(OLEDDisplay *display)
{
static int textPositions[7]; // Static array that persists beyond function scope
#ifdef DISPLAY_FORCE_TOMTHUMB_FONT
textPositions[0] = textZeroLine;
textPositions[1] = textFirstLine_tiny;
textPositions[2] = textSecondLine_tiny;
textPositions[3] = textThirdLine_tiny;
textPositions[4] = textFourthLine_tiny;
textPositions[5] = textFifthLine_tiny;
textPositions[6] = textSixthLine_tiny;
return textPositions;
#endif
if (isHighResolution) {
textPositions[0] = textZeroLine;
textPositions[1] = textFirstLine_medium;

View File

@@ -35,6 +35,21 @@ namespace graphics
#define textFifthLine_large (textFourthLine_large + (FONT_HEIGHT_SMALL + 5))
#define textSixthLine_large (textFifthLine_large + (FONT_HEIGHT_SMALL + 5))
// Tiny Font Spacing (TomThumb)
// Only active when DISPLAY_FORCE_TOMTHUMB_FONT is defined
#ifdef DISPLAY_FORCE_TOMTHUMB_FONT
#define TINY_REDUCE 5
#define textFirstLine_tiny (FONT_HEIGHT_SMALL + 1)
#define textSecondLine_tiny (textFirstLine_tiny + (FONT_HEIGHT_SMALL - TINY_REDUCE + 5))
#define textThirdLine_tiny (textSecondLine_tiny + (FONT_HEIGHT_SMALL - TINY_REDUCE + 5))
#define textFourthLine_tiny (textThirdLine_tiny + (FONT_HEIGHT_SMALL - TINY_REDUCE + 5))
#define textFifthLine_tiny (textFourthLine_tiny + (FONT_HEIGHT_SMALL - TINY_REDUCE + 5))
#define textSixthLine_tiny (textFifthLine_tiny + (FONT_HEIGHT_SMALL - TINY_REDUCE + 5))
#endif
// Quick screen access
#define SCREEN_WIDTH display->getWidth()
#define SCREEN_HEIGHT display->getHeight()

View File

@@ -194,17 +194,12 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
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
char timeString[16];
int hour = 0;
int minute = 0;
int second = 0;
if (rtc_sec > 0) {
long hms = rtc_sec % 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;
// hour = hour > 12 ? hour - 12 : hour;
if (config.display.use_12h_clock) {
hour %= 12;
if (hour == 0)
if (hour == 0) {
hour = 12;
}
snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute);
} else {
snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute);
@@ -229,24 +224,56 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
char secondString[8];
snprintf(secondString, sizeof(secondString), "%02d", second);
#ifdef T_WATCH_S3
float scale = 1.5;
#elif defined(CHATTER_2)
float scale = 1.1;
#else
float scale = 0.75;
if (isHighResolution) {
scale = 1.5;
}
#endif
static bool scaleInitialized = false;
static float scale = 0.75f;
static float segmentWidth = SEGMENT_WIDTH * 0.75f;
static float segmentHeight = SEGMENT_HEIGHT * 0.75f;
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
if (!scaleInitialized) {
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
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];
if (character == ':') {
@@ -257,19 +284,21 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
}
uint16_t hourMinuteTextX = (display->getWidth() / 2) - (timeStringWidth / 2);
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
for (uint8_t i = 0; i < strlen(timeString); i++) {
for (size_t i = 0; i < len; i++) {
char character = timeString[i];
if (character == ':') {
drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale);
hourMinuteTextX += segmentHeight + 6;
if (scale >= 2.0f) {
hourMinuteTextX += (uint16_t)(4.5f * scale);
}
} else {
drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character - '0', scale);
@@ -279,38 +308,29 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
hourMinuteTextX += 5;
}
// draw seconds string
// draw seconds string + AM/PM
display->setFont(FONT_SMALL);
int xOffset = (isHighResolution) ? 0 : -1;
if (hour >= 10) {
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) {
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2,
isPM ? "pm" : "am");
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - 1, isPM ? "pm" : "am");
}
#ifndef USE_EINK
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);
#endif
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
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);
int line = 0;
#ifdef T_WATCH_S3
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14);
}
#endif
// clock face center coordinates
int16_t centerX = display->getWidth() / 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
// 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

View File

@@ -581,11 +581,8 @@ void menuHandler::systemBaseMenu()
optionsArray[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";
optionsEnumArray[options++] = ScreenOptions;
#endif
#if defined(M5STACK_UNITC6L)
optionsArray[options] = "Bluetooth";
@@ -785,17 +782,24 @@ void menuHandler::nodeNameLengthMenu()
void menuHandler::resetNodeDBMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
static const char *optionsArray[] = {"Back", "Reset All", "Preserve Favorites"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Confirm Reset NodeDB";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 1) {
if (selected == 1 || selected == 2) {
disableBluetooth();
screen->setFrames(Screen::FOCUS_DEFAULT);
}
if (selected == 1) {
LOG_INFO("Initiate node-db reset");
nodeDB->resetNodes();
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);

View File

@@ -24,7 +24,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "configuration.h"
#if HAS_SCREEN
#include "MessageRenderer.h"
#ifdef DISPLAY_FORCE_TOMTHUMB_FONT
#define MESSAGE_TINY_Y_OFFSET -3
#else
#define MESSAGE_TINY_Y_OFFSET 0
#endif
// Core includes
#include "NodeDB.h"
#include "configuration.h"

View File

@@ -424,7 +424,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
EntryRenderer renderer, NodeExtrasRenderer extras, float heading, double lat, double lon)
{
const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
const int rowYOffset = FONT_HEIGHT_SMALL - 3;
int rowYOffset = FONT_HEIGHT_SMALL - 3;
#ifdef DISPLAY_FORCE_TOMTHUMB_FONT
rowYOffset += 4;
#endif
bool locationScreen = false;
if (strcmp(title, "Bearings") == 0)
@@ -443,6 +447,9 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
// Space below header
y += COMMON_HEADER_HEIGHT;
#ifdef DISPLAY_FORCE_TOMTHUMB_FONT
y += 2; // Push entire list down by 2 pixels for TomThumb
#endif
int totalEntries = nodeDB->getNumMeshNodes();
int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;

View File

@@ -278,6 +278,9 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
uint16_t totalLines = lineCount + alertBannerOptions;
uint16_t screenHeight = display->height();
uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3;
#ifdef DISPLAY_FORCE_TOMTHUMB_FONT
effectiveLineHeight = FONT_HEIGHT_SMALL + 2;
#endif
uint8_t visibleTotalLines = std::min<uint8_t>(totalLines, (screenHeight - vPadding * 2) / effectiveLineHeight);
uint8_t linesShown = lineCount;
const char *linePointers[visibleTotalLines + 1] = {0}; // this is sort of a dynamic allocation
@@ -408,6 +411,9 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
uint16_t screenHeight = display->height();
uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3;
#ifdef DISPLAY_FORCE_TOMTHUMB_FONT
effectiveLineHeight = FONT_HEIGHT_SMALL + 2;
#endif
uint8_t visibleTotalLines = std::min<uint8_t>(totalLines, (screenHeight - vPadding * 2) / effectiveLineHeight);
uint8_t linesShown = lineCount;
const char *linePointers[visibleTotalLines + 1] = {0}; // this is sort of a dynamic allocation
@@ -633,6 +639,9 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
uint16_t screenHeight = display->height();
uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3;
#ifdef DISPLAY_FORCE_TOMTHUMB_FONT
effectiveLineHeight = FONT_HEIGHT_SMALL + 2;
#endif
uint8_t visibleTotalLines = std::min<uint8_t>(lineCount, (screenHeight - vPadding * 2) / effectiveLineHeight);
uint16_t contentHeight = visibleTotalLines * effectiveLineHeight;
uint16_t boxHeight = contentHeight + vPadding * 2;
@@ -664,6 +673,9 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
// === Draw Content ===
int16_t lineY = boxTop + vPadding;
#ifdef DISPLAY_FORCE_TOMTHUMB_FONT
lineY += 2; // Offset entire options list downward
#endif
for (int i = 0; i < lineCount; i++) {
int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2;
if (needs_bell && i == 0) {

View File

@@ -0,0 +1,410 @@
#include "OLEDDisplayFontsTomThumb.h"
const uint8_t TomThumb4x6[] PROGMEM = {
0x05, // heightMinus1 = 5 → height = 6
0x04, // width (unused by Meshtastic, but must exist)
0x20, // first char
0xBD, // last char
// Jump Table:
0xFF, 0xFF, 0x00, 0x04, // space (advance 3->4)
0x00, 0x00, 0x02, 0x04, // exclam (advance 3->4)
0x00, 0x02, 0x03, 0x04, // quotedbl (advance 3->4)
0x00, 0x05, 0x03, 0x04, // numbersign (advance 3->4)
0x00, 0x08, 0x03, 0x04, // dollar (advance 3->4)
0x00, 0x0B, 0x03, 0x04, // percent (advance 3->4)
0x00, 0x0E, 0x03, 0x04, // ampersand (advance 3->4)
0x00, 0x11, 0x02, 0x04, // quotesingle (advance 3->4)
0x00, 0x13, 0x03, 0x04, // parenleft (advance 3->4)
0x00, 0x16, 0x02, 0x04, // parenright (advance 3->4)
0x00, 0x18, 0x03, 0x04, // asterisk (advance 3->4)
0x00, 0x1B, 0x03, 0x04, // plus (advance 3->4)
0x00, 0x1E, 0x02, 0x04, // comma (advance 3->4)
0x00, 0x20, 0x03, 0x04, // hyphen (advance 3->4)
0x00, 0x23, 0x02, 0x04, // period (advance 3->4)
0x00, 0x25, 0x03, 0x04, // slash (advance 3->4)
0x00, 0x28, 0x03, 0x04, // zero (advance 3->4)
0x00, 0x2B, 0x02, 0x04, // one (advance 3->4)
0x00, 0x2D, 0x03, 0x04, // two (advance 3->4)
0x00, 0x30, 0x03, 0x04, // three (advance 3->4)
0x00, 0x33, 0x03, 0x04, // four (advance 3->4)
0x00, 0x36, 0x03, 0x04, // five (advance 3->4)
0x00, 0x39, 0x03, 0x04, // six (advance 3->4)
0x00, 0x3C, 0x03, 0x04, // seven (advance 3->4)
0x00, 0x3F, 0x03, 0x04, // eight (advance 3->4)
0x00, 0x42, 0x03, 0x04, // nine (advance 3->4)
0x00, 0x45, 0x02, 0x04, // colon (advance 3->4)
0x00, 0x47, 0x02, 0x04, // semicolon (advance 3->4)
0x00, 0x49, 0x03, 0x04, // less (advance 3->4)
0x00, 0x4C, 0x03, 0x04, // equal (advance 3->4)
0x00, 0x4F, 0x03, 0x04, // greater (advance 3->4)
0x00, 0x52, 0x03, 0x04, // question (advance 3->4)
0x00, 0x55, 0x03, 0x04, // at (advance 3->4)
0x00, 0x58, 0x03, 0x04, // A (advance 3->4)
0x00, 0x5B, 0x03, 0x04, // B (advance 3->4)
0x00, 0x5E, 0x03, 0x04, // C (advance 3->4)
0x00, 0x61, 0x03, 0x04, // D (advance 3->4)
0x00, 0x64, 0x03, 0x04, // E (advance 3->4)
0x00, 0x67, 0x03, 0x04, // F (advance 3->4)
0x00, 0x6A, 0x03, 0x04, // G (advance 3->4)
0x00, 0x6D, 0x03, 0x04, // H (advance 3->4)
0x00, 0x70, 0x03, 0x04, // I (advance 3->4)
0x00, 0x73, 0x03, 0x04, // J (advance 3->4)
0x00, 0x76, 0x03, 0x04, // K (advance 3->4)
0x00, 0x79, 0x03, 0x04, // L (advance 3->4)
0x00, 0x7C, 0x03, 0x04, // M (advance 3->4)
0x00, 0x7F, 0x03, 0x04, // N (advance 3->4)
0x00, 0x82, 0x03, 0x04, // O (advance 3->4)
0x00, 0x85, 0x03, 0x04, // P (advance 3->4)
0x00, 0x88, 0x03, 0x04, // Q (advance 3->4)
0x00, 0x8B, 0x03, 0x04, // R (advance 3->4)
0x00, 0x8E, 0x03, 0x04, // S (advance 3->4)
0x00, 0x91, 0x03, 0x04, // T (advance 3->4)
0x00, 0x94, 0x03, 0x04, // U (advance 3->4)
0x00, 0x97, 0x03, 0x04, // V (advance 3->4)
0x00, 0x9A, 0x03, 0x04, // W (advance 3->4)
0x00, 0x9D, 0x03, 0x04, // X (advance 3->4)
0x00, 0xA0, 0x03, 0x04, // Y (advance 3->4)
0x00, 0xA3, 0x03, 0x04, // Z (advance 3->4)
0x00, 0xA6, 0x03, 0x04, // bracketleft (advance 3->4)
0x00, 0xA9, 0x03, 0x04, // backslash (advance 3->4)
0x00, 0xAC, 0x03, 0x04, // bracketright (advance 3->4)
0x00, 0xAF, 0x03, 0x04, // asciicircum (advance 3->4)
0x00, 0xB2, 0x03, 0x04, // underscore (advance 3->4)
0x00, 0xB5, 0x02, 0x04, // grave (advance 3->4)
0x00, 0xB7, 0x03, 0x04, // a (advance 3->4)
0x00, 0xBA, 0x03, 0x04, // b (advance 3->4)
0x00, 0xBD, 0x03, 0x04, // c (advance 3->4)
0x00, 0xC0, 0x03, 0x04, // d (advance 3->4)
0x00, 0xC3, 0x03, 0x04, // e (advance 3->4)
0x00, 0xC6, 0x03, 0x04, // f (advance 3->4)
0x00, 0xC9, 0x03, 0x04, // g (advance 3->4)
0x00, 0xCC, 0x03, 0x04, // h (advance 3->4)
0x00, 0xCF, 0x02, 0x04, // i (advance 3->4)
0x00, 0xD1, 0x03, 0x04, // j (advance 3->4)
0x00, 0xD4, 0x03, 0x04, // k (advance 3->4)
0x00, 0xD7, 0x03, 0x04, // l (advance 3->4)
0x00, 0xDA, 0x03, 0x04, // m (advance 3->4)
0x00, 0xDD, 0x03, 0x04, // n (advance 3->4)
0x00, 0xE0, 0x03, 0x04, // o (advance 3->4)
0x00, 0xE3, 0x03, 0x04, // p (advance 3->4)
0x00, 0xE6, 0x03, 0x04, // q (advance 3->4)
0x00, 0xE9, 0x03, 0x04, // r (advance 3->4)
0x00, 0xEC, 0x03, 0x04, // s (advance 3->4)
0x00, 0xEF, 0x03, 0x04, // t (advance 3->4)
0x00, 0xF2, 0x03, 0x04, // u (advance 3->4)
0x00, 0xF5, 0x03, 0x04, // v (advance 3->4)
0x00, 0xF8, 0x03, 0x04, // w (advance 3->4)
0x00, 0xFB, 0x03, 0x04, // x (advance 3->4)
0x00, 0xFE, 0x03, 0x04, // y (advance 3->4)
0x01, 0x01, 0x03, 0x04, // z (advance 3->4)
0x01, 0x04, 0x03, 0x04, // braceleft (advance 3->4)
0x01, 0x07, 0x02, 0x04, // bar (advance 3->4)
0x01, 0x09, 0x03, 0x04, // braceright (advance 3->4)
0x01, 0x0C, 0x03, 0x04, // asciitilde (advance 3->4)
0x01, 0x0F, 0x02, 0x04, // exclamdown (advance 3->4)
0x01, 0x11, 0x03, 0x04, // cent
0x01, 0x14, 0x03, 0x04, // sterling
0x01, 0x17, 0x03, 0x04, // currency
0x01, 0x1A, 0x03, 0x04, // yen
0x01, 0x1D, 0x02, 0x04, // brokenbar
0x01, 0x1F, 0x03, 0x04, // section
0x01, 0x22, 0x03, 0x04, // dieresis
0x01, 0x25, 0x03, 0x04, // copyright
0x01, 0x28, 0x03, 0x04, // ordfeminine
0x01, 0x2B, 0x02, 0x04, // guillemotleft
0x01, 0x2D, 0x03, 0x04, // logicalnot
0x01, 0x30, 0x02, 0x04, // softhyphen
0x01, 0x32, 0x03, 0x04, // registered
0x01, 0x35, 0x03, 0x04, // macron
0x01, 0x38, 0x03, 0x04, // degree
0x01, 0x3B, 0x03, 0x04, // plusminus
0x01, 0x3E, 0x03, 0x04, // twosuperior
0x01, 0x41, 0x03, 0x04, // threesuperior
0x01, 0x44, 0x03, 0x04, // acute
0x01, 0x47, 0x03, 0x04, // mu
0x01, 0x4A, 0x03, 0x04, // paragraph
0x01, 0x4D, 0x03, 0x04, // periodcentered
0x01, 0x50, 0x03, 0x04, // cedilla
0x01, 0x53, 0x02, 0x04, // onesuperior
0x01, 0x55, 0x03, 0x04, // ordmasculine
0x01, 0x58, 0x03, 0x04, // guillemotright
0x01, 0x5B, 0x03, 0x04, // onequarter
0x01, 0x5E, 0x03, 0x04, // onehalf
0x01, 0x61, 0x03, 0x04, // threequarters
0x01, 0x64, 0x03, 0x04, // questiondown
0x01, 0x67, 0x03, 0x04, // Agrave
0x01, 0x6A, 0x03, 0x04, // Aacute
0x01, 0x6D, 0x03, 0x04, // Acircumflex
0x01, 0x70, 0x03, 0x04, // Atilde
0x01, 0x73, 0x03, 0x04, // Adieresis
0x01, 0x76, 0x03, 0x04, // Aring
0x01, 0x79, 0x03, 0x04, // AE
0x01, 0x7C, 0x03, 0x04, // Ccedilla
0x01, 0x7F, 0x03, 0x04, // Egrave
0x01, 0x82, 0x03, 0x04, // Eacute
0x01, 0x85, 0x03, 0x04, // Ecircumflex
0x01, 0x88, 0x03, 0x04, // Edieresis
0x01, 0x8B, 0x03, 0x04, // Igrave
0x01, 0x8E, 0x03, 0x04, // Iacute
0x01, 0x91, 0x03, 0x04, // Icircumflex
0x01, 0x94, 0x03, 0x04, // Idieresis
0x01, 0x97, 0x03, 0x04, // Eth
0x01, 0x9A, 0x03, 0x04, // Ntilde
0x01, 0x9D, 0x03, 0x04, // Ograve
0x01, 0xA0, 0x03, 0x04, // Oacute
0x01, 0xA3, 0x03, 0x04, // Ocircumflex
0x01, 0xA6, 0x03, 0x04, // Otilde
0x01, 0xA9, 0x03, 0x04, // Odieresis
0x01, 0xAC, 0x03, 0x04, // multiply
0x01, 0xAF, 0x03, 0x04, // Oslash
0x01, 0xB2, 0x03, 0x04, // Ugrave
0x01, 0xB5, 0x03, 0x04, // Uacute
0x01, 0xB8, 0x03, 0x04, // Ucircumflex
0x01, 0xBB, 0x03, 0x04, // Udieresis
0x01, 0xBE, 0x03, 0x04, // Yacute
0x01, 0xC1, 0x03, 0x04, // Thorn
0x01, 0xC4, 0x03, 0x04, // germandbls
0x01, 0xC7, 0x03, 0x04, // agrave
0x01, 0xCA, 0x03, 0x04, // aacute
0x01, 0xCD, 0x03, 0x04, // acircumflex
0x01, 0xD0, 0x03, 0x04, // atilde
0x01, 0xD3, 0x03, 0x04, // adieresis
0x01, 0xD6, 0x03, 0x04, // aring
0x01, 0xD9, 0x03, 0x04, // ae
0x01, 0xDC, 0x03, 0x04, // ccedilla
0x01, 0xDF, 0x03, 0x04, // egrave
0x01, 0xE2, 0x03, 0x04, // eacute
0x01, 0xE5, 0x03, 0x04, // ecircumflex
0x01, 0xE8, 0x03, 0x04, // edieresis
0x01, 0xEB, 0x03, 0x04, // igrave
0x01, 0xEE, 0x02, 0x04, // iacute
0x01, 0xF0, 0x03, 0x04, // icircumflex
0x01, 0xF3, 0x03, 0x04, // idieresis
0x01, 0xF6, 0x03, 0x04, // eth
0x01, 0xF9, 0x03, 0x04, // ntilde
0x01, 0xFC, 0x03, 0x04, // ograve
0x01, 0xFF, 0x03, 0x04, // oacute
0x02, 0x02, 0x03, 0x04, // ocircumflex
0x02, 0x05, 0x03, 0x04, // otilde
0x02, 0x08, 0x03, 0x04, // odieresis
0x02, 0x0B, 0x03, 0x04, // divide
0x02, 0x0E, 0x03, 0x04, // oslash
0x02, 0x11, 0x03, 0x04, // ugrave
0x02, 0x14, 0x03, 0x04, // uacute
0x02, 0x17, 0x03, 0x04, // ucircumflex
0x02, 0x1A, 0x03, 0x04, // udieresis
0x02, 0x1D, 0x03, 0x04, // yacute
0x02, 0x20, 0x03, 0x04, // thorn
// =================
// Font Bitmap Data:
// =================
0x00, 0x17, // exclam
0x03, 0x00, 0x04, // quotedbl
0x1F, 0x0A, 0x1F, // numbersign
0x0A, 0x1F, 0x05, // dollar
0x09, 0x04, 0x12, // percent
0x0F, 0x17, 0x1C, // ampersand
0x00, 0x04, // quotesingle
0x00, 0x0E, 0x11, // parenleft
0x11, 0x0E, // parenright
0x05, 0x02, 0x05, // asterisk
0x04, 0x0E, 0x04, // plus
0x10, 0x08, // comma
0x04, 0x04, 0x04, // hyphen
0x00, 0x10, // period
0x18, 0x04, 0x04, // slash
0x1E, 0x11, 0x0F, // zero
0x02, 0x1F, // one
0x19, 0x15, 0x12, // two
0x11, 0x15, 0x0A, // three
0x07, 0x04, 0x1F, // four
0x17, 0x15, 0x09, // five
0x1E, 0x15, 0x1D, // six
0x19, 0x05, 0x04, // seven
0x1F, 0x15, 0x1F, // eight
0x17, 0x15, 0x0F, // nine
0x00, 0x0A, // colon
0x10, 0x0A, // semicolon
0x04, 0x0A, 0x11, // less
0x0A, 0x0A, 0x0A, // equal
0x11, 0x0A, 0x04, // greater
0x01, 0x15, 0x04, // question
0x0E, 0x15, 0x16, // at
0x1E, 0x05, 0x1E, // A
0x1F, 0x15, 0x0A, // B
0x0E, 0x11, 0x11, // C
0x1F, 0x11, 0x0E, // D
0x1F, 0x15, 0x15, // E
0x1F, 0x05, 0x05, // F
0x0E, 0x15, 0x1D, // G
0x1F, 0x04, 0x1F, // H
0x11, 0x1F, 0x11, // I
0x08, 0x10, 0x0F, // J
0x1F, 0x04, 0x1B, // K
0x1F, 0x10, 0x10, // L
0x1F, 0x06, 0x1F, // M
0x1F, 0x0E, 0x1F, // N
0x0E, 0x11, 0x0E, // O
0x1F, 0x05, 0x02, // P
0x0E, 0x19, 0x1E, // Q
0x1F, 0x0D, 0x16, // R
0x12, 0x15, 0x09, // S
0x01, 0x1F, 0x01, // T
0x0F, 0x10, 0x1F, // U
0x07, 0x18, 0x07, // V
0x1F, 0x0C, 0x1F, // W
0x1B, 0x04, 0x1B, // X
0x03, 0x1C, 0x04, // Y
0x19, 0x15, 0x13, // Z
0x1F, 0x11, 0x11, // bracketleft
0x02, 0x04, 0x08, // backslash
0x11, 0x11, 0x1F, // bracketright
0x02, 0x01, 0x02, // asciicircum
0x10, 0x10, 0x10, // underscore
0x01, 0x02, // grave
0x1A, 0x16, 0x1C, // a
0x1F, 0x12, 0x0C, // b
0x0C, 0x12, 0x12, // c
0x0C, 0x12, 0x1F, // d
0x0C, 0x1A, 0x16, // e
0x04, 0x1E, 0x05, // f
0x0C, 0x2A, 0x1E, // g
0x1F, 0x02, 0x1C, // h
0x00, 0x1D, // i
0x10, 0x20, 0x1D, // j
0x1F, 0x0C, 0x12, // k
0x11, 0x1F, 0x10, // l
0x1E, 0x0E, 0x1E, // m
0x1E, 0x02, 0x1C, // n
0x0C, 0x12, 0x0C, // o
0x3E, 0x12, 0x0C, // p
0x0C, 0x12, 0x3E, // q
0x1C, 0x02, 0x02, // r
0x14, 0x1E, 0x0A, // s
0x02, 0x1F, 0x12, // t
0x0E, 0x10, 0x1E, // u
0x0E, 0x18, 0x0E, // v
0x1E, 0x1C, 0x1E, // w
0x12, 0x0C, 0x12, // x
0x06, 0x28, 0x1E, // y
0x1A, 0x1E, 0x16, // z
0x04, 0x1B, 0x11, // braceleft
0x00, 0x1B, // bar
0x11, 0x1B, 0x04, // braceright
0x02, 0x03, 0x01, // asciitilde
0x00, 0x1D, // exclamdown
0x0E, 0x1B, 0x0A, // cent
0x14, 0x1F, 0x15, // sterling
0x15, 0x0E, 0x15, // currency
0x0B, 0x1C, 0x0B, // yen
0x00, 0x1B, // brokenbar
0x14, 0x1B, 0x05, // section
0x01, 0x00, 0x01, // dieresis
0x02, 0x05, 0x05, // copyright
0x16, 0x15, 0x17, // ordfeminine
0x02, 0x05, // guillemotleft
0x02, 0x02, 0x06, // logicalnot
0x04, 0x04, // softhyphen
0x07, 0x03, 0x04, // registered
0x01, 0x01, 0x01, // macron
0x02, 0x05, 0x02, // degree
0x12, 0x17, 0x12, // plusminus
0x01, 0x07, 0x04, // twosuperior
0x05, 0x07, 0x07, // threesuperior
0x00, 0x02, 0x01, // acute
0x1F, 0x08, 0x07, // mu
0x02, 0x1D, 0x1F, // paragraph
0x0E, 0x0E, 0x0E, // periodcentered
0x10, 0x14, 0x08, // cedilla
0x00, 0x07, // onesuperior
0x12, 0x15, 0x12, // ordmasculine
0x00, 0x05, 0x02, // guillemotright
0x03, 0x08, 0x18, // onequarter
0x0B, 0x18, 0x10, // onehalf
0x03, 0x0B, 0x18, // threequarters
0x18, 0x15, 0x10, // questiondown
0x18, 0x0D, 0x1A, // Agrave
0x1A, 0x0D, 0x18, // Aacute
0x19, 0x0D, 0x19, // Acircumflex
0x1A, 0x0F, 0x19, // Atilde
0x1D, 0x0A, 0x1D, // Adieresis
0x1F, 0x0B, 0x1C, // Aring
0x1E, 0x1F, 0x15, // AE
0x06, 0x29, 0x19, // Ccedilla
0x1C, 0x1D, 0x16, // Egrave
0x1E, 0x1D, 0x14, // Eacute
0x1D, 0x1D, 0x15, // Ecircumflex
0x1D, 0x1C, 0x15, // Edieresis
0x14, 0x1D, 0x16, // Igrave
0x16, 0x1D, 0x14, // Iacute
0x15, 0x1D, 0x15, // Icircumflex
0x15, 0x1C, 0x15, // Idieresis
0x1F, 0x15, 0x0E, // Eth
0x1D, 0x0B, 0x1E, // Ntilde
0x1C, 0x15, 0x1E, // Ograve
0x1E, 0x15, 0x1C, // Oacute
0x1D, 0x15, 0x1D, // Ocircumflex
0x1D, 0x17, 0x1E, // Otilde
0x1D, 0x14, 0x1D, // Odieresis
0x0A, 0x04, 0x0A, // multiply
0x1E, 0x15, 0x0F, // Oslash
0x1D, 0x12, 0x1C, // Ugrave
0x1C, 0x12, 0x1D, // Uacute
0x1D, 0x11, 0x1D, // Ucircumflex
0x1D, 0x10, 0x1D, // Udieresis
0x0C, 0x1A, 0x0D, // Yacute
0x1F, 0x0A, 0x0E, // Thorn
0x3E, 0x15, 0x0B, // germandbls
0x18, 0x15, 0x1E, // agrave
0x1A, 0x15, 0x1C, // aacute
0x19, 0x15, 0x1D, // acircumflex
0x1A, 0x17, 0x1D, // atilde
0x19, 0x14, 0x1D, // adieresis
0x18, 0x17, 0x1F, // aring
0x1C, 0x1E, 0x0E, // ae
0x04, 0x2A, 0x1A, // ccedilla
0x08, 0x1D, 0x1E, // egrave
0x0A, 0x1D, 0x1C, // eacute
0x09, 0x1D, 0x1D, // ecircumflex
0x09, 0x1C, 0x1D, // edieresis
0x00, 0x1D, 0x02, // igrave
0x02, 0x1D, // iacute
0x01, 0x1D, 0x01, // icircumflex
0x01, 0x1C, 0x01, // idieresis
0x0A, 0x17, 0x1D, // eth
0x1D, 0x07, 0x1A, // ntilde
0x08, 0x15, 0x0A, // ograve
0x0A, 0x15, 0x08, // oacute
0x09, 0x15, 0x09, // ocircumflex
0x09, 0x17, 0x0A, // otilde
0x09, 0x14, 0x09, // odieresis
0x04, 0x15, 0x04, // divide
0x1C, 0x16, 0x0E, // oslash
0x0D, 0x12, 0x1C, // ugrave
0x0C, 0x12, 0x1D, // uacute
0x0D, 0x11, 0x1D, // ucircumflex
0x0D, 0x10, 0x1D, // udieresis
0x04, 0x2A, 0x1D, // yacute
0x3E, 0x14, 0x08 // thorn
};
// ============================================================================
// FONT_INFO wrapper required by Meshtastic
// ============================================================================
//
// NOTE:
// Meshtastic OLED renderer does *not* use the FONT_CHAR_INFO jump table when
// the font uses the raw-array jump table format. But this struct MUST exist.
//
static const FONT_CHAR_INFO TomThumb4x6_CharInfo[] PROGMEM = {};
// ============================================================================
// Final FONT_INFO Export
// ============================================================================
const FONT_INFO TomThumb4x6_Info = {.heightBits = 6, // REAL glyph height
.baseline = 4, // Correct baseline for 6px font
.startChar = 0x20,
.endChar = 0xBD,
.charInfo = TomThumb4x6_CharInfo,
.data = TomThumb4x6};

View File

@@ -0,0 +1,32 @@
#pragma once
#include <Arduino.h>
#ifdef __cplusplus
extern "C" {
#endif
// Information about a single character
typedef struct {
uint8_t widthBits; // Glyph width in bits
uint16_t offset; // Offset into the bitmap table
} FONT_CHAR_INFO;
// Information about the whole font
typedef struct {
uint8_t heightBits; // Character height in pixels (6px)
uint8_t baseline; // baseline (height-1) = 5
uint8_t startChar; // First supported char = 0x20
uint8_t endChar; // Last supported char = 0xBD
const FONT_CHAR_INFO *charInfo; // Jump table
const uint8_t *data; // Bitmap table
} FONT_INFO;
// Raw PROGMEM font data (jump table + bitmap stream)
extern const uint8_t TomThumb4x6[] PROGMEM;
// Wrapper combining the tables so OLED code can use it
extern const FONT_INFO TomThumb4x6_Info;
#ifdef __cplusplus
}
#endif

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()
: ProtobufModule("neighborinfo", meshtastic_PortNum_NEIGHBORINFO_APP, &meshtastic_NeighborInfo_msg),
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
Assumes that the neighborInfo packet has been allocated
Collect neighbor info from the nodeDB's history, capping at a maximum number of
entries and max time Assumes that the neighborInfo packet has been allocated
@returns the number of entries collected
*/
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)) {
neighborInfo->neighbors[neighborInfo->neighbors_count].node_id = nbr.node_id;
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
// the mesh
// Note: we don't set the last_rx_time and node_broadcast_intervals_secs
// here, because we don't want to send this over the mesh
neighborInfo->neighbors_count++;
}
}
@@ -88,8 +89,9 @@ void NeighborInfoModule::cleanUpNeighbors()
uint32_t now = getTime();
NodeNum my_node_id = nodeDB->getNodeNum();
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
// cannot use isWithinTimespanMs() as it->last_rx_time is seconds since 1970
// We will remove a neighbor if we haven't heard from them in twice the
// 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)) {
LOG_DEBUG("Remove neighbor with node ID 0x%x", it->node_id);
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);
}
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
Pass it to an upper client; do not persist this data on the mesh
*/
bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_NeighborInfo *np)
{
LOG_DEBUG("NeighborInfo: handleReceivedProtobuf");
if (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) {
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
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
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)
{
@@ -168,8 +200,10 @@ void NeighborInfoModule::resetNeighbors()
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
// an edge. So we assume that if it's zero, then this packet is from our node.
LOG_DEBUG("updateNeighbors");
// 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) {
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
neighbors[i].snr = snr;
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)
neighbors[i].node_broadcast_interval_secs = node_broadcast_interval_secs;
return &neighbors[i];
@@ -200,10 +235,12 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen
new_nbr.node_id = n;
new_nbr.snr = snr;
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)
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;
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;
/* 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
* @return the number of entries collected
@@ -66,5 +70,8 @@ class NeighborInfoModule : public ProtobufModule<meshtastic_NeighborInfo>, priva
/* These are for debugging only */
void printNeighborInfo(const char *header, const meshtastic_NeighborInfo *np);
void printNodeDBNeighbors();
private:
uint32_t lastSentReply = 0; // Last time we sent a position reply (used for reply throttling only)
};
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"
#endif
#if __has_include(MESHTASTIC_BME680_HEADER)
#if __has_include(<bsec2.h>)
#include "Sensor/BME680Sensor.h"
#endif
@@ -214,7 +214,7 @@ void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
#if __has_include(<Adafruit_LTR390.h>)
addSensor<LTR390UVSensor>(i2cScanner, ScanI2C::DeviceType::LTR390UV);
#endif
#if __has_include(MESHTASTIC_BME680_HEADER)
#if __has_include(<bsec2.h>)
addSensor<BME680Sensor>(i2cScanner, ScanI2C::DeviceType::BME_680);
#endif
#if __has_include(<Adafruit_BMP280.h>)

View File

@@ -1,6 +1,6 @@
#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 "BME680Sensor.h"
@@ -10,7 +10,6 @@
BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {}
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
int32_t BME680Sensor::runOnce()
{
if (!bme680.run()) {
@@ -18,13 +17,10 @@ int32_t BME680Sensor::runOnce()
}
return 35;
}
#endif // defined(MESHTASTIC_BME680_BSEC2_SUPPORTED)
bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
{
status = 0;
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
if (!bme680.begin(dev->address.address, *bus))
checkStatus("begin");
@@ -46,25 +42,12 @@ bool BME680Sensor::initDevice(TwoWire *bus, ScanI2C::FoundDevice *dev)
if (status == 0)
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();
return status;
}
bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
{
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
if (bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal == 0)
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)
measurement->variant.environment_metrics.iaq = bme680.getData(BSEC_OUTPUT_IAQ).signal;
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;
}
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
void BME680Sensor::loadState()
{
#ifdef FSCom
@@ -179,6 +144,5 @@ void BME680Sensor::checkStatus(const char *functionName)
else if (bme680.sensor.status > BME68X_OK)
LOG_WARN("%s BME68X code: %d", functionName, bme680.sensor.status);
}
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
#endif

View File

@@ -1,40 +1,23 @@
#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 "TelemetrySensor.h"
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
#include <bme68xLibrary.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()
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
const uint8_t bsec_config[] = {
#include "config/bme680/bme680_iaq_33v_3s_4d/bsec_iaq.txt"
};
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
class BME680Sensor : public TelemetrySensor
{
private:
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
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:
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
const char *bsecConfigFileName = "/prefs/bsec.dat";
uint8_t bsecState[BSEC_MAX_STATE_BLOB_SIZE] = {0};
uint8_t accuracy = 0;
@@ -51,13 +34,10 @@ class BME680Sensor : public TelemetrySensor
void loadState();
void updateState();
void checkStatus(const char *functionName);
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
public:
BME680Sensor();
#if MESHTASTIC_BME680_BSEC2_SUPPORTED == 1
virtual int32_t runOnce() override;
#endif // MESHTASTIC_BME680_BSEC2_SUPPORTED
virtual bool getMetrics(meshtastic_Telemetry *measurement) 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 bool isMqttServerAddressPrivate = false;
static bool isConnected = false;
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();
const bool connected = pubSub.connect(nodeId.c_str(), config.mqttUsername, config.mqttPassword);
if (connected) {
isConnected = true;
LOG_INFO("MQTT connected");
} else {
isConnected = false;
LOG_WARN("Failed to connect to MQTT server");
}
return connected;
@@ -507,6 +510,7 @@ bool MQTT::publish(const char *topic, const uint8_t *payload, size_t length, boo
void MQTT::reconnect()
{
isConnected = false;
if (wantsLink()) {
if (moduleConfig.mqtt.proxy_to_client_enabled) {
LOG_INFO("MQTT connect via client proxy instead");
@@ -534,7 +538,7 @@ void MQTT::reconnect()
runASAP = true;
reconnectCount = 0;
isMqttServerAddressPrivate = isPrivateIpAddress(clientConnection->remoteIP());
isConnected = true;
publishNodeInfo();
sendSubscriptions();
} else {
@@ -688,7 +692,7 @@ void MQTT::publishNodeInfo()
}
void MQTT::publishQueuedMessages()
{
if (mqttQueue.isEmpty())
if (mqttQueue.isEmpty() || !isConnected)
return;
LOG_DEBUG("Publish enqueued MQTT message");
@@ -895,4 +899,4 @@ void MQTT::perhapsReportToMap()
// Update the last report time
last_report_to_map = millis();
}
}

View File

@@ -14,6 +14,9 @@
#include "error.h"
#include "main.h"
#include "meshUtils.h"
#include "power.h"
#include <hal/nrf_lpcomp.h>
#ifdef BQ25703A_ADDR
#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
#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();
if (ok != NRF_SUCCESS) {
LOG_ERROR("FIXME: Ignoring soft device (EasyDMA pending?) and forcing system-off!");
@@ -420,4 +440,4 @@ void enterDfuMode()
#else
enterUf2Dfu();
#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
* use portduino specific init code (such as gpioBind) to setup portduino on their host machine,
* before running 'arduino' code.
@@ -218,6 +232,11 @@ void portduinoSetup()
// If LoRa `Module: auto` (default in config.yaml),
// attempt to auto config based on Product Strings
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};
// Try CH341
try {
@@ -227,21 +246,32 @@ void portduinoSetup()
ch341Hal->getProductString(autoconf_product, 95);
delete ch341Hal;
std::cout << "autoconf: Found CH341 device " << autoconf_product << std::endl;
found_ch341 = true;
} catch (...) {
std::cout << "autoconf: Could not locate CH341 device" << std::endl;
}
// Try Pi HAT+
if (strlen(autoconf_product) < 6) {
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) {
std::ifstream hatProductFile("/proc/device-tree/hat/product");
if (hatProductFile.is_open()) {
hatProductFile.read(autoconf_product, 95);
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 {
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
@@ -297,6 +327,7 @@ void portduinoSetup()
autoconf_product[0] = 0x0;
} else {
std::cout << "autoconf: Found eeprom data " << autoconf_raw << std::endl;
found_rak_eeprom = true;
if (mac_start != nullptr) {
std::cout << "autoconf: Found mac data " << mac_start << std::endl;
if (strlen(mac_start) == 12)
@@ -325,12 +356,29 @@ void portduinoSetup()
if (strlen(autoconf_product) > 0) {
// From configProducts map in PortduinoGlue.h
std::string product_config = "";
try {
if (configProducts.find(autoconf_product) != configProducts.end()) {
product_config = configProducts.at(autoconf_product);
} catch (std::out_of_range &e) {
std::cerr << "autoconf: Unable to find config for " << autoconf_product << std::endl;
exit(EXIT_FAILURE);
} else {
if (found_hat) {
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())) {
std::cout << "autoconf: Using " << product_config << " as config file for " << autoconf_product << std::endl;
} else {

View File

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

View File

@@ -1,6 +1,8 @@
#define LED_PIN LED
#define USE_SSD1306 // Heltec_v3 has a SSD1306 display
#define DISPLAY_FORCE_SMALL_FONTS
#define DISPLAY_FORCE_TOMTHUMB_FONT
#define RESET_OLED RST_OLED
#define I2C_SDA SDA_OLED // I2C pins for this board

View File

@@ -210,6 +210,16 @@ No longer populated on PCB
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
#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
#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 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_ETHERNET 1

View File

@@ -7,6 +7,12 @@
#define VARIANT_MCK (64000000ul) // Master clock frequency
#define USE_LFXO // 32.768kHz crystal for LFCLK
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Sample size change
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define DISPLAY_FORCE_SMALL_FONTS
#define DISPLAY_FORCE_TOMTHUMB_FONT
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Pin Capacity Definitions
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

View File

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