Merge branch 'master' into tft-gui-work

This commit is contained in:
Manuel 2024-03-29 12:56:23 +01:00 committed by GitHub
commit 1a943931d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 226 additions and 50 deletions

View File

@ -15,6 +15,6 @@ rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg update platformio pkg update
pio run --environment native pio run --environment native
cp .pio/build/native/program "$OUTDIR/meshtasticd_linux_$(arch)" cp .pio/build/native/program "$OUTDIR/meshtasticd_linux_$(uname -m)"
cp bin/device-install.* $OUTDIR cp bin/device-install.* $OUTDIR
cp bin/device-update.* $OUTDIR cp bin/device-update.* $OUTDIR

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
cp "release/meshtasticd_linux_$(arch)" /usr/sbin/meshtasticd cp "release/meshtasticd_linux_$(uname -m)" /usr/sbin/meshtasticd
mkdir /etc/meshtasticd mkdir /etc/meshtasticd
if [[ -f "/etc/meshtasticd/config.yaml" ]]; then if [[ -f "/etc/meshtasticd/config.yaml" ]]; then
cp bin/config-dist.yaml /etc/meshtasticd/config-upgrade.yaml cp bin/config-dist.yaml /etc/meshtasticd/config-upgrade.yaml

View File

@ -246,7 +246,6 @@ Fsm powerFSM(&stateBOOT);
void PowerFSM_setup() void PowerFSM_setup()
{ {
bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0); bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0);
bool isInfrastructureRole = isRouter || config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER;
bool isTrackerOrSensor = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER || bool isTrackerOrSensor = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER || config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR; config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR;

View File

@ -184,7 +184,6 @@ bool EInkDisplay::connect()
// Init GxEPD2 // Init GxEPD2
adafruitDisplay->init(); adafruitDisplay->init();
adafruitDisplay->setRotation(3); adafruitDisplay->setRotation(3);
adafruitDisplay->clearScreen(); // Clearing now, so the boot logo will draw nice and smoothe (fast refresh)
} }
#elif defined(PCA10059) #elif defined(PCA10059)
{ {

View File

@ -5,7 +5,7 @@
// Constructor // Constructor
EInkDynamicDisplay::EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus) EInkDynamicDisplay::EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
: EInkDisplay(address, sda, scl, geometry, i2cBus) : EInkDisplay(address, sda, scl, geometry, i2cBus), NotifiedWorkerThread("EInkDynamicDisplay")
{ {
// If tracking ghost pixels, grab memory // If tracking ghost pixels, grab memory
#ifdef EINK_LIMIT_GHOSTING_PX #ifdef EINK_LIMIT_GHOSTING_PX
@ -112,12 +112,15 @@ void EInkDynamicDisplay::endOrDetach()
// If the GxEPD2 version reports that it has the async modifications // If the GxEPD2 version reports that it has the async modifications
#ifdef HAS_EINK_ASYNCFULL #ifdef HAS_EINK_ASYNCFULL
if (previousRefresh == FULL) { if (previousRefresh == FULL) {
asyncRefreshRunning = true; // Set the flag - picked up at start of determineMode(), next loop. asyncRefreshRunning = true; // Set the flag - checked in determineMode(); cleared by onNotify()
if (previousFrameFlags & BLOCKING) if (previousFrameFlags & BLOCKING)
awaitRefresh(); awaitRefresh();
else else {
LOG_DEBUG("Async full-refresh begins\n"); // Async begins
LOG_DEBUG("Async full-refresh begins (dropping frames)\n");
notifyLater(intervalPollAsyncRefresh, DUE_POLL_ASYNCREFRESH, true); // Hand-off to NotifiedWorkerThread
}
} }
// Fast Refresh // Fast Refresh
@ -141,7 +144,7 @@ bool EInkDynamicDisplay::determineMode()
checkInitialized(); checkInitialized();
checkForPromotion(); checkForPromotion();
#if defined(HAS_EINK_ASYNCFULL) #if defined(HAS_EINK_ASYNCFULL)
checkAsyncFullRefresh(); checkBusyAsyncRefresh();
#endif #endif
checkRateLimiting(); checkRateLimiting();
@ -252,6 +255,7 @@ void EInkDynamicDisplay::checkRateLimiting()
if (now - previousRunMs < EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000) { if (now - previousRunMs < EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000) {
refresh = SKIPPED; refresh = SKIPPED;
reason = EXCEEDED_RATELIMIT_FAST; reason = EXCEEDED_RATELIMIT_FAST;
LOG_DEBUG("refresh=SKIPPED, reason=EXCEEDED_RATELIMIT_FAST, frameFlags=0x%x\n", frameFlags);
return; return;
} }
} }
@ -447,9 +451,44 @@ void EInkDynamicDisplay::resetGhostPixelTracking()
} }
#endif // EINK_LIMIT_GHOSTING_PX #endif // EINK_LIMIT_GHOSTING_PX
// Handle any asyc tasks
void EInkDynamicDisplay::onNotify(uint32_t notification)
{
// Which task
switch (notification) {
case DUE_POLL_ASYNCREFRESH:
pollAsyncRefresh();
break;
}
}
#ifdef HAS_EINK_ASYNCFULL #ifdef HAS_EINK_ASYNCFULL
// Check the status of an "async full-refresh", and run the finish-up code if the hardware is ready // Run the post-update code if the hardware is ready
void EInkDynamicDisplay::checkAsyncFullRefresh() void EInkDynamicDisplay::pollAsyncRefresh()
{
// We shouldn't be here..
if (!asyncRefreshRunning)
return;
// Still running, check back later
if (adafruitDisplay->epd2.isBusy()) {
// Schedule next call of pollAsyncRefresh()
NotifiedWorkerThread::notifyLater(intervalPollAsyncRefresh, DUE_POLL_ASYNCREFRESH, true);
return;
}
// If asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done
adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
asyncRefreshRunning = false; // Unset the flag
LOG_DEBUG("Async full-refresh complete\n");
// Note: this code only works because of a modification to meshtastic/GxEPD2.
// It is only equipped to intercept calls to nextPage()
}
// Check the status of "async full-refresh"; skip if running
void EInkDynamicDisplay::checkBusyAsyncRefresh()
{ {
// No refresh taking place, continue with determineMode() // No refresh taking place, continue with determineMode()
if (!asyncRefreshRunning) if (!asyncRefreshRunning)
@ -472,15 +511,6 @@ void EInkDynamicDisplay::checkAsyncFullRefresh()
return; return;
} }
// If we asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done
adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
asyncRefreshRunning = false; // Unset the flag
LOG_DEBUG("Async full-refresh complete\n");
// Note: this code only works because of a modification to meshtastic/GxEPD2.
// It is only equipped to intercept calls to nextPage()
} }
// Hold control while an async refresh runs // Hold control while an async refresh runs

View File

@ -6,6 +6,7 @@
#include "EInkDisplay2.h" #include "EInkDisplay2.h"
#include "GxEPD2_BW.h" #include "GxEPD2_BW.h"
#include "concurrency/NotifiedWorkerThread.h"
/* /*
Derives from the EInkDisplay adapter class. Derives from the EInkDisplay adapter class.
@ -14,7 +15,7 @@
(Full, Fast, Skip) (Full, Fast, Skip)
*/ */
class EInkDynamicDisplay : public EInkDisplay class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWorkerThread
{ {
public: public:
// Constructor // Constructor
@ -61,13 +62,20 @@ class EInkDynamicDisplay : public EInkDisplay
REDRAW_WITH_FULL, REDRAW_WITH_FULL,
}; };
void configForFastRefresh(); // GxEPD2 code to set fast-refresh enum notificationTypes : uint8_t { // What was onNotify() called for
void configForFullRefresh(); // GxEPD2 code to set full-refresh NONE = 0, // This behavior (NONE=0) is fixed by NotifiedWorkerThread class
bool determineMode(); // Assess situation, pick a refresh type DUE_POLL_ASYNCREFRESH = 1,
void applyRefreshMode(); // Run any relevant GxEPD2 code, so next update will use correct refresh type };
void adjustRefreshCounters(); // Update fastRefreshCount const uint32_t intervalPollAsyncRefresh = 100;
bool update(); // Trigger the display update - determine mode, then call base class
void endOrDetach(); // Run the post-update code, or delegate it off to checkAsyncFullRefresh() void onNotify(uint32_t notification) override; // Handle any async tasks - overrides NotifiedWorkerThread
void configForFastRefresh(); // GxEPD2 code to set fast-refresh
void configForFullRefresh(); // GxEPD2 code to set full-refresh
bool determineMode(); // Assess situation, pick a refresh type
void applyRefreshMode(); // Run any relevant GxEPD2 code, so next update will use correct refresh type
void adjustRefreshCounters(); // Update fastRefreshCount
bool update(); // Trigger the display update - determine mode, then call base class
void endOrDetach(); // Run the post-update code, or delegate it off to checkBusyAsyncRefresh()
// Checks as part of determineMode() // Checks as part of determineMode()
void checkInitialized(); // Is this the very first frame? void checkInitialized(); // Is this the very first frame?
@ -111,10 +119,13 @@ class EInkDynamicDisplay : public EInkDisplay
// Conditional - async full refresh - only with modified meshtastic/GxEPD2 // Conditional - async full refresh - only with modified meshtastic/GxEPD2
#if defined(HAS_EINK_ASYNCFULL) #if defined(HAS_EINK_ASYNCFULL)
void checkAsyncFullRefresh(); // Check the status of "async full-refresh"; run the post-update code if the hardware is ready void pollAsyncRefresh(); // Run the post-update code if the hardware is ready
void awaitRefresh(); // Hold control while an async refresh runs void checkBusyAsyncRefresh(); // Check if display is busy running an async full-refresh (rejecting new frames)
void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay() void awaitRefresh(); // Hold control while an async refresh runs
bool asyncRefreshRunning = false; // Flag, checked by checkAsyncFullRefresh() void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay()
bool asyncRefreshRunning = false; // Flag, checked by checkBusyAsyncRefresh()
#else
void pollAsyncRefresh() {} // Dummy method. In theory, not reachable
#endif #endif
}; };

View File

@ -262,14 +262,65 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i
#ifdef USE_EINK #ifdef USE_EINK
/// Used on eink displays while in deep sleep /// Used on eink displays while in deep sleep
static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) static void drawDeepSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{ {
// Next frame should use full-refresh, and block while running, else device will sleep before async callback // Next frame should use full-refresh, and block while running, else device will sleep before async callback
EINK_ADD_FRAMEFLAG(display, COSMETIC); EINK_ADD_FRAMEFLAG(display, COSMETIC);
EINK_ADD_FRAMEFLAG(display, BLOCKING); EINK_ADD_FRAMEFLAG(display, BLOCKING);
LOG_DEBUG("Drawing deep sleep screen\n");
drawIconScreen("Sleeping...", display, state, x, y); drawIconScreen("Sleeping...", display, state, x, y);
} }
/// Used on eink displays when screen updates are paused
static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
{
LOG_DEBUG("Drawing screensaver overlay\n");
EINK_ADD_FRAMEFLAG(display, COSMETIC); // Take the opportunity for a full-refresh
// Config
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
const char *pauseText = "Screen Paused";
const char *idText = owner.short_name;
constexpr uint16_t padding = 5;
constexpr uint8_t dividerGap = 1;
constexpr uint8_t imprecision = 5; // How far the box origins can drift from center. Combat burn-in.
// Dimensions
const uint16_t idTextWidth = display->getStringWidth(idText, strlen(idText));
const uint16_t pauseTextWidth = display->getStringWidth(pauseText, strlen(pauseText));
const uint16_t boxWidth = padding + idTextWidth + padding + padding + pauseTextWidth + padding;
const uint16_t boxHeight = padding + FONT_HEIGHT_SMALL + padding;
// Position
const int16_t boxLeft = (display->width() / 2) - (boxWidth / 2) + random(-imprecision, imprecision + 1);
// const int16_t boxRight = boxLeft + boxWidth - 1;
const int16_t boxTop = (display->height() / 2) - (boxHeight / 2 + random(-imprecision, imprecision + 1));
const int16_t boxBottom = boxTop + boxHeight - 1;
const int16_t idTextLeft = boxLeft + padding;
const int16_t idTextTop = boxTop + padding;
const int16_t pauseTextLeft = boxLeft + padding + idTextWidth + padding + padding;
const int16_t pauseTextTop = boxTop + padding;
const int16_t dividerX = boxLeft + padding + idTextWidth + padding;
const int16_t dividerTop = boxTop + 1 + dividerGap;
const int16_t dividerBottom = boxBottom - 1 - dividerGap;
// Draw: box
display->setColor(EINK_WHITE);
display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2); // Clear a slightly oversized area for the box
display->setColor(EINK_BLACK);
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
// Draw: Text
display->drawString(idTextLeft, idTextTop, idText);
display->drawString(pauseTextLeft, pauseTextTop, pauseText);
display->drawString(pauseTextLeft + 1, pauseTextTop, pauseText); // Faux bold
// Draw: divider
display->drawLine(dividerX, dividerTop, dividerX, dividerBottom);
}
#endif #endif
static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
@ -948,18 +999,17 @@ Screen::~Screen()
void Screen::doDeepSleep() void Screen::doDeepSleep()
{ {
#ifdef USE_EINK #ifdef USE_EINK
static FrameCallback sleepFrames[] = {drawSleepScreen}; setOn(false, drawDeepSleepScreen);
static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]);
ui->setFrames(sleepFrames, sleepFrameCount);
ui->update();
#ifdef PIN_EINK_EN #ifdef PIN_EINK_EN
digitalWrite(PIN_EINK_EN, LOW); // power off backlight digitalWrite(PIN_EINK_EN, LOW); // power off backlight
#endif #endif
#endif #else
// Without E-Ink display:
setOn(false); setOn(false);
#endif
} }
void Screen::handleSetOn(bool on) void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
{ {
if (!useDisplay) if (!useDisplay)
return; return;
@ -978,6 +1028,10 @@ void Screen::handleSetOn(bool on)
setInterval(0); // Draw ASAP setInterval(0); // Draw ASAP
runASAP = true; runASAP = true;
} else { } else {
#ifdef USE_EINK
// eInkScreensaver parameter is usually NULL (default argument), default frame used instead
setScreensaverFrames(einkScreensaver);
#endif
LOG_INFO("Turning off screen\n"); LOG_INFO("Turning off screen\n");
dispdev->displayOff(); dispdev->displayOff();
#ifdef T_WATCH_S3 #ifdef T_WATCH_S3
@ -1028,6 +1082,7 @@ void Screen::setup()
logo_timeout *= 2; logo_timeout *= 2;
// Add frames. // Add frames.
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST);
static FrameCallback bootFrames[] = {drawBootScreen}; static FrameCallback bootFrames[] = {drawBootScreen};
static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]); static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
ui->setFrames(bootFrames, bootFrameCount); ui->setFrames(bootFrames, bootFrameCount);
@ -1283,6 +1338,58 @@ void Screen::setWelcomeFrames()
} }
} }
#ifdef USE_EINK
/// Determine which screensaver frame to use, then set the FrameCallback
void Screen::setScreensaverFrames(FrameCallback einkScreensaver)
{
// Remember current frame, restore position at power-on
uint8_t frameNumber = ui->getUiState()->currentFrame;
// Retain specified frame / overlay callback beyond scope of this method
static FrameCallback screensaverFrame;
static OverlayCallback screensaverOverlay;
// If: one-off screensaver frame passed as argument. Handles doDeepSleep()
if (einkScreensaver != NULL) {
screensaverFrame = einkScreensaver;
ui->setFrames(&screensaverFrame, 1);
}
// Else, display the usual "overlay" screensaver
else {
screensaverOverlay = drawScreensaverOverlay;
ui->setOverlays(&screensaverOverlay, 1);
}
// Request new frame, ASAP
setFastFramerate();
uint64_t startUpdate;
do {
startUpdate = millis(); // Handle impossibly unlikely corner case of a millis() overflow..
delay(1);
ui->update();
} while (ui->getUiState()->lastUpdate < startUpdate);
#ifndef USE_EINK_DYNAMICDISPLAY
// Retrofit to EInkDisplay class
delay(10);
screen->forceDisplay();
#endif
// Prepare now for next frame, shown when display wakes
ui->setOverlays(NULL, 0); // Clear overlay
setFrames(); // Return to normal display updates
ui->switchToFrame(frameNumber); // Attempt to return to same frame after power-on
// Pick a refresh method, for when display wakes
#ifdef EINK_HASQUIRK_GHOSTING
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // Really ugly to see ghosting from "screen paused"
#else
EINK_ADD_FRAMEFLAG(dispdev, RESPONSIVE); // Really nice to wake screen with a fast-refresh
#endif
}
#endif
// restore our regular frame list // restore our regular frame list
void Screen::setFrames() void Screen::setFrames()
{ {
@ -1383,7 +1490,8 @@ void Screen::handleShutdownScreen()
{ {
LOG_DEBUG("showing shutdown screen\n"); LOG_DEBUG("showing shutdown screen\n");
showingNormalScreen = false; showingNormalScreen = false;
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Use fast-refresh for next frame, no skip please
EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void { auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
drawFrameText(display, state, x, y, "Shutting down..."); drawFrameText(display, state, x, y, "Shutting down...");
@ -1391,6 +1499,7 @@ void Screen::handleShutdownScreen()
static FrameCallback frames[] = {frame}; static FrameCallback frames[] = {frame};
setFrameImmediateDraw(frames); setFrameImmediateDraw(frames);
forceDisplay();
} }
void Screen::handleRebootScreen() void Screen::handleRebootScreen()

View File

@ -73,6 +73,10 @@ class Screen
#define MILES_TO_FEET 5280 #define MILES_TO_FEET 5280
#endif #endif
// Intuitive colors. E-Ink display is inverted from OLED(?)
#define EINK_BLACK OLEDDISPLAY_COLOR::WHITE
#define EINK_WHITE OLEDDISPLAY_COLOR::BLACK
namespace graphics namespace graphics
{ {
@ -139,12 +143,12 @@ class Screen : public concurrency::OSThread
// Not thread safe - must be called before any other methods are called. // Not thread safe - must be called before any other methods are called.
void setup(); void setup();
/// Turns the screen on/off. /// Turns the screen on/off. Optionally, pass a custom screensaver frame for E-Ink
void setOn(bool on) void setOn(bool on, FrameCallback einkScreensaver = NULL)
{ {
if (!on) if (!on)
handleSetOn( // We handle off commands immediately, because they might be called because the CPU is shutting down
false); // We handle off commands immediately, because they might be called because the CPU is shutting down handleSetOn(false, einkScreensaver);
else else
enqueueCmd(ScreenCmd{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF}); enqueueCmd(ScreenCmd{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF});
} }
@ -321,6 +325,11 @@ class Screen : public concurrency::OSThread
void setWelcomeFrames(); void setWelcomeFrames();
#ifdef USE_EINK
/// Draw an image to remain on E-Ink display after screen off
void setScreensaverFrames(FrameCallback einkScreensaver = NULL);
#endif
protected: protected:
/// Updates the UI. /// Updates the UI.
// //
@ -351,7 +360,7 @@ class Screen : public concurrency::OSThread
} }
// Implementations of various commands, called from doTask(). // Implementations of various commands, called from doTask().
void handleSetOn(bool on); void handleSetOn(bool on, FrameCallback einkScreensaver = NULL);
void handleOnPress(); void handleOnPress();
void handleShowNextFrame(); void handleShowNextFrame();
void handleShowPrevFrame(); void handleShowPrevFrame();

View File

@ -333,7 +333,7 @@ static LGFX *tft = nullptr;
#include <TFT_eSPI.h> // Graphics and font library for ILI9341 driver chip #include <TFT_eSPI.h> // Graphics and font library for ILI9341 driver chip
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
#elif ARCH_PORTDUINO && !HAS_TFT #elif ARCH_PORTDUINO && HAS_SCREEN != 0 && !HAS_TFT
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip #include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
class LGFX : public lgfx::LGFX_Device class LGFX : public lgfx::LGFX_Device
@ -404,7 +404,8 @@ class LGFX : public lgfx::LGFX_Device
static LGFX *tft = nullptr; static LGFX *tft = nullptr;
#endif #endif
#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || ARCH_PORTDUINO #if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || \
(ARCH_PORTDUINO && HAS_SCREEN != 0)
#include "SPILock.h" #include "SPILock.h"
#include "TFTDisplay.h" #include "TFTDisplay.h"
#include <SPI.h> #include <SPI.h>

View File

@ -336,6 +336,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
auto changes = SEGMENT_CONFIG; auto changes = SEGMENT_CONFIG;
auto existingRole = config.device.role; auto existingRole = config.device.role;
bool isRegionUnset = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET); bool isRegionUnset = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET);
bool requiresReboot = true;
switch (c.which_payload_variant) { switch (c.which_payload_variant) {
case meshtastic_Config_device_tag: case meshtastic_Config_device_tag:
@ -375,7 +376,21 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
case meshtastic_Config_lora_tag: case meshtastic_Config_lora_tag:
LOG_INFO("Setting config: LoRa\n"); LOG_INFO("Setting config: LoRa\n");
config.has_lora = true; config.has_lora = true;
// If no lora radio parameters change, don't need to reboot
if (config.lora.use_preset == c.payload_variant.lora.use_preset && config.lora.region == c.payload_variant.lora.region &&
config.lora.modem_preset == c.payload_variant.lora.modem_preset &&
config.lora.bandwidth == c.payload_variant.lora.bandwidth &&
config.lora.spread_factor == c.payload_variant.lora.spread_factor &&
config.lora.coding_rate == c.payload_variant.lora.coding_rate &&
config.lora.tx_power == c.payload_variant.lora.tx_power &&
config.lora.frequency_offset == c.payload_variant.lora.frequency_offset &&
config.lora.override_frequency == c.payload_variant.lora.override_frequency &&
config.lora.channel_num == c.payload_variant.lora.channel_num &&
config.lora.sx126x_rx_boosted_gain == c.payload_variant.lora.sx126x_rx_boosted_gain) {
requiresReboot = false;
}
config.lora = c.payload_variant.lora; config.lora = c.payload_variant.lora;
// If we're setting region for the first time, init the region
if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) { if (isRegionUnset && config.lora.region > meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
config.lora.tx_enabled = true; config.lora.tx_enabled = true;
initRegion(); initRegion();
@ -395,7 +410,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
break; break;
} }
saveChanges(changes); saveChanges(changes, requiresReboot);
} }
void AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) void AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)

View File

@ -14,6 +14,8 @@ build_flags =
-D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
-D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. -D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
-D EINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting"
-D EINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight
lib_deps = lib_deps =
${esp32s3_base.lib_deps} ${esp32s3_base.lib_deps}
https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a

View File

@ -13,7 +13,8 @@ build_flags =
-D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates -D EINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates
-D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates -D EINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates
-D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated
;-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. ;-D EINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached.
-D EINK_HASQUIRK_VICIOUSFASTREFRESH ; Identify that pixels drawn by fast-refresh are harder to clear
lib_deps = lib_deps =
${esp32s3_base.lib_deps} ${esp32s3_base.lib_deps}
https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a https://github.com/meshtastic/GxEPD2#55f618961db45a23eff0233546430f1e5a80f63a

View File

@ -1,4 +1,4 @@
[VERSION] [VERSION]
major = 2 major = 2
minor = 3 minor = 3
build = 3 build = 4