Merge branch 'master' into arduino-esp32-v3.2

This commit is contained in:
Manuel
2025-09-18 09:48:51 +02:00
committed by GitHub
47 changed files with 1728 additions and 117 deletions

View File

@@ -183,9 +183,9 @@ class AmbientLightingThread : public concurrency::OSThread
#endif
#endif
pixels.show();
LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d",
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
// LOG_DEBUG("Init NeoPixel Ambient light w/ brightness(current)=%d, red=%d, green=%d, blue=%d",
// moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red,
// moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif
#ifdef RGBLED_CA
analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red);

View File

@@ -294,6 +294,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
type = AHT10;
break;
#endif
#if !defined(M5STACK_UNITC6L)
case INA_ADDR:
case INA_ADDR_ALTERNATE:
case INA_ADDR_WAVESHARE_UPS:
@@ -340,6 +341,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
// else: probably a RAK12500/UBLOX GPS on I2C
}
break;
#endif
case MCP9808_ADDR:
// We need to check for STK8BAXX first, since register 0x07 is new data flag for the z-axis and can produce some
// weird result. and register 0x00 doesn't seems to be colliding with MCP9808 and LIS3DH chips.

View File

@@ -1205,7 +1205,7 @@ static const char *DETECTED_MESSAGE = "%s detected";
LOG_DEBUG(PROBE_MESSAGE, COMMAND, FAMILY_NAME); \
clearBuffer(); \
_serial_gps->write(COMMAND "\r\n"); \
GnssModel_t detectedDriver = getProbeResponse(TIMEOUT, RESPONSE_MAP); \
GnssModel_t detectedDriver = getProbeResponse(TIMEOUT, RESPONSE_MAP, serialSpeed); \
if (detectedDriver != GNSS_MODEL_UNKNOWN) { \
return detectedDriver; \
} \
@@ -1367,9 +1367,18 @@ GnssModel_t GPS::probe(int serialSpeed)
return GNSS_MODEL_UNKNOWN;
}
GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap)
GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap, int serialSpeed)
{
char response[256] = {0}; // Fixed buffer instead of String
// Calculate buffer size based on baud rate - 256 bytes for 9600 baud as baseline
// Higher baud rates get proportionally larger buffers to handle more data
int bufferSize = (serialSpeed * 256) / 9600;
// Clamp buffer size between reasonable limits
if (bufferSize < 128)
bufferSize = 128;
if (bufferSize > 2048)
bufferSize = 2048;
char *response = new char[bufferSize](); // Dynamically allocate based on baud rate
uint16_t responseLen = 0;
unsigned long start = millis();
while (millis() - start < timeout) {
@@ -1377,7 +1386,7 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipI
char c = _serial_gps->read();
// Add char to buffer if there's space
if (responseLen < sizeof(response) - 1) {
if (responseLen < bufferSize - 1) {
response[responseLen++] = c;
response[responseLen] = '\0';
}
@@ -1390,6 +1399,7 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipI
for (const auto &chipInfo : responseMap) {
if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
LOG_INFO("%s detected", chipInfo.chipName.c_str());
delete[] response; // Cleanup before return
return chipInfo.driver;
}
}
@@ -1404,6 +1414,7 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipI
#ifdef GPS_DEBUG
LOG_DEBUG(response);
#endif
delete[] response; // Cleanup before return
return GNSS_MODEL_UNKNOWN; // Return unknown on timeout
}

View File

@@ -236,7 +236,7 @@ class GPS : private concurrency::OSThread
virtual int32_t runOnce() override;
GnssModel_t getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap);
GnssModel_t getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap, int serialSpeed);
// Get GNSS model
GnssModel_t probe(int serialSpeed);

View File

@@ -9,6 +9,9 @@
static RTCQuality currentQuality = RTCQualityNone;
uint32_t lastSetFromPhoneNtpOrGps = 0;
static uint32_t lastTimeValidationWarning = 0;
static const uint32_t TIME_VALIDATION_WARNING_INTERVAL_MS = 15000; // 15 seconds
RTCQuality getRTCQuality()
{
return currentQuality;
@@ -48,7 +51,9 @@ RTCSetResult readFromRTC()
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
}
return RTCSetResultInvalidTime;
}
#endif
@@ -87,7 +92,10 @@ RTCSetResult readFromRTC()
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime;
}
#endif
@@ -130,11 +138,20 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
uint32_t printableEpoch = tv->tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
#ifdef BUILD_EPOCH
if (tv->tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime;
} else if (tv->tv_sec > (BUILD_EPOCH + FORTY_YEARS)) {
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH,
BUILD_EPOCH + FORTY_YEARS);
} else if ((uint64_t)tv->tv_sec > ((uint64_t)BUILD_EPOCH + FORTY_YEARS)) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
// 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,
(uint32_t)BUILD_EPOCH, maxAllowedPrintable);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime;
}
#endif
@@ -252,11 +269,20 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime;
} else if (tv.tv_sec > (BUILD_EPOCH + FORTY_YEARS)) {
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH,
BUILD_EPOCH + FORTY_YEARS);
} else if ((uint64_t)tv.tv_sec > ((uint64_t)BUILD_EPOCH + FORTY_YEARS)) {
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
// 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,
(uint32_t)BUILD_EPOCH, maxAllowedPrintable);
lastTimeValidationWarning = millis();
}
return RTCSetResultInvalidTime;
}
#endif
@@ -316,14 +342,40 @@ uint32_t getValidTime(RTCQuality minQuality, bool local)
time_t gm_mktime(struct tm *tm)
{
#if !MESHTASTIC_EXCLUDE_TZ
setenv("TZ", "GMT0", 1);
time_t res = mktime(tm);
if (*config.device.tzdef) {
setenv("TZ", config.device.tzdef, 1);
} else {
setenv("TZ", "UTC0", 1);
time_t result = 0;
// First, get us to the start of tm->year, by calcuating the number of days since the Unix epoch.
int year = 1900 + tm->tm_year; // tm_year is years since 1900
int year_minus_one = year - 1;
int days_before_this_year = 0;
days_before_this_year += year_minus_one * 365;
// leap days: every 4 years, except 100s, but including 400s.
days_before_this_year += year_minus_one / 4 - year_minus_one / 100 + year_minus_one / 400;
// subtract from 1970-01-01 to get days since epoch
days_before_this_year -= 719162; // (1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400);
// Now, within this tm->year, compute the days *before* this tm->month starts.
int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year
int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11
// If this is a leap year, and we're past February, add a day:
if (tm->tm_mon >= 2 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) {
days_this_year_before_this_month += 1;
}
return res;
// And within this month:
int days_this_month_before_today = tm->tm_mday - 1; // tm->tm_mday is 1..31
// Now combine them all together, and convert days to seconds:
result += (days_before_this_year + days_this_year_before_this_month + days_this_month_before_today);
result *= 86400L;
// Finally, add in the hours, minutes, and seconds of today:
result += tm->tm_hour * 3600;
result += tm->tm_min * 60;
result += tm->tm_sec;
return result;
#else
return mktime(tm);
#endif

View File

@@ -56,5 +56,5 @@ time_t gm_mktime(struct tm *tm);
#define SEC_PER_HOUR 3600
#define SEC_PER_MIN 60
#ifdef BUILD_EPOCH
#define FORTY_YEARS (40UL * 365 * SEC_PER_DAY) // probably time to update your firmware
static constexpr uint64_t FORTY_YEARS = (40ULL * 365 * SEC_PER_DAY); // Use 64-bit arithmetic to prevent overflow
#endif

View File

@@ -317,6 +317,14 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
#elif defined(USE_SSD1306)
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(USE_SPISSD1306)
dispdev = new SSD1306Spi(SSD1306_RESET, SSD1306_RS, SSD1306_NSS, GEOMETRY_64_48);
if (!dispdev->init()) {
LOG_DEBUG("Error: SSD1306 not detected!");
} else {
static_cast<SSD1306Spi *>(dispdev)->setHorizontalOffset(32);
LOG_INFO("SSD1306 init success");
}
#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \
defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
@@ -507,7 +515,7 @@ void Screen::setup()
// === Apply loaded brightness ===
#if defined(ST7789_CS)
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SPISSD1306)
dispdev->setBrightness(brightness);
#endif
LOG_INFO("Applied screen brightness: %d", brightness);
@@ -554,7 +562,7 @@ void Screen::setup()
static_cast<TFTDisplay *>(dispdev)->flipScreenVertically();
#elif defined(USE_ST7789)
static_cast<ST7789Spi *>(dispdev)->flipScreenVertically();
#else
#elif !defined(M5STACK_UNITC6L)
dispdev->flipScreenVertically();
#endif
}
@@ -692,7 +700,11 @@ int32_t Screen::runOnce()
#ifndef DISABLE_WELCOME_UNSET
if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
#if defined(M5STACK_UNITC6L)
menuHandler::LoraRegionPicker();
#else
menuHandler::OnboardMessage();
#endif
}
#endif
if (!NotificationRenderer::isOverlayBannerShowing() && rebootAtMsec != 0) {
@@ -890,8 +902,12 @@ void Screen::setFrames(FrameFocus focus)
#if defined(DISPLAY_CLOCK_FRAME)
fsi.positions.clock = numframes;
#if defined(M5STACK_UNITC6L)
normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame;
#else
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
: graphics::ClockRenderer::drawDigitalClockFrame;
#endif
indicatorIcons.push_back(digital_icon_clock);
#endif
@@ -1226,6 +1242,10 @@ void Screen::handleShowNextFrame()
void Screen::setFastFramerate()
{
#if defined(M5STACK_UNITC6L)
dispdev->clear();
dispdev->display();
#endif
// We are about to start a transition so speed up fps
targetFramerate = SCREEN_TRANSITION_FRAMERATE;
@@ -1297,13 +1317,23 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
}
} else {
if (longName && longName[0]) {
#if defined(M5STACK_UNITC6L)
strcpy(banner, "New Message");
#else
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
#endif
} else {
strcpy(banner, "New Message");
}
}
#if defined(M5STACK_UNITC6L)
screen->setOn(true);
screen->showSimpleBanner(banner, 1500);
playLongBeep();
#else
screen->showSimpleBanner(banner, 3000);
#endif
}
}
@@ -1386,7 +1416,11 @@ int Screen::handleInputEvent(const InputEvent *event)
if (devicestate.rx_text_message.from) {
menuHandler::messageResponseMenu();
} else {
#if defined(M5STACK_UNITC6L)
menuHandler::textMessageMenu();
#else
menuHandler::textMessageBaseMenu();
#endif
}
} else if (framesetInfo.positions.firstFavorite != 255 &&
this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&

View File

@@ -81,6 +81,8 @@ class Screen
#include <SSD1306Wire.h>
#elif defined(USE_ST7789)
#include <ST7789Spi.h>
#elif defined(USE_SPISSD1306)
#include <SSD1306Spi.h>
#else
// the SH1106/SSD1306 variant is auto-detected
#include <AutoOLEDWire.h>

View File

@@ -79,6 +79,10 @@
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
#define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
#elif defined(M5STACK_UNITC6L)
#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13
#define FONT_MEDIUM FONT_SMALL_LOCAL // Height: 13
#define FONT_LARGE FONT_SMALL_LOCAL // Height: 13
#else
#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13
#define FONT_MEDIUM FONT_MEDIUM_LOCAL // Height: 19

View File

@@ -124,7 +124,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int batteryX = 1;
int batteryY = HEADER_OFFSET_Y + 1;
#if !defined(M5STACK_UNITC6L)
// === Battery Icons ===
if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging
batteryX += 1;
@@ -337,7 +337,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
}
}
}
#endif
display->setColor(WHITE); // Reset for other UI
}

View File

@@ -277,12 +277,13 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
std::string uptime = UIRenderer::drawTimeDelta(days, hours, minutes, seconds);
// Line 1 (Still)
#if !defined(M5STACK_UNITC6L)
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
if (config.display.heading_bold)
display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
display->setColor(WHITE);
#endif
// Setup string to assemble analogClock string
std::string analogClock = "";
@@ -386,17 +387,24 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
char shortnameble[35];
getMacAddr(dmac);
snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]);
#if defined(M5STACK_UNITC6L)
snprintf(shortnameble, sizeof(shortnameble), "%s", screen->ourId);
#else
snprintf(shortnameble, sizeof(shortnameble), "BLE: %s", screen->ourId);
#endif
int textWidth = display->getStringWidth(shortnameble);
int nameX = (SCREEN_WIDTH - textWidth);
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
// === Second Row: Radio Preset ===
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
char regionradiopreset[25];
const char *region = myRegion ? myRegion->name : NULL;
if (region != nullptr) {
#if defined(M5STACK_UNITC6L)
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s", region);
#else
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode);
#endif
}
textWidth = display->getStringWidth(regionradiopreset);
nameX = (SCREEN_WIDTH - textWidth) / 2;
@@ -408,9 +416,17 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
float freq = RadioLibInterface::instance->getFreq();
snprintf(freqStr, sizeof(freqStr), "%.3f", freq);
if (config.lora.channel_num == 0) {
#if defined(M5STACK_UNITC6L)
snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz", freqStr);
#else
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz", freqStr);
#endif
} else {
#if defined(M5STACK_UNITC6L)
snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz (%d)", freqStr, config.lora.channel_num);
#else
snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num);
#endif
}
size_t len = strlen(frequencyslot);
if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) {
@@ -420,6 +436,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
#if !defined(M5STACK_UNITC6L)
// === Fourth Row: Channel Utilization ===
const char *chUtil = "ChUtil:";
char chUtilPercentage[10];
@@ -476,6 +493,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[4],
chUtilPercentage);
#endif
}
// ****************************
@@ -501,8 +519,11 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
#ifdef USE_EINK
barsOffset -= 12;
#endif
#if defined(M5STACK_UNITC6L)
const int barX = x + 45 + barsOffset;
#else
const int barX = x + 40 + barsOffset;
#endif
auto drawUsageRow = [&](const char *label, uint32_t used, uint32_t total, bool isHeap = false) {
if (total == 0)
return;
@@ -527,7 +548,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
// Label
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawString(labelX, getTextPositions(display)[line], label);
#if !defined(M5STACK_UNITC6L)
// Bar
int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2;
display->setColor(WHITE);
@@ -535,7 +556,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
display->fillRect(barX, barY, fillWidth, barHeight);
display->setColor(WHITE);
#endif
// Value string
display->setTextAlignment(TEXT_ALIGN_RIGHT);
display->drawString(SCREEN_WIDTH - 2, getTextPositions(display)[line], combinedStr);
@@ -588,10 +609,16 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
line += 1;
}
line += 1;
char appversionstr[35];
snprintf(appversionstr, sizeof(appversionstr), "Ver: %s", optstr(APP_VERSION));
char appversionstr_formatted[40];
char *lastDot = strrchr(appversionstr, '.');
#if defined(M5STACK_UNITC6L)
if (lastDot != nullptr) {
*lastDot = '\0'; // truncate string
}
#else
if (lastDot) {
size_t prefixLen = lastDot - appversionstr;
strncpy(appversionstr_formatted, appversionstr, prefixLen);
@@ -602,10 +629,12 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
strncpy(appversionstr, appversionstr_formatted, sizeof(appversionstr) - 1);
appversionstr[sizeof(appversionstr) - 1] = '\0';
}
#endif
int textWidth = display->getStringWidth(appversionstr);
int nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line], appversionstr);
display->drawString(nameX, getTextPositions(display)[line], appversionstr);
#if !defined(M5STACK_UNITC6L)
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it
line += 1;
char uptimeStr[32] = "";
@@ -624,6 +653,7 @@ void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
}
#endif
}
} // namespace DebugRenderer
} // namespace graphics

View File

@@ -79,7 +79,11 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
"NP_865",
"BR_902"};
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "LoRa Region";
#else
bannerOptions.message = "Set the LoRa region";
#endif
bannerOptions.durationMs = duration;
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 27;
@@ -260,7 +264,11 @@ void menuHandler::TZPicker()
void menuHandler::clockMenu()
{
#if defined(M5STACK_UNITC6L)
static const char *optionsArray[] = {"Back", "Time Format", "Timezone"};
#else
static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"};
#endif
enum optionsNumbers { Back = 0, Clock = 1, Time = 2, Timezone = 3 };
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Clock Action";
@@ -284,8 +292,11 @@ void menuHandler::clockMenu()
void menuHandler::messageResponseMenu()
{
enum optionsNumbers { Back = 0, Dismiss = 1, Preset = 2, Freetext = 3, Aloud = 4, enumEnd = 5 };
#if defined(M5STACK_UNITC6L)
static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply Preset"};
#else
static const char *optionsArray[enumEnd] = {"Back", "Dismiss", "Reply via Preset"};
#endif
static int optionsEnumArray[enumEnd] = {Back, Dismiss, Preset};
int options = 3;
@@ -299,7 +310,11 @@ void menuHandler::messageResponseMenu()
optionsEnumArray[options++] = Aloud;
#endif
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Message";
#else
bannerOptions.message = "Message Action";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
@@ -349,7 +364,11 @@ void menuHandler::homeBaseMenu()
optionsArray[options] = "Send Position";
optionsEnumArray[options++] = Position;
#if defined(M5STACK_UNITC6L)
optionsArray[options] = "New Preset";
#else
optionsArray[options] = "New Preset Msg";
#endif
optionsEnumArray[options++] = Preset;
if (kb_found) {
optionsArray[options] = "New Freetext Msg";
@@ -357,7 +376,11 @@ void menuHandler::homeBaseMenu()
}
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Home";
#else
bannerOptions.message = "Home Action";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
@@ -396,6 +419,11 @@ void menuHandler::homeBaseMenu()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::textMessageMenu()
{
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
}
void menuHandler::textMessageBaseMenu()
{
enum optionsNumbers { Back, Preset, Freetext, enumEnd };
@@ -439,11 +467,17 @@ void menuHandler::systemBaseMenu()
optionsArray[options] = "Screen Options";
optionsEnumArray[options++] = ScreenOptions;
#endif
#if defined(M5STACK_UNITC6L)
optionsArray[options] = "Bluetooth";
#else
optionsArray[options] = "Bluetooth Toggle";
#endif
optionsEnumArray[options++] = Bluetooth;
#if defined(M5STACK_UNITC6L)
optionsArray[options] = "Power";
#else
optionsArray[options] = "Reboot/Shutdown";
#endif
optionsEnumArray[options++] = PowerMenu;
if (test_enabled) {
@@ -452,7 +486,11 @@ void menuHandler::systemBaseMenu()
}
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "System";
#else
bannerOptions.message = "System Action";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
@@ -485,7 +523,11 @@ void menuHandler::systemBaseMenu()
void menuHandler::favoriteBaseMenu()
{
enum optionsNumbers { Back, Preset, Freetext, Remove, TraceRoute, enumEnd };
#if defined(M5STACK_UNITC6L)
static const char *optionsArray[enumEnd] = {"Back", "New Preset"};
#else
static const char *optionsArray[enumEnd] = {"Back", "New Preset Msg"};
#endif
static int optionsEnumArray[enumEnd] = {Back, Preset};
int options = 2;
@@ -493,13 +535,19 @@ void menuHandler::favoriteBaseMenu()
optionsArray[options] = "New Freetext Msg";
optionsEnumArray[options++] = Freetext;
}
#if !defined(M5STACK_UNITC6L)
optionsArray[options] = "Trace Route";
optionsEnumArray[options++] = TraceRoute;
#endif
optionsArray[options] = "Remove Favorite";
optionsEnumArray[options++] = Remove;
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Favorites";
#else
bannerOptions.message = "Favorites Action";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = options;
@@ -558,11 +606,19 @@ void menuHandler::positionBaseMenu()
void menuHandler::nodeListMenu()
{
enum optionsNumbers { Back, Favorite, TraceRoute, Verify, Reset, enumEnd };
#if defined(M5STACK_UNITC6L)
static const char *optionsArray[] = {"Back", "Add Favorite", "Reset Node"};
#else
static const char *optionsArray[] = {"Back", "Add Favorite", "Trace Route", "Key Verification", "Reset NodeDB"};
#endif
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Node Action";
bannerOptions.optionsArrayPtr = optionsArray;
#if defined(M5STACK_UNITC6L)
bannerOptions.optionsCount = 3;
#else
bannerOptions.optionsCount = 5;
#endif
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Favorite) {
menuQueue = add_favorite;
@@ -669,7 +725,11 @@ void menuHandler::BluetoothToggleMenu()
{
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Bluetooth";
#else
bannerOptions.message = "Toggle Bluetooth";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -861,7 +921,11 @@ void menuHandler::rebootMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Reboot";
#else
bannerOptions.message = "Reboot Device?";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -881,7 +945,11 @@ void menuHandler::shutdownMenu()
{
static const char *optionsArray[] = {"Back", "Confirm"};
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Shutdown";
#else
bannerOptions.message = "Shutdown Device?";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
@@ -898,7 +966,12 @@ void menuHandler::shutdownMenu()
void menuHandler::addFavoriteMenu()
{
#if defined(M5STACK_UNITC6L)
screen->showNodePicker("Node Favorite", 30000, [](uint32_t nodenum) -> void {
#else
screen->showNodePicker("Node To Favorite", 30000, [](uint32_t nodenum) -> void {
#endif
LOG_WARN("Nodenum: %u", nodenum);
nodeDB->set_favorite(true, nodenum);
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
@@ -1094,7 +1167,11 @@ void menuHandler::powerMenu()
#endif
BannerOverlayOptions bannerOptions;
#if defined(M5STACK_UNITC6L)
bannerOptions.message = "Power";
#else
bannerOptions.message = "Reboot / Shutdown";
#endif
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;

View File

@@ -76,6 +76,7 @@ class menuHandler
static void notificationsMenu();
static void screenOptionsMenu();
static void powerMenu();
static void textMessageMenu();
private:
static void saveUIConfig();

View File

@@ -181,12 +181,19 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
#if defined(M5STACK_UNITC6L)
const int fixedTopHeight = 24;
const int windowX = 0;
const int windowY = fixedTopHeight;
const int windowWidth = 64;
const int windowHeight = SCREEN_HEIGHT - fixedTopHeight;
#else
const int navHeight = FONT_HEIGHT_SMALL;
const int scrollBottom = SCREEN_HEIGHT - navHeight;
const int usableHeight = scrollBottom;
const int textWidth = SCREEN_WIDTH;
#endif
bool isInverted = (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED);
bool isBold = config.display.heading_bold;
@@ -201,7 +208,11 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
graphics::drawCommonHeader(display, x, y, titleStr);
const char *messageString = "No messages";
int center_text = (SCREEN_WIDTH / 2) - (display->getStringWidth(messageString) / 2);
#if defined(M5STACK_UNITC6L)
display->drawString(center_text, windowY + (windowHeight / 2) - (FONT_HEIGHT_SMALL / 2) - 5, messageString);
#else
display->drawString(center_text, getTextPositions(display)[2], messageString);
#endif
return;
}
@@ -209,6 +220,10 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
char headerStr[80];
const char *sender = "???";
#if defined(M5STACK_UNITC6L)
if (node && node->has_user)
sender = node->user.short_name;
#else
if (node && node->has_user) {
if (SCREEN_WIDTH >= 200 && strlen(node->user.long_name) > 0) {
sender = node->user.long_name;
@@ -216,6 +231,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
sender = node->user.short_name;
}
}
#endif
uint32_t seconds = sinceReceived(&mp), minutes = seconds / 60, hours = minutes / 60, days = hours / 24;
uint8_t timestampHours, timestampMinutes;
int32_t daysAgo;
@@ -235,10 +251,61 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
sender);
}
} else {
#if defined(M5STACK_UNITC6L)
snprintf(headerStr, sizeof(headerStr), "%s from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(),
sender);
#else
snprintf(headerStr, sizeof(headerStr), "%s ago from %s", UIRenderer::drawTimeDelta(days, hours, minutes, seconds).c_str(),
sender);
#endif
}
#if defined(M5STACK_UNITC6L)
graphics::drawCommonHeader(display, x, y, titleStr);
int headerY = getTextPositions(display)[1];
display->drawString(x, headerY, headerStr);
for (int separatorX = 0; separatorX < SCREEN_WIDTH; separatorX += 2) {
display->setPixel(separatorX, fixedTopHeight - 1);
}
cachedLines.clear();
std::string fullMsg(messageBuf);
std::string currentLine;
for (size_t i = 0; i < fullMsg.size();) {
unsigned char c = fullMsg[i];
size_t charLen = 1;
if ((c & 0xE0) == 0xC0)
charLen = 2;
else if ((c & 0xF0) == 0xE0)
charLen = 3;
else if ((c & 0xF8) == 0xF0)
charLen = 4;
std::string nextChar = fullMsg.substr(i, charLen);
std::string testLine = currentLine + nextChar;
if (display->getStringWidth(testLine.c_str()) > windowWidth) {
cachedLines.push_back(currentLine);
currentLine = nextChar;
} else {
currentLine = testLine;
}
i += charLen;
}
if (!currentLine.empty())
cachedLines.push_back(currentLine);
cachedHeights = calculateLineHeights(cachedLines, emotes);
int yOffset = windowY;
int linesDrawn = 0;
for (size_t i = 0; i < cachedLines.size(); ++i) {
if (linesDrawn >= 2)
break;
int lineHeight = cachedHeights[i];
if (yOffset + lineHeight > windowY + windowHeight)
break;
drawStringWithEmotes(display, windowX, yOffset, cachedLines[i], emotes, numEmotes);
yOffset += lineHeight;
linesDrawn++;
}
screen->forceDisplay();
#else
uint32_t now = millis();
#ifndef EXCLUDE_EMOJI
// === Bounce animation setup ===
@@ -355,6 +422,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// Draw header at the end to sort out overlapping elements
graphics::drawCommonHeader(display, x, y, titleStr);
#endif
}
std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth)

View File

@@ -21,6 +21,10 @@ extern bool haveGlyphs(const char *str);
// Global screen instance
extern graphics::Screen *screen;
#if defined(M5STACK_UNITC6L)
static uint32_t lastSwitchTime = 0;
#else
#endif
namespace graphics
{
namespace NodeListRenderer
@@ -393,9 +397,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
{
const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
const int rowYOffset = FONT_HEIGHT_SMALL - 3;
#if defined(M5STACK_UNITC6L)
int columnWidth = display->getWidth();
#else
int columnWidth = display->getWidth() / 2;
#endif
display->clear();
// Draw the battery/time header
@@ -408,8 +414,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;
int visibleNodeRows = totalRowsAvailable;
#if defined(M5STACK_UNITC6L)
int totalColumns = 1;
#else
int totalColumns = 2;
#endif
int startIndex = scrollIndex * visibleNodeRows * totalColumns;
if (nodeDB->getMeshNodeByIndex(startIndex)->num == nodeDB->getNodeNum()) {
startIndex++; // skip own node
@@ -445,12 +454,14 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
}
}
#if !defined(M5STACK_UNITC6L)
// Draw column separator
if (shownCount > 0) {
const int firstNodeY = y + 3;
drawColumnSeparator(display, x, firstNodeY, lastNodeY);
}
#endif
const int scrollStartY = y + 3;
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
}
@@ -468,6 +479,13 @@ void drawDynamicNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state,
unsigned long now = millis();
#if defined(M5STACK_UNITC6L)
display->clear();
if (now - lastSwitchTime >= 3000) {
display->display();
lastSwitchTime = now;
}
#endif
// On very first call (on boot or state enter)
if (lastRenderedMode == MODE_COUNT) {
currentMode = MODE_LAST_HEARD;
@@ -522,6 +540,14 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state,
double lat = DegD(ourNode->position.latitude_i);
double lon = DegD(ourNode->position.longitude_i);
#if defined(M5STACK_UNITC6L)
display->clear();
uint32_t now = millis();
if (now - lastSwitchTime >= 2000) {
display->display();
lastSwitchTime = now;
}
#endif
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
#if HAS_GPS
if (screen->hasHeading()) {

View File

@@ -459,6 +459,135 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
// count lines
uint16_t boxWidth = hPadding * 2 + maxWidth;
#if defined(M5STACK_UNITC6L)
if (needs_bell) {
if (isHighResolution && boxWidth <= 150)
boxWidth += 26;
if (!isHighResolution && boxWidth <= 100)
boxWidth += 20;
}
uint16_t screenHeight = display->height();
uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3;
uint8_t visibleTotalLines = std::min<uint8_t>(lineCount, (screenHeight - vPadding * 2) / effectiveLineHeight);
uint16_t contentHeight = visibleTotalLines * effectiveLineHeight;
uint16_t boxHeight = contentHeight + vPadding * 2;
if (visibleTotalLines == 1)
boxHeight += (isHighResolution ? 4 : 3);
int16_t boxLeft = (display->width() / 2) - (boxWidth / 2);
if (totalLines > visibleTotalLines)
boxWidth += (isHighResolution ? 4 : 2);
int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
if (visibleTotalLines == 1) {
boxTop += 25;
}
if (alertBannerOptions < 3) {
int missingLines = 3 - alertBannerOptions;
int moveUp = missingLines * (effectiveLineHeight / 2);
boxTop -= moveUp;
if (boxTop < 0)
boxTop = 0;
}
// === Draw Box ===
display->setColor(BLACK);
display->fillRect(boxLeft, boxTop, boxWidth, boxHeight);
display->setColor(WHITE);
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
display->fillRect(boxLeft, boxTop - 2, boxWidth, 1);
display->fillRect(boxLeft - 2, boxTop, 1, boxHeight);
display->fillRect(boxLeft + boxWidth + 1, boxTop, 1, boxHeight);
display->setColor(BLACK);
display->fillRect(boxLeft, boxTop, 1, 1);
display->fillRect(boxLeft + boxWidth - 1, boxTop, 1, 1);
display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1);
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
display->setColor(WHITE);
int16_t lineY = boxTop + vPadding;
int swingRange = 8;
static int swingOffset = 0;
static bool swingRight = true;
static unsigned long lastSwingTime = 0;
unsigned long now = millis();
int swingSpeedMs = 10 / (swingRange * 2);
if (now - lastSwingTime >= (unsigned long)swingSpeedMs) {
lastSwingTime = now;
if (swingRight) {
swingOffset++;
if (swingOffset >= swingRange)
swingRight = false;
} else {
swingOffset--;
if (swingOffset <= 0)
swingRight = true;
}
}
for (int i = 0; i < lineCount; i++) {
bool isTitle = (i == 0);
int globalOptionIndex = (i - 1) + firstOptionToShow;
bool isSelectedOption = (!isTitle && globalOptionIndex >= 0 && globalOptionIndex == curSelected);
uint16_t visibleWidth = 64 - hPadding * 2;
if (totalLines > visibleTotalLines)
visibleWidth -= 6;
char lineBuffer[lineLengths[i] + 1];
strncpy(lineBuffer, lines[i], lineLengths[i]);
lineBuffer[lineLengths[i]] = '\0';
if (isTitle) {
if (visibleTotalLines == 1) {
display->setColor(BLACK);
display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight);
display->setColor(WHITE);
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer);
} else {
display->setColor(WHITE);
display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight);
display->setColor(BLACK);
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer);
display->setColor(WHITE);
if (needs_bell) {
int bellY = boxTop + (FONT_HEIGHT_SMALL - 8) / 2;
display->drawXbm(boxLeft + (boxWidth - lineWidths[i]) / 2 - 10, bellY, 8, 8, bell_alert);
display->drawXbm(boxLeft + (boxWidth + lineWidths[i]) / 2 + 2, bellY, 8, 8, bell_alert);
}
}
lineY = boxTop + effectiveLineHeight + 1;
} else if (isSelectedOption) {
display->setColor(WHITE);
display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight);
display->setColor(BLACK);
if (lineLengths[i] > 15 && lineWidths[i] > visibleWidth) {
int textX = boxLeft + hPadding + swingOffset;
display->drawString(textX, lineY - 1, lineBuffer);
} else {
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY - 1, lineBuffer);
}
display->setColor(WHITE);
lineY += effectiveLineHeight;
} else {
display->setColor(BLACK);
display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight);
display->setColor(WHITE);
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY, lineBuffer);
lineY += effectiveLineHeight;
}
}
if (totalLines > visibleTotalLines) {
const uint8_t scrollBarWidth = 5;
int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2;
int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight;
uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight;
float ratio = (float)visibleTotalLines / totalLines;
uint16_t indicatorHeight = std::max((int)(scrollBarHeight * ratio), 4);
float scrollRatio = (float)(firstOptionToShow + lineCount - visibleTotalLines) / (totalLines - visibleTotalLines);
uint16_t indicatorY = scrollBarY + scrollRatio * (scrollBarHeight - indicatorHeight);
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
}
#else
if (needs_bell) {
if (isHighResolution && boxWidth <= 150)
boxWidth += 26;
@@ -547,6 +676,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
}
#endif
}
/// Draw the last text message we received

View File

@@ -20,7 +20,7 @@
// External variables
extern graphics::Screen *screen;
static uint32_t lastSwitchTime = 0;
namespace graphics
{
NodeNum UIRenderer::currentFavoriteNodeNum = 0;
@@ -218,7 +218,6 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
// **********************
void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y)
{
if (favoritedNodes.empty())
return;
@@ -230,8 +229,15 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex];
if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite)
return;
uint32_t now = millis();
display->clear();
#if defined(M5STACK_UNITC6L)
if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒
{
display->display();
lastSwitchTime = now;
}
#endif
currentFavoriteNodeNum = node->num;
// === Create the shortName and title string ===
const char *shortName = (node->has_user && haveGlyphs(node->user.short_name)) ? node->user.short_name : "Node";
@@ -250,9 +256,13 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
// List of available macro Y positions in order, from top to bottom.
int line = 1; // which slot to use next
std::string usernameStr;
// === 1. Long Name (always try to show first) ===
#if defined(M5STACK_UNITC6L)
const char *username = (node->has_user && node->user.long_name[0]) ? node->user.short_name : nullptr;
#else
const char *username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr;
#endif
if (username) {
usernameStr = sanitizeString(username); // Sanitize the incoming long_name just in case
// Print node's long name (e.g. "Backpack Node")
@@ -307,7 +317,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
if (seenStr[0] && line < 5) {
display->drawString(x, getTextPositions(display)[line++], seenStr);
}
#if !defined(M5STACK_UNITC6L)
// === 4. Uptime (only show if metric is present) ===
char uptimeStr[32] = "";
if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) {
@@ -479,6 +489,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
}
// else show nothing
}
#endif
}
// ****************************
@@ -492,7 +503,11 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
int line = 1;
// === Header ===
#if defined(M5STACK_UNITC6L)
graphics::drawCommonHeader(display, x, y, "Home");
#else
graphics::drawCommonHeader(display, x, y, "");
#endif
// === Content below header ===
@@ -507,20 +522,25 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
config.display.heading_bold = false;
// Display Region and Channel Utilization
#if defined(M5STACK_UNITC6L)
drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
#else
drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
#endif
char uptimeStr[32] = "";
uint32_t uptime = millis() / 1000;
uint32_t days = uptime / 86400;
uint32_t hours = (uptime % 86400) / 3600;
uint32_t mins = (uptime % 3600) / 60;
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
#if !defined(M5STACK_UNITC6L)
if (days)
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %ud %uh", days, hours);
else if (hours)
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %uh %um", hours, mins);
else
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %um", mins);
#endif
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
// === Second Row: Satellites and Voltage ===
@@ -549,6 +569,21 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
}
#endif
#if defined(M5STACK_UNITC6L)
line += 1;
// === Node Identity ===
int textWidth = 0;
int nameX = 0;
char shortnameble[35];
snprintf(shortnameble, sizeof(shortnameble), "%s",
graphics::UIRenderer::haveGlyphs(owner.short_name) ? owner.short_name : "");
// === ShortName Centered ===
textWidth = display->getStringWidth(shortnameble);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
#else
if (powerStatus->getHasBattery()) {
char batStr[20];
int batV = powerStatus->getBatteryVoltageMv() / 1000;
@@ -674,6 +709,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
}
#endif
}
// Start Functions to write date/time to the screen
@@ -832,6 +868,28 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
// needs to be drawn relative to x and y
// draw centered icon left to right and centered above the one line of app text
#if defined(M5STACK_UNITC6L)
display->drawXbm(x + (SCREEN_WIDTH - 50) / 2, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits);
display->setFont(FONT_MEDIUM);
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
// Draw region in upper left
if (upperMsg) {
int msgWidth = display->getStringWidth(upperMsg);
int msgX = x + (SCREEN_WIDTH - msgWidth) / 2;
int msgY = y;
display->drawString(msgX, msgY, upperMsg);
}
// Draw version and short name in bottom middle
char buf[25];
snprintf(buf, sizeof(buf), "%s %s", xstr(APP_VERSION_SHORT),
graphics::UIRenderer::haveGlyphs(owner.short_name) ? owner.short_name : "");
display->drawString(x + getStringCenteredX(buf), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, buf);
screen->forceDisplay();
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
#else
display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2,
icon_width, icon_height, icon_bits);
@@ -840,7 +898,6 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
const char *title = "meshtastic.org";
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
display->setFont(FONT_SMALL);
// Draw region in upper left
if (upperMsg)
display->drawString(x + 0, y + 0, upperMsg);
@@ -855,6 +912,7 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
screen->forceDisplay();
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
#endif
}
// ****************************
@@ -930,15 +988,26 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime);
char fullLine[40];
snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr);
#if !defined(M5STACK_UNITC6L)
display->drawString(0, getTextPositions(display)[line++], fullLine);
#endif
// === Third Row: Latitude ===
char latStr[32];
#if defined(M5STACK_UNITC6L)
snprintf(latStr, sizeof(latStr), "Lat:%.5f", geoCoord.getLatitude() * 1e-7);
display->drawString(x, getTextPositions(display)[line++] + 2, latStr);
#else
snprintf(latStr, sizeof(latStr), " Lat: %.5f", geoCoord.getLatitude() * 1e-7);
display->drawString(x, getTextPositions(display)[line++], latStr);
#endif
// === Fourth Row: Longitude ===
char lonStr[32];
#if defined(M5STACK_UNITC6L)
snprintf(lonStr, sizeof(lonStr), "Lon:%.3f", geoCoord.getLongitude() * 1e-7);
display->drawString(x, getTextPositions(display)[line++] + 4, lonStr);
#else
snprintf(lonStr, sizeof(lonStr), " Lon: %.5f", geoCoord.getLongitude() * 1e-7);
display->drawString(x, getTextPositions(display)[line++], lonStr);
@@ -950,8 +1019,9 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), " Alt: %.0im", geoCoord.getAltitude());
}
display->drawString(x, getTextPositions(display)[line++], DisplayLineTwo);
#endif
}
#if !defined(M5STACK_UNITC6L)
// === Draw Compass if heading is valid ===
if (validHeading) {
// --- Compass Rendering: landscape (wide) screens use original side-aligned logic ---
@@ -1034,6 +1104,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
}
}
#endif
#endif
}
#ifdef USERPREFS_OEM_TEXT
@@ -1190,7 +1261,6 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
display->setColor(WHITE);
}
}
// Knock the corners off the square
display->setColor(BLACK);
display->drawRect(rectX, y - 2, 1, 1);

View File

@@ -287,6 +287,9 @@ const uint8_t digital_icon_clock[] PROGMEM = {0b00111100, 0b01000010, 0b10000101
#define analog_icon_clock_height 8
const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000,
0b00100100, 0b01000010, 0b01000010, 0b11111111};
#ifdef M5STACK_UNITC6L
#include "img/icon_small.xbm"
#else
#include "img/icon.xbm"
#endif
static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");

View File

@@ -0,0 +1,30 @@
#ifndef USERPREFS_HAS_SPLASH
#define icon_width 50
#define icon_height 20
static uint8_t icon_bits[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x80, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x80,
0x07, 0xc0, 0x07, 0x00, 0x00, 0x00, 0xc0, 0x0f,
0xc0, 0x0f, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xe0,
0x0f, 0x00, 0x00, 0x00, 0xe0, 0x07, 0xf0, 0x1f,
0x00, 0x00, 0x00, 0xf0, 0x03, 0xf0, 0x3f, 0x00,
0x00, 0x00, 0xf8, 0x03, 0xf8, 0x7f, 0x00, 0x00,
0x00, 0xf8, 0x01, 0xfc, 0x7e, 0x00, 0x00, 0x00,
0xfc, 0x00, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0xfe,
0x00, 0x7e, 0xf8, 0x00, 0x00, 0x00, 0x7e, 0x00,
0x3f, 0xf8, 0x01, 0x00, 0x00, 0x3f, 0x00, 0x1f,
0xf0, 0x01, 0x00, 0x00, 0x1f, 0x80, 0x1f, 0xe0,
0x03, 0x00, 0x80, 0x1f, 0xc0, 0x0f, 0xe0, 0x03,
0x00, 0x80, 0x0f, 0xc0, 0x07, 0xc0, 0x07, 0x00,
0xc0, 0x0f, 0xe0, 0x07, 0x80, 0x0f, 0x00, 0xe0,
0x07, 0xf0, 0x03, 0x80, 0x1f, 0x00, 0xe0, 0x03,
0xf8, 0x03, 0x00, 0x1f, 0x00, 0xf0, 0x03, 0xf8,
0x01, 0x00, 0x3f, 0x00, 0xf8, 0x01, 0xfc, 0x00,
0x00, 0x7e, 0x00, 0xfc, 0x00, 0xfe, 0x00, 0x00,
0x7e, 0x00, 0xfc, 0x00, 0x7e, 0x00, 0x00, 0xfc,
0x00, 0x7e, 0x00, 0x3f, 0x00, 0x00, 0xf8, 0x00,
0x7e, 0x00, 0x3e, 0x00, 0x00, 0xf8, 0x00, 0x38,
0x00, 0x1c, 0x00, 0x00, 0x70, 0x00, 0x10, 0x00,
0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00 };
#endif

95
src/input/i2cButton.cpp Normal file
View File

@@ -0,0 +1,95 @@
#include "i2cButton.h"
#include "meshUtils.h"
#include "configuration.h"
#if defined(M5STACK_UNITC6L)
#include "MeshService.h"
#include "RadioLibInterface.h"
#include "buzz.h"
#include "input/InputBroker.h"
#include "main.h"
#include "modules/CannedMessageModule.h"
#include "modules/ExternalNotificationModule.h"
#include "power.h"
#include "sleep.h"
#ifdef ARCH_PORTDUINO
#include "platform/portduino/PortduinoGlue.h"
#endif
i2cButtonThread *i2cButton;
using namespace concurrency;
extern void i2c_read_byte(uint8_t addr, uint8_t reg, uint8_t *value);
extern void i2c_write_byte(uint8_t addr, uint8_t reg, uint8_t value);
#define PI4IO_M_ADDR 0x43
#define getbit(x, y) ((x) >> (y)&0x01)
#define PI4IO_REG_IRQ_STA 0x13
#define PI4IO_REG_IN_STA 0x0F
#define PI4IO_REG_CHIP_RESET 0x01
i2cButtonThread::i2cButtonThread(const char *name) : OSThread(name)
{
_originName = name;
if (inputBroker)
inputBroker->registerSource(this);
}
int32_t i2cButtonThread::runOnce()
{
static bool btn1_pressed = false;
static uint32_t press_start_time = 0;
const uint32_t LONG_PRESS_TIME = 1000;
static bool long_press_triggered = false;
uint8_t in_data;
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, &in_data);
i2c_write_byte(PI4IO_M_ADDR, PI4IO_REG_IRQ_STA, in_data);
if (getbit(in_data, 0)) {
uint8_t input_state;
i2c_read_byte(PI4IO_M_ADDR, PI4IO_REG_IN_STA, &input_state);
if (!getbit(input_state, 0)) {
if (!btn1_pressed) {
btn1_pressed = true;
press_start_time = millis();
long_press_triggered = false;
}
} else {
if (btn1_pressed) {
btn1_pressed = false;
uint32_t press_duration = millis() - press_start_time;
if (long_press_triggered) {
long_press_triggered = false;
return 50;
}
if (press_duration < LONG_PRESS_TIME) {
InputEvent evt;
evt.source = "UserButton";
evt.inputEvent = INPUT_BROKER_USER_PRESS;
evt.kbchar = 0;
evt.touchX = 0;
evt.touchY = 0;
this->notifyObservers(&evt);
}
}
}
}
if (btn1_pressed && !long_press_triggered && (millis() - press_start_time >= LONG_PRESS_TIME)) {
long_press_triggered = true;
InputEvent evt;
evt.source = "UserButton";
evt.inputEvent = INPUT_BROKER_SELECT;
evt.kbchar = 0;
evt.touchX = 0;
evt.touchY = 0;
this->notifyObservers(&evt);
}
return 50;
}
#endif

18
src/input/i2cButton.h Normal file
View File

@@ -0,0 +1,18 @@
#pragma once
#include "InputBroker.h"
#include "OneButton.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
#if defined(M5STACK_UNITC6L)
class i2cButtonThread : public Observable<const InputEvent *>, public concurrency::OSThread
{
public:
const char *_originName;
explicit i2cButtonThread(const char *name);
int32_t runOnce() override;
};
extern i2cButtonThread *i2cButton;
#endif

View File

@@ -385,7 +385,6 @@ void setup()
io.digitalWrite(EXPANDS_GPIO_EN, HIGH);
io.pinMode(EXPANDS_SD_PULLEN, INPUT);
#endif
concurrency::hasBeenSetup = true;
#if ARCH_PORTDUINO
SPISettings spiSettings(settingsMap[spiSpeed], MSBFIRST, SPI_MODE0);
@@ -544,6 +543,12 @@ void setup()
#endif
#endif
#if defined(M5STACK_UNITC6L)
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, 1);
c6l_init();
#endif
#ifdef PIN_LCD_RESET
// FIXME - move this someplace better, LCD is at address 0x3F
pinMode(PIN_LCD_RESET, OUTPUT);
@@ -877,7 +882,8 @@ void setup()
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS)
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \
defined(USE_SPISSD1306)
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
#elif defined(ARCH_PORTDUINO)
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) &&
@@ -1140,7 +1146,8 @@ void setup()
// Don't call screen setup until after nodedb is setup (because we need
// the current region name)
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS)
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || \
defined(USE_SPISSD1306)
if (screen)
screen->setup();
#elif defined(ARCH_PORTDUINO)

View File

@@ -6,6 +6,7 @@
#include <memory>
#include "PointerQueue.h"
#include "configuration.h" // For LOG_WARN, LOG_DEBUG, LOG_HEAP
template <class T> class Allocator
{
@@ -14,13 +15,14 @@ template <class T> class Allocator
Allocator() : deleter([this](T *p) { this->release(p); }) {}
virtual ~Allocator() {}
/// Return a queable object which has been prefilled with zeros. Panic if no buffer is available
/// Return a queable object which has been prefilled with zeros. Return nullptr if no buffer is available
/// Note: this method is safe to call from regular OR ISR code
T *allocZeroed()
{
T *p = allocZeroed(0);
assert(p); // FIXME panic instead
if (!p) {
LOG_WARN("Failed to allocate zeroed memory");
}
return p;
}
@@ -39,10 +41,12 @@ template <class T> class Allocator
T *allocCopy(const T &src, TickType_t maxWait = portMAX_DELAY)
{
T *p = alloc(maxWait);
assert(p);
if (!p) {
LOG_WARN("Failed to allocate memory for copy");
return nullptr;
}
if (p)
*p = src;
*p = src;
return p;
}
@@ -83,7 +87,9 @@ template <class T> class MemoryDynamic : public Allocator<T>
/// Return a buffer for use by others
virtual void release(T *p) override
{
assert(p);
if (p == nullptr)
return;
LOG_HEAP("Freeing 0x%x", p);
free(p);
@@ -98,3 +104,58 @@ template <class T> class MemoryDynamic : public Allocator<T>
return p;
}
};
/**
* A static memory pool that uses a fixed buffer instead of heap allocation
*/
template <class T, int MaxSize> class MemoryPool : public Allocator<T>
{
private:
T pool[MaxSize];
bool used[MaxSize];
public:
MemoryPool() : pool{}, used{}
{
// Arrays are now zero-initialized by member initializer list
// pool array: all elements are default-constructed (zero for POD types)
// used array: all elements are false (zero-initialized)
}
/// Return a buffer for use by others
virtual void release(T *p) override
{
if (!p) {
LOG_DEBUG("Failed to release memory, pointer is null");
return;
}
// Find the index of this pointer in our pool
int index = p - pool;
if (index >= 0 && index < MaxSize) {
assert(used[index]); // Should be marked as used
used[index] = false;
LOG_HEAP("Released static pool item %d at 0x%x", index, p);
} else {
LOG_WARN("Pointer 0x%x not from our pool!", p);
}
}
protected:
// Alloc some storage from our static pool
virtual T *alloc(TickType_t maxWait) override
{
// Find first free slot
for (int i = 0; i < MaxSize; i++) {
if (!used[i]) {
used[i] = true;
LOG_HEAP("Allocated static pool item %d at 0x%x", i, &pool[i]);
return &pool[i];
}
}
// No free slots available - return nullptr instead of asserting
LOG_WARN("No free slots available in static memory pool!");
return nullptr;
}
};

View File

@@ -46,11 +46,14 @@ the new node can build its node db)
MeshService *service;
static MemoryDynamic<meshtastic_MqttClientProxyMessage> staticMqttClientProxyMessagePool;
#define MAX_MQTT_PROXY_MESSAGES 16
static MemoryPool<meshtastic_MqttClientProxyMessage, MAX_MQTT_PROXY_MESSAGES> staticMqttClientProxyMessagePool;
static MemoryDynamic<meshtastic_QueueStatus> staticQueueStatusPool;
#define MAX_QUEUE_STATUS 4
static MemoryPool<meshtastic_QueueStatus, MAX_QUEUE_STATUS> staticQueueStatusPool;
static MemoryDynamic<meshtastic_ClientNotification> staticClientNotificationPool;
#define MAX_CLIENT_NOTIFICATIONS 4
static MemoryPool<meshtastic_ClientNotification, MAX_CLIENT_NOTIFICATIONS> staticClientNotificationPool;
Allocator<meshtastic_MqttClientProxyMessage> &mqttClientProxyMessagePool = staticMqttClientProxyMessagePool;

View File

@@ -663,7 +663,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.bluetooth.fixed_pin = defaultBLEPin;
#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS)
defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) || defined(USE_SPISSD1306)
bool hasScreen = true;
#ifdef HELTEC_MESH_NODE_T114
uint32_t st7789_id = get_st7789_id(ST7789_NSS, ST7789_SCK, ST7789_SDA, ST7789_RS, ST7789_RESET);

View File

@@ -5,6 +5,7 @@
#include "MeshService.h"
#include "NodeDB.h"
#include "RTC.h"
#include "configuration.h"
#include "detect/LoRaRadioType.h"
#include "main.h"
@@ -27,14 +28,24 @@
// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX
// And every TX packet might have a retransmission packet or an ack alive at any moment
#ifdef ARCH_PORTDUINO
// Portduino (native) targets can use dynamic memory pools with runtime-configurable sizes
#define MAX_PACKETS \
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
// static MemoryPool<MeshPacket> staticPool(MAX_PACKETS);
static MemoryDynamic<meshtastic_MeshPacket> staticPool;
static MemoryDynamic<meshtastic_MeshPacket> dynamicPool;
Allocator<meshtastic_MeshPacket> &packetPool = dynamicPool;
#else
// Embedded targets use static memory pools with compile-time constants
#define MAX_PACKETS_STATIC \
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + 2 * MAX_TX_QUEUE + \
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
static MemoryPool<meshtastic_MeshPacket, MAX_PACKETS_STATIC> staticPool;
Allocator<meshtastic_MeshPacket> &packetPool = staticPool;
#endif
static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));

View File

@@ -66,6 +66,8 @@ typedef enum _meshtastic_Language {
meshtastic_Language_UKRAINIAN = 16,
/* Bulgarian */
meshtastic_Language_BULGARIAN = 17,
/* Czech */
meshtastic_Language_CZECH = 18,
/* Simplified Chinese (experimental) */
meshtastic_Language_SIMPLIFIED_CHINESE = 30,
/* Traditional Chinese (experimental) */

View File

@@ -259,8 +259,8 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_T_DECK_PRO = 102,
/* Lilygo TLora Pager */
meshtastic_HardwareModel_T_LORA_PAGER = 103,
/* GAT562 Mesh Trial Tracker */
meshtastic_HardwareModel_GAT562_MESH_TRIAL_TRACKER = 104,
/* M5Stack Reserved */
meshtastic_HardwareModel_M5STACK_RESERVED = 104, /* 0x68 */
/* RAKwireless WisMesh Tag */
meshtastic_HardwareModel_WISMESH_TAG = 105,
/* RAKwireless WisBlock Core RAK3312 https://docs.rakwireless.com/product-categories/wisduo/rak3112-module/overview/ */
@@ -274,6 +274,8 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_T_ECHO_LITE = 109,
/* New Heltec LoRA32 with ESP32-S3 CPU */
meshtastic_HardwareModel_HELTEC_V4 = 110,
/* M5Stack C6L */
meshtastic_HardwareModel_M5STACK_C6L = 111,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */

View File

@@ -292,11 +292,14 @@ JSONArray htmlListDir(const char *dirname, uint8_t levels)
JSONObject thisFileMap;
thisFileMap["size"] = new JSONValue((int)file.size());
#ifdef ARCH_ESP32
thisFileMap["name"] = new JSONValue(String(file.path()).substring(1).c_str());
String fileName = String(file.path()).substring(1);
thisFileMap["name"] = new JSONValue(fileName.c_str());
#else
thisFileMap["name"] = new JSONValue(String(file.name()).substring(1).c_str());
String fileName = String(file.name()).substring(1);
thisFileMap["name"] = new JSONValue(fileName.c_str());
#endif
if (String(file.name()).substring(1).endsWith(".gz")) {
String tempName = String(file.name()).substring(1);
if (tempName.endsWith(".gz")) {
#ifdef ARCH_ESP32
String modifiedFile = String(file.path()).substring(1);
#else
@@ -339,7 +342,8 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res)
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
@@ -367,7 +371,8 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
return;
} else {
@@ -376,7 +381,8 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("Error");
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
return;
}
@@ -622,10 +628,7 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
tempArray.push_back(new JSONValue((int)logArray[i]));
}
JSONValue *result = new JSONValue(tempArray);
// Clean up original array to prevent memory leak
for (auto *val : tempArray) {
delete val;
}
// Note: Don't delete tempArray elements here - JSONValue now owns them
return result;
};
@@ -656,7 +659,9 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
// data->wifi
JSONObject jsonObjWifi;
jsonObjWifi["rssi"] = new JSONValue(WiFi.RSSI());
jsonObjWifi["ip"] = new JSONValue(WiFi.localIP().toString().c_str());
String wifiIPString = WiFi.localIP().toString();
std::string wifiIP = wifiIPString.c_str();
jsonObjWifi["ip"] = new JSONValue(wifiIP.c_str());
// data->memory
JSONObject jsonObjMemory;
@@ -702,7 +707,8 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
}
@@ -773,7 +779,8 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res)
jsonObjOuter["status"] = new JSONValue("ok");
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
// Clean up the nodesArray to prevent memory leak
@@ -926,7 +933,8 @@ void handleBlinkLED(HTTPRequest *req, HTTPResponse *res)
JSONObject jsonObjOuter;
jsonObjOuter["status"] = new JSONValue("ok");
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
}
@@ -968,7 +976,8 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObjOuter);
res->print(value->Stringify().c_str());
std::string jsonString = value->Stringify();
res->print(jsonString.c_str());
delete value;
// Clean up the networkObjs to prevent memory leak

View File

@@ -15,22 +15,26 @@
// FIXME - max_count is actually 32 but we save/load this as one long string of preencoded MeshPacket bytes - not a big array in
// RAM #define MAX_RX_TOPHONE (member_size(DeviceState, receive_queue) / member_size(DeviceState, receive_queue[0]))
#ifndef MAX_RX_TOPHONE
#if defined(ARCH_ESP32) && !(defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S3))
#define MAX_RX_TOPHONE 8
#else
#define MAX_RX_TOPHONE 32
#endif
#endif
/// max number of QueueStatus packets which can be waiting for delivery to phone
#ifndef MAX_RX_QUEUESTATUS_TOPHONE
#define MAX_RX_QUEUESTATUS_TOPHONE 4
#define MAX_RX_QUEUESTATUS_TOPHONE 2
#endif
/// max number of MqttClientProxyMessage packets which can be waiting for delivery to phone
#ifndef MAX_RX_MQTTPROXY_TOPHONE
#define MAX_RX_MQTTPROXY_TOPHONE 32
#define MAX_RX_MQTTPROXY_TOPHONE 8
#endif
/// max number of ClientNotification packets which can be waiting for delivery to phone
#ifndef MAX_RX_NOTIFICATION_TOPHONE
#define MAX_RX_NOTIFICATION_TOPHONE 4
#define MAX_RX_NOTIFICATION_TOPHONE 2
#endif
/// Verify baseline assumption of node size. If it increases, we need to reevaluate

View File

@@ -1432,10 +1432,17 @@ void CannedMessageModule::drawDestinationSelectionScreen(OLEDDisplay *display, O
meshtastic_NodeInfoLite *node = this->filteredNodes[nodeIndex].node;
if (node) {
if (node->is_favorite) {
#if defined(M5STACK_UNITC6L)
snprintf(entryText, sizeof(entryText), "* %s", node->user.short_name);
} else {
snprintf(entryText, sizeof(entryText), "%s", node->user.short_name);
}
#else
snprintf(entryText, sizeof(entryText), "* %s", node->user.long_name);
} else {
snprintf(entryText, sizeof(entryText), "%s", node->user.long_name);
}
#endif
}
}
}
@@ -1606,7 +1613,11 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
int yOffset = y + 10;
#else
display->setFont(FONT_MEDIUM);
#if defined(M5STACK_UNITC6L)
int yOffset = y;
#else
int yOffset = y + 10;
#endif
#endif
// --- Delivery Status Message ---
@@ -1631,13 +1642,20 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
}
display->drawString(display->getWidth() / 2 + x, yOffset, buffer);
#if defined(M5STACK_UNITC6L)
yOffset += lineCount * FONT_HEIGHT_MEDIUM - 5; // only 1 line gap, no extra padding
#else
yOffset += lineCount * FONT_HEIGHT_MEDIUM; // only 1 line gap, no extra padding
#endif
#ifndef USE_EINK
// --- SNR + RSSI Compact Line ---
if (this->ack) {
display->setFont(FONT_SMALL);
#if defined(M5STACK_UNITC6L)
snprintf(buffer, sizeof(buffer), "SNR: %.1f dB \nRSSI: %d", this->lastRxSnr, this->lastRxRssi);
#else
snprintf(buffer, sizeof(buffer), "SNR: %.1f dB RSSI: %d", this->lastRxSnr, this->lastRxRssi);
#endif
display->drawString(display->getWidth() / 2 + x, yOffset, buffer);
}
#endif

View File

@@ -7,6 +7,7 @@
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/SerialKeyboardImpl.h"
#include "input/UpDownInterruptImpl1.h"
#include "input/i2cButton.h"
#include "modules/SystemCommandsModule.h"
#if HAS_TRACKBALL
#include "input/TrackballInterruptImpl1.h"
@@ -196,6 +197,9 @@ void setupModules()
#endif
cardKbI2cImpl = new CardKbI2cImpl();
cardKbI2cImpl->init();
#if defined(M5STACK_UNITC6L)
i2cButton = new i2cButtonThread("i2cButtonThread");
#endif
#ifdef INPUTBROKER_MATRIX_TYPE
kbMatrixImpl = new KbMatrixImpl();
kbMatrixImpl->init();

View File

@@ -11,6 +11,12 @@
#include <NimBLEDevice.h>
#include <mutex>
#ifdef NIMBLE_TWO
#include "NimBLEAdvertising.h"
#include "NimBLEExtAdvertising.h"
#include "PowerStatus.h"
#endif
NimBLECharacteristic *fromNumCharacteristic;
NimBLECharacteristic *BatteryCharacteristic;
NimBLECharacteristic *logRadioCharacteristic;
@@ -56,13 +62,18 @@ class BluetoothPhoneAPI : public PhoneAPI, public concurrency::OSThread
{
PhoneAPI::onNowHasData(fromRadioNum);
LOG_DEBUG("BLE notify fromNum");
uint8_t cc = bleServer->getConnectedCount();
LOG_DEBUG("BLE notify fromNum: %d connections: %d", fromRadioNum, cc);
uint8_t val[4];
put_le32(val, fromRadioNum);
fromNumCharacteristic->setValue(val, sizeof(val));
#ifdef NIMBLE_TWO
fromNumCharacteristic->notify(val, sizeof(val), BLE_HS_CONN_HANDLE_NONE);
#else
fromNumCharacteristic->notify();
#endif
}
/// Check the current underlying physical link to see if the client is currently connected
@@ -79,7 +90,12 @@ static uint8_t lastToRadio[MAX_TO_FROM_RADIO_SIZE];
class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
{
#ifdef NIMBLE_TWO
virtual void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo)
#else
virtual void onWrite(NimBLECharacteristic *pCharacteristic)
#endif
{
auto val = pCharacteristic->getValue();
@@ -97,7 +113,11 @@ class NimbleBluetoothToRadioCallback : public NimBLECharacteristicCallbacks
class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
{
#ifdef NIMBLE_TWO
virtual void onRead(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo)
#else
virtual void onRead(NimBLECharacteristic *pCharacteristic)
#endif
{
int tries = 0;
bluetoothPhoneAPI->phoneWants = true;
@@ -107,9 +127,7 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
tries++;
}
std::lock_guard<std::mutex> guard(bluetoothPhoneAPI->nimble_mutex);
std::string fromRadioByteString(bluetoothPhoneAPI->fromRadioBytes,
bluetoothPhoneAPI->fromRadioBytes + bluetoothPhoneAPI->numBytes);
pCharacteristic->setValue(fromRadioByteString);
pCharacteristic->setValue(bluetoothPhoneAPI->fromRadioBytes, bluetoothPhoneAPI->numBytes);
if (bluetoothPhoneAPI->numBytes != 0) // if we did send something, queue it up right away to reload
bluetoothPhoneAPI->setIntervalFromNow(0);
@@ -121,7 +139,17 @@ class NimbleBluetoothFromRadioCallback : public NimBLECharacteristicCallbacks
class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
{
#ifdef NIMBLE_TWO
public:
NimbleBluetoothServerCallback(NimbleBluetooth *ble) { this->ble = ble; }
private:
NimbleBluetooth *ble;
virtual uint32_t onPassKeyDisplay()
#else
virtual uint32_t onPassKeyRequest()
#endif
{
uint32_t passkey = config.bluetooth.fixed_pin;
@@ -133,7 +161,8 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
LOG_INFO("*** Enter passkey %d on the peer side ***", passkey);
powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(std::to_string(passkey)));
meshtastic::BluetoothStatus newStatus(std::to_string(passkey));
bluetoothStatus->updateStatus(&newStatus);
#if HAS_SCREEN // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
if (screen) {
@@ -169,11 +198,16 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
return passkey;
}
#ifdef NIMBLE_TWO
virtual void onAuthenticationComplete(NimBLEConnInfo &connInfo)
#else
virtual void onAuthenticationComplete(ble_gap_conn_desc *desc)
#endif
{
LOG_INFO("BLE authentication complete");
bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED));
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
bluetoothStatus->updateStatus(&newStatus);
// Todo: migrate this display code back into Screen class, and observe bluetoothStatus
if (passkeyShowing) {
@@ -183,12 +217,23 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
}
}
#ifdef NIMBLE_TWO
virtual void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo)
{
LOG_INFO("BLE incoming connection %s", connInfo.getAddress().toString().c_str());
}
virtual void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason)
{
LOG_INFO("BLE disconnect reason: %d", reason);
#else
virtual void onDisconnect(NimBLEServer *pServer, ble_gap_conn_desc *desc)
{
LOG_INFO("BLE disconnect");
#endif
bluetoothStatus->updateStatus(
new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED));
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
bluetoothStatus->updateStatus(&newStatus);
if (bluetoothPhoneAPI) {
std::lock_guard<std::mutex> guard(bluetoothPhoneAPI->nimble_mutex);
@@ -198,6 +243,10 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
bluetoothPhoneAPI->numBytes = 0;
bluetoothPhoneAPI->queue_size = 0;
}
#ifdef NIMBLE_TWO
// Restart Advertising
ble->startAdvertising();
#endif
}
};
@@ -249,7 +298,11 @@ int NimbleBluetooth::getRssi()
if (bleServer && isConnected()) {
auto service = bleServer->getServiceByUUID(MESH_SERVICE_UUID);
uint16_t handle = service->getHandle();
#ifdef NIMBLE_TWO
return NimBLEDevice::getClientByHandle(handle)->getRssi();
#else
return NimBLEDevice::getClientByID(handle)->getRssi();
#endif
}
return 0; // FIXME figure out where to source this
}
@@ -271,8 +324,11 @@ void NimbleBluetooth::setup()
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY);
}
bleServer = NimBLEDevice::createServer();
#ifdef NIMBLE_TWO
NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback(this);
#else
NimbleBluetoothServerCallback *serverCallbacks = new NimbleBluetoothServerCallback();
#endif
bleServer->setCallbacks(serverCallbacks, true);
setupService();
startAdvertising();
@@ -316,8 +372,11 @@ void NimbleBluetooth::setupService()
NimBLEService *batteryService = bleServer->createService(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service
BatteryCharacteristic = batteryService->createCharacteristic( // 0x2A19 is the Battery Level characteristic)
(uint16_t)0x2a19, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY, 1);
#ifdef NIMBLE_TWO
NimBLE2904 *batteryLevelDescriptor = BatteryCharacteristic->create2904();
#else
NimBLE2904 *batteryLevelDescriptor = (NimBLE2904 *)BatteryCharacteristic->createDescriptor((uint16_t)0x2904);
#endif
batteryLevelDescriptor->setFormat(NimBLE2904::FORMAT_UINT8);
batteryLevelDescriptor->setNamespace(1);
batteryLevelDescriptor->setUnit(0x27ad);
@@ -327,11 +386,40 @@ void NimbleBluetooth::setupService()
void NimbleBluetooth::startAdvertising()
{
#ifdef NIMBLE_TWO
NimBLEExtAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
NimBLEExtAdvertisement legacyAdvertising;
legacyAdvertising.setLegacyAdvertising(true);
legacyAdvertising.setScannable(true);
legacyAdvertising.setConnectable(true);
legacyAdvertising.setFlags(BLE_HS_ADV_F_DISC_GEN);
if (powerStatus->getHasBattery() == 1) {
legacyAdvertising.setCompleteServices(NimBLEUUID((uint16_t)0x180f));
}
legacyAdvertising.setCompleteServices(NimBLEUUID(MESH_SERVICE_UUID));
legacyAdvertising.setMinInterval(500);
legacyAdvertising.setMaxInterval(1000);
NimBLEExtAdvertisement legacyScanResponse;
legacyScanResponse.setLegacyAdvertising(true);
legacyScanResponse.setConnectable(true);
legacyScanResponse.setName(getDeviceName());
if (!pAdvertising->setInstanceData(0, legacyAdvertising)) {
LOG_ERROR("BLE failed to set legacyAdvertising");
} else if (!pAdvertising->setScanResponseData(0, legacyScanResponse)) {
LOG_ERROR("BLE failed to set legacyScanResponse");
} else if (!pAdvertising->start(0, 0, 0)) {
LOG_ERROR("BLE failed to start legacyAdvertising");
}
#else
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising->reset();
pAdvertising->addServiceUUID(MESH_SERVICE_UUID);
pAdvertising->addServiceUUID(NimBLEUUID((uint16_t)0x180f)); // 0x180F is the Battery Service
pAdvertising->start(0);
#endif
}
/// Given a level between 0-100, update the BLE attribute
@@ -339,7 +427,11 @@ void updateBatteryLevel(uint8_t level)
{
if ((config.bluetooth.enabled == true) && bleServer && nimbleBluetooth->isConnected()) {
BatteryCharacteristic->setValue(&level, 1);
#ifdef NIMBLE_TWO
BatteryCharacteristic->notify(&level, 1, BLE_HS_CONN_HANDLE_NONE);
#else
BatteryCharacteristic->notify();
#endif
}
}
@@ -354,7 +446,11 @@ void NimbleBluetooth::sendLog(const uint8_t *logMessage, size_t length)
if (!bleServer || !isConnected() || length > 512) {
return;
}
#ifdef NIMBLE_TWO
logRadioCharacteristic->notify(logMessage, length, BLE_HS_CONN_HANDLE_NONE);
#else
logRadioCharacteristic->notify(logMessage, length, true);
#endif
}
void clearNVS()
@@ -364,4 +460,4 @@ void clearNVS()
ESP.restart();
#endif
}
#endif
#endif

View File

@@ -12,10 +12,15 @@ class NimbleBluetooth : BluetoothApi
bool isConnected();
int getRssi();
void sendLog(const uint8_t *logMessage, size_t length);
#if defined(NIMBLE_TWO)
void startAdvertising();
#endif
private:
void setupService();
#if !defined(NIMBLE_TWO)
void startAdvertising();
#endif
};
void setBluetoothEnable(bool enable);

View File

@@ -194,6 +194,8 @@
#define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO
#elif defined(T_LORA_PAGER)
#define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER
#elif defined(M5STACK_UNITC6L)
#define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L
#endif
// -----------------------------------------------------------------------------

View File

@@ -59,7 +59,8 @@ void onConnect(uint16_t conn_handle)
LOG_INFO("BLE Connected to %s", central_name);
// Notify UI (or any other interested firmware components)
bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED));
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
bluetoothStatus->updateStatus(&newStatus);
}
/**
* Callback invoked when a connection is dropped
@@ -74,7 +75,8 @@ void onDisconnect(uint16_t conn_handle, uint8_t reason)
}
// Notify UI (or any other interested firmware components)
bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED));
meshtastic::BluetoothStatus newStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
bluetoothStatus->updateStatus(&newStatus);
}
void onCccd(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value)
{
@@ -326,7 +328,8 @@ bool NRF52Bluetooth::onPairingPasskey(uint16_t conn_handle, uint8_t const passke
textkey += (char)passkey[i];
// Notify UI (or other components) of pairing event and passkey
bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(textkey));
meshtastic::BluetoothStatus newStatus(textkey);
bluetoothStatus->updateStatus(&newStatus);
#if !defined(MESHTASTIC_EXCLUDE_SCREEN) // Todo: migrate this display code back into Screen class, and observe bluetoothStatus
if (screen) {
@@ -398,12 +401,13 @@ void NRF52Bluetooth::onPairingCompleted(uint16_t conn_handle, uint8_t auth_statu
{
if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) {
LOG_INFO("BLE pair success");
bluetoothStatus->updateStatus(new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED));
meshtastic::BluetoothStatus newConnectedStatus(meshtastic::BluetoothStatus::ConnectionState::CONNECTED);
bluetoothStatus->updateStatus(&newConnectedStatus);
} else {
LOG_INFO("BLE pair failed");
// Notify UI (or any other interested firmware components)
bluetoothStatus->updateStatus(
new meshtastic::BluetoothStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED));
meshtastic::BluetoothStatus newDisconnectedStatus(meshtastic::BluetoothStatus::ConnectionState::DISCONNECTED);
bluetoothStatus->updateStatus(&newDisconnectedStatus);
}
// Todo: migrate this display code back into Screen class, and observe bluetoothStatus