From 915f882e1f6b741b64f3a4a1ded2445412909b74 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Sun, 24 Aug 2025 10:13:18 -0500 Subject: [PATCH 01/24] Pkc fix (#7722) --- src/mesh/Router.cpp | 5 +++-- src/modules/AdminModule.cpp | 28 +++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index cceacfe9e..1f835bca7 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -529,8 +529,9 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) #endif // Don't use PKC with Ham mode !owner.is_licensed && - // Don't use PKC if it's not explicitly requested and a non-primary channel is requested - !(p->pki_encrypted != true && p->channel > 0) && + // Don't use PKC on 'serial' or 'gpio' channels unless explicitly requested + !(p->pki_encrypted != true && (strcasecmp(channels.getName(chIndex), Channels::serialChannel) == 0 || + strcasecmp(channels.getName(chIndex), Channels::gpioChannel) == 0)) && // Check for valid keys and single node destination config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr && // Check for a known public key for the destination diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 4c893e462..9e8ce2e6b 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -505,7 +505,9 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta if (mp.decoded.want_response && !myReply) { myReply = allocErrorResponse(meshtastic_Routing_Error_NONE, &mp); } - + if (mp.pki_encrypted) { + myReply->pki_encrypted = true; + } return handled; } @@ -941,6 +943,9 @@ void AdminModule::handleGetOwner(const meshtastic_MeshPacket &req) res.which_payload_variant = meshtastic_AdminMessage_get_owner_response_tag; setPassKey(&res); myReply = allocDataProtobuf(res); + if (req.pki_encrypted) { + myReply->pki_encrypted = true; + } } } @@ -1012,6 +1017,9 @@ void AdminModule::handleGetConfig(const meshtastic_MeshPacket &req, const uint32 res.which_payload_variant = meshtastic_AdminMessage_get_config_response_tag; setPassKey(&res); myReply = allocDataProtobuf(res); + if (req.pki_encrypted) { + myReply->pki_encrypted = true; + } } } @@ -1099,6 +1107,9 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const res.which_payload_variant = meshtastic_AdminMessage_get_module_config_response_tag; setPassKey(&res); myReply = allocDataProtobuf(res); + if (req.pki_encrypted) { + myReply->pki_encrypted = true; + } } } @@ -1123,6 +1134,9 @@ void AdminModule::handleGetNodeRemoteHardwarePins(const meshtastic_MeshPacket &r } setPassKey(&r); myReply = allocDataProtobuf(r); + if (req.pki_encrypted) { + myReply->pki_encrypted = true; + } } void AdminModule::handleGetDeviceMetadata(const meshtastic_MeshPacket &req) @@ -1132,6 +1146,9 @@ void AdminModule::handleGetDeviceMetadata(const meshtastic_MeshPacket &req) r.which_payload_variant = meshtastic_AdminMessage_get_device_metadata_response_tag; setPassKey(&r); myReply = allocDataProtobuf(r); + if (req.pki_encrypted) { + myReply->pki_encrypted = true; + } } void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &req) @@ -1200,6 +1217,9 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r r.which_payload_variant = meshtastic_AdminMessage_get_device_connection_status_response_tag; setPassKey(&r); myReply = allocDataProtobuf(r); + if (req.pki_encrypted) { + myReply->pki_encrypted = true; + } } void AdminModule::handleGetChannel(const meshtastic_MeshPacket &req, uint32_t channelIndex) @@ -1211,6 +1231,9 @@ void AdminModule::handleGetChannel(const meshtastic_MeshPacket &req, uint32_t ch r.which_payload_variant = meshtastic_AdminMessage_get_channel_response_tag; setPassKey(&r); myReply = allocDataProtobuf(r); + if (req.pki_encrypted) { + myReply->pki_encrypted = true; + } } } @@ -1220,6 +1243,9 @@ void AdminModule::handleGetDeviceUIConfig(const meshtastic_MeshPacket &req) r.which_payload_variant = meshtastic_AdminMessage_get_ui_config_response_tag; r.get_ui_config_response = uiconfig; myReply = allocDataProtobuf(r); + if (req.pki_encrypted) { + myReply->pki_encrypted = true; + } } void AdminModule::reboot(int32_t seconds) From 3d825c51dd7bd837ea58f2e148c2b63c698e67c6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 24 Aug 2025 14:44:51 -0500 Subject: [PATCH 02/24] Update meshtastic/device-ui digest to 0f32b64 (#7728) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index cce4d2dcf..543205996 100644 --- a/platformio.ini +++ b/platformio.ini @@ -118,7 +118,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/3dc7cf3e233aaa8cc23492cca50541fc099ebfa1.zip + https://github.com/meshtastic/device-ui/archive/0f32b64dca418c6465763ec576509a6a2bfbc50a.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From 1a279c6053f485fdfb606145767124b208fb20a4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 06:31:38 -0500 Subject: [PATCH 03/24] Upgrade trunk (#7677) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index de38e3ec0..a0dcf2ff5 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -4,19 +4,19 @@ cli: plugins: sources: - id: trunk - ref: v1.7.1 + ref: v1.7.2 uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.461 - - renovate@41.74.0 + - checkov@3.2.465 + - renovate@41.82.10 - prettier@3.6.2 - trufflehog@3.90.5 - yamllint@1.37.1 - bandit@1.8.6 - - trivy@0.64.1 - - taplo@0.9.3 - - ruff@0.12.7 + - trivy@0.65.0 + - taplo@0.10.0 + - ruff@0.12.10 - isort@6.0.1 - markdownlint@0.45.0 - oxipng@9.1.5 @@ -25,7 +25,7 @@ lint: - flake8@7.3.0 - hadolint@2.12.1-beta - shfmt@3.6.0 - - shellcheck@0.10.0 + - shellcheck@0.11.0 - black@25.1.0 - git-diff-check - gitleaks@8.28.0 From 3f5c30e3b35c5a8c779ff3542c52cfe7b107c871 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Tue, 26 Aug 2025 16:35:25 +0200 Subject: [PATCH 04/24] T-Lora Pager (#7613) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * initial commit * preset rotary1 encoder * define TAB+ESC * haptic feedback * allow switch off haptic feedback * enable audio amplifier * include PR4684 * fix for tft target * add ES8311 audio codec * fix KB scan duplicate * display workaround to avoid debris * fix debris on display * keyboard backlight * enable screen options * fsm based bounce-free rotary encoder implementation * use fsm RotaryEncoder only for T-Lora Pager * change inputbroker default config to allow using rotary wheel for screens AND menues --------- Co-authored-by: Thomas Göttgens Co-authored-by: Ben Meadors --- src/detect/ScanI2C.h | 3 +- src/detect/ScanI2CTwoWire.cpp | 10 +- src/graphics/Screen.cpp | 4 +- src/graphics/ScreenFonts.h | 2 +- src/graphics/TFTDisplay.cpp | 199 +++++++++++++-- src/graphics/TFTDisplay.h | 2 + src/graphics/draw/DebugRenderer.cpp | 8 +- src/graphics/draw/MenuHandler.cpp | 10 +- src/graphics/draw/UIRenderer.cpp | 2 +- src/graphics/images.h | 3 +- src/input/RotaryEncoderImpl.cpp | 73 ++++++ src/input/RotaryEncoderImpl.h | 28 +++ src/input/TLoraPagerKeyboard.cpp | 230 ++++++++++++++++++ src/input/TLoraPagerKeyboard.h | 23 +- src/input/cardKbI2cImpl.cpp | 4 +- src/main.cpp | 37 ++- src/main.h | 2 +- src/mesh/NodeDB.cpp | 11 +- src/modules/Modules.cpp | 10 + src/platform/esp32/architecture.h | 2 + .../extra_variants/t_lora_pager/variant.cpp | 27 ++ variants/esp32s3/tlora-pager/pins_arduino.h | 19 ++ variants/esp32s3/tlora-pager/platformio.ini | 70 ++++++ variants/esp32s3/tlora-pager/variant.h | 125 ++++++++++ 24 files changed, 855 insertions(+), 49 deletions(-) create mode 100644 src/input/RotaryEncoderImpl.cpp create mode 100644 src/input/RotaryEncoderImpl.h create mode 100644 src/input/TLoraPagerKeyboard.cpp create mode 100644 src/platform/extra_variants/t_lora_pager/variant.cpp create mode 100644 variants/esp32s3/tlora-pager/pins_arduino.h create mode 100644 variants/esp32s3/tlora-pager/platformio.ini create mode 100644 variants/esp32s3/tlora-pager/variant.h diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index c1358861b..e46c6f623 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -79,7 +79,8 @@ class ScanI2C BQ27220, LTR553ALS, BHI260AP, - BMM150 + BMM150, + DRV2605 } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 8b3670cd9..9aef9defe 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -483,8 +483,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) type = MLX90614; logFoundDevice("MLX90614", (uint8_t)addr.address); } else { - type = MPR121KB; - logFoundDevice("MPR121KB", (uint8_t)addr.address); + registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // DRV2605_REG_STATUS + if (registerValue == 0xe0) { + type = DRV2605; + logFoundDevice("DRV2605", (uint8_t)addr.address); + } else { + type = MPR121KB; + logFoundDevice("MPR121KB", (uint8_t)addr.address); + } } break; diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index fa71e17d8..dea08d5ba 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -318,7 +318,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O dispdev = new SSD1306Wire(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #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(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) dispdev = new TFTDisplay(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE); #elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY) @@ -550,7 +550,7 @@ void Screen::setup() #else if (!config.display.flip_screen) { #if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \ - defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) + defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) static_cast(dispdev)->flipScreenVertically(); #elif defined(USE_ST7789) static_cast(dispdev)->flipScreenVertically(); diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h index 84ec45977..a25417b05 100644 --- a/src/graphics/ScreenFonts.h +++ b/src/graphics/ScreenFonts.h @@ -73,7 +73,7 @@ #endif #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS)) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) // The screen is bigger so use bigger fonts #define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19 diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp index f8787612f..b1814005e 100644 --- a/src/graphics/TFTDisplay.cpp +++ b/src/graphics/TFTDisplay.cpp @@ -562,6 +562,91 @@ class LGFX : public lgfx::LGFX_Device static LGFX *tft = nullptr; +#elif defined(ST7796_CS) +#include // Graphics and font library for ST7796 driver chip + +class LGFX : public lgfx::LGFX_Device +{ + lgfx::Panel_ST7796 _panel_instance; + lgfx::Bus_SPI _bus_instance; + lgfx::Light_PWM _light_instance; + + public: + LGFX(void) + { + { + auto cfg = _bus_instance.config(); + + // SPI + cfg.spi_host = ST7796_SPI_HOST; + cfg.spi_mode = 0; + cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing + // 80MHz by an integer) + cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving + cfg.spi_3wire = false; + cfg.use_lock = true; // Set to true to use transaction locking + cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch / + // SPI_DMA_CH_AUTO=auto setting) + cfg.pin_sclk = ST7796_SCK; // Set SPI SCLK pin number + cfg.pin_mosi = ST7796_SDA; // Set SPI MOSI pin number + cfg.pin_miso = ST7796_MISO; // Set SPI MISO pin number (-1 = disable) + cfg.pin_dc = ST7796_RS; // Set SPI DC pin number (-1 = disable) + + _bus_instance.config(cfg); // applies the set value to the bus. + _panel_instance.setBus(&_bus_instance); // set the bus on the panel. + } + + { // Set the display panel control. + auto cfg = _panel_instance.config(); // Gets a structure for display panel settings. + + cfg.pin_cs = ST7796_CS; // Pin number where CS is connected (-1 = disable) + cfg.pin_rst = ST7796_RESET; // Pin number where RST is connected (-1 = disable) + cfg.pin_busy = ST7796_BUSY; // Pin number where BUSY is connected (-1 = disable) + + // cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC + // cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC + cfg.panel_width = TFT_WIDTH; // actual displayable width + cfg.panel_height = TFT_HEIGHT; // actual displayable height + cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction + cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction + cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is mirrored) +#ifdef TFT_DUMMY_READ_PIXELS + cfg.dummy_read_pixel = TFT_DUMMY_READ_PIXELS; // Number of bits for dummy read before pixel readout +#else + cfg.dummy_read_pixel = 8; // Number of bits for dummy read before pixel readout +#endif + cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read + cfg.readable = true; // Set to true if data can be read + cfg.invert = true; // Set to true if the light/darkness of the panel is reversed + cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped + cfg.dlen_16bit = + false; // Set to true for panels that transmit data length in 16-bit units with 16-bit parallel or SPI + cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.) + + _panel_instance.config(cfg); + } + +#ifdef ST7796_BL + // Set the backlight control. (delete if not necessary) + { + auto cfg = _light_instance.config(); // Gets a structure for backlight settings. + + cfg.pin_bl = ST7796_BL; // Pin number to which the backlight is connected + cfg.invert = false; // true to invert the brightness of the backlight + cfg.freq = 44100; + cfg.pwm_channel = 7; + + _light_instance.config(cfg); + _panel_instance.setLight(&_light_instance); // Set the backlight on the panel. + } +#endif + + setPanel(&_panel_instance); // Sets the panel to use. + } +}; + +static LGFX *tft = nullptr; + #elif defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) #include // Graphics and font library for ILI9341/ILI9342 driver chip @@ -997,8 +1082,9 @@ static LGFX *tft = nullptr; #endif -#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \ - defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || (ARCH_PORTDUINO && HAS_SCREEN != 0) +#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ST7796_CS) || defined(ILI9341_DRIVER) || \ + defined(ILI9342_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST72xx_DE) || \ + (ARCH_PORTDUINO && HAS_SCREEN != 0) #include "SPILock.h" #include "TFTDisplay.h" #include @@ -1047,32 +1133,97 @@ void TFTDisplay::display(bool fromBlank) { if (fromBlank) tft->fillScreen(TFT_BLACK); - // tft->clear(); + concurrency::LockGuard g(spiLock); - uint16_t x, y; + uint32_t x, y; + uint32_t y_byteIndex; + uint8_t y_byteMask; + uint32_t x_FirstPixelUpdate; + uint32_t x_LastPixelUpdate; + bool isset, dblbuf_isset; + uint16_t colorTftMesh, colorTftBlack; + bool somethingChanged = false; - for (y = 0; y < displayHeight; y++) { - for (x = 0; x < displayWidth; x++) { - auto isset = buffer[x + (y / 8) * displayWidth] & (1 << (y & 7)); + // Store colors byte-reversed so that TFT_eSPI doesn't have to swap bytes in a separate step + colorTftMesh = (TFT_MESH >> 8) | ((TFT_MESH & 0xFF) << 8); + colorTftBlack = (TFT_BLACK >> 8) | ((TFT_BLACK & 0xFF) << 8); + + y = 0; + while (y < displayHeight) { + y_byteIndex = (y / 8) * displayWidth; + y_byteMask = (1 << (y & 7)); + + // Step 1: Do a quick scan of 8 rows together. This allows fast-forwarding over unchanged screen areas. + if (y_byteMask == 1) { if (!fromBlank) { - // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent - auto dblbuf_isset = buffer_back[x + (y / 8) * displayWidth] & (1 << (y & 7)); - if (isset != dblbuf_isset) { - tft->drawPixel(x, y, isset ? TFT_MESH : TFT_BLACK); + for (x = 0; x < displayWidth; x++) { + if (buffer[x + y_byteIndex] != buffer_back[x + y_byteIndex]) + break; } - } else if (isset) { - tft->drawPixel(x, y, TFT_MESH); + } else { + for (x = 0; x < displayWidth; x++) { + if (buffer[x + y_byteIndex] != 0) + break; + } + } + if (x >= displayWidth) { + // No changed pixels found in these 8 rows, fast-forward to the next 8 + y = y + 8; + continue; } } + + // Step 2: Scan each of the 8 rows individually. Find the first pixel in each row that needs updating + for (x_FirstPixelUpdate = 0; x_FirstPixelUpdate < displayWidth; x_FirstPixelUpdate++) { + isset = buffer[x_FirstPixelUpdate + y_byteIndex] & y_byteMask; + + if (!fromBlank) { + // get src pixel in the page based ordering the OLED lib uses + dblbuf_isset = buffer_back[x_FirstPixelUpdate + y_byteIndex] & y_byteMask; + if (isset != dblbuf_isset) { + break; + } + } else if (isset) { + break; + } + } + + // Did we find a pixel that needs updating on this row? + if (x_FirstPixelUpdate < displayWidth) { + + // Quickly write out the first changed pixel (saves another array lookup) + linePixelBuffer[x_FirstPixelUpdate] = isset ? colorTftMesh : colorTftBlack; + x_LastPixelUpdate = x_FirstPixelUpdate; + + // Step 3: copy all remaining pixels in this row into the pixel line buffer, + // while also recording the last pixel in the row that needs updating + for (x = x_FirstPixelUpdate + 1; x < displayWidth; x++) { + isset = buffer[x + y_byteIndex] & y_byteMask; + linePixelBuffer[x] = isset ? colorTftMesh : colorTftBlack; + + if (!fromBlank) { + dblbuf_isset = buffer_back[x + y_byteIndex] & y_byteMask; + if (isset != dblbuf_isset) { + x_LastPixelUpdate = x; + } + } else if (isset) { + x_LastPixelUpdate = x; + } + } + + // Step 4: Send the changed pixels on this line to the screen as a single block transfer. + // This function accepts pixel data MSB first so it can dump the memory straight out the SPI port. + tft->pushRect(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1, + &linePixelBuffer[x_FirstPixelUpdate]); + + somethingChanged = true; + } + y++; } // Copy the Buffer to the Back Buffer - for (y = 0; y < (displayHeight / 8); y++) { - for (x = 0; x < displayWidth; x++) { - uint16_t pos = x + y * displayWidth; - buffer_back[pos] = buffer[pos]; - } - } + if (somethingChanged) + memcpy(buffer_back, buffer, displayBufferSize); } void TFTDisplay::sdlLoop() @@ -1264,13 +1415,21 @@ bool TFTDisplay::connect() tft->setRotation(1); // T-Deck has the TFT in landscape #elif defined(T_WATCH_S3) tft->setRotation(2); // T-Watch S3 left-handed orientation -#elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR) +#elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR) || defined(T_LORA_PAGER) tft->setRotation(0); // use config.yaml to set rotation #else tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label #endif tft->fillScreen(TFT_BLACK); + if (this->linePixelBuffer == NULL) { + this->linePixelBuffer = (uint16_t *)malloc(sizeof(uint16_t) * displayWidth); + + if (!this->linePixelBuffer) { + LOG_ERROR("Not enough memory to create TFT line buffer\n"); + return false; + } + } return true; } diff --git a/src/graphics/TFTDisplay.h b/src/graphics/TFTDisplay.h index 60adfdf7c..27672ad29 100644 --- a/src/graphics/TFTDisplay.h +++ b/src/graphics/TFTDisplay.h @@ -58,4 +58,6 @@ class TFTDisplay : public OLEDDisplay // Connect to the display virtual bool connect() override; + + uint16_t *linePixelBuffer = nullptr; }; \ No newline at end of file diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 5d9b5a33b..a0f29f10d 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -94,7 +94,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat, (storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ + ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgQuestionL1); @@ -106,7 +107,7 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 #endif } else { #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS)) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, imgSFL1); @@ -121,7 +122,8 @@ void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16 } else { // TODO: Raspberry Pi supports more than just the one screen size #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || ARCH_PORTDUINO) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS) || \ + ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(screen->ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgInfoL1); diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 512f650ec..bcd8d8ee8 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -434,8 +434,8 @@ void menuHandler::systemBaseMenu() optionsArray[options] = "Notifications"; optionsEnumArray[options++] = Notifications; -#if defined(ST7789_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || \ - defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT +#if defined(ST7789_CS) || defined(ST7796_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || \ + defined(USE_SH1107) || defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT optionsArray[options] = "Screen Options"; optionsEnumArray[options++] = ScreenOptions; #endif @@ -725,7 +725,7 @@ void menuHandler::BrightnessPickerMenu() #if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) // For HELTEC devices, use analogWrite to control backlight analogWrite(VTFT_LEDA, uiconfig.screen_brightness); -#elif defined(ST7789_CS) +#elif defined(ST7789_CS) || defined(ST7796_CS) static_cast(screen->getDisplayDevice())->setDisplayBrightness(uiconfig.screen_brightness); #elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) screen->getDisplayDevice()->setBrightness(uiconfig.screen_brightness); @@ -768,7 +768,7 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display) bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsCount = 10; bannerOptions.bannerCallback = [display](int selected) -> void { -#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT +#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT uint8_t TFT_MESH_r = 0; uint8_t TFT_MESH_g = 0; uint8_t TFT_MESH_b = 0; @@ -1045,7 +1045,7 @@ void menuHandler::screenOptionsMenu() } // Only show screen color for TFT displays -#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || HAS_TFT +#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || defined(T_DECK) || defined(T_LORA_PAGER) || HAS_TFT optionsArray[options] = "Screen Color"; optionsEnumArray[options++] = ScreenColor; #endif diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index 71d92616f..049722df8 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -194,7 +194,7 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes } #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS)) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(HX8357_CS) || defined(ST7796_CS)) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) if (isHighResolution) { diff --git a/src/graphics/images.h b/src/graphics/images.h index beef3a1b2..c66e4b992 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -27,7 +27,8 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03 0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f}; #if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \ - defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || ARCH_PORTDUINO) && \ + defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || defined(ILI9488_CS) || defined(ST7796_CS) || \ + ARCH_PORTDUINO) && \ !defined(DISPLAY_FORCE_SMALL_FONTS) const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff}; const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f}; diff --git a/src/input/RotaryEncoderImpl.cpp b/src/input/RotaryEncoderImpl.cpp new file mode 100644 index 000000000..b71e800e0 --- /dev/null +++ b/src/input/RotaryEncoderImpl.cpp @@ -0,0 +1,73 @@ +#ifdef T_LORA_PAGER + +#include "RotaryEncoderImpl.h" +#include "InputBroker.h" +#include "RotaryEncoder.h" + +#define ORIGIN_NAME "RotaryEncoder" + +RotaryEncoderImpl *rotaryEncoderImpl; + +RotaryEncoderImpl::RotaryEncoderImpl() : concurrency::OSThread(ORIGIN_NAME), originName(ORIGIN_NAME) {} + +bool RotaryEncoderImpl::init() +{ + if (!moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.inputbroker_pin_a == 0 || + moduleConfig.canned_message.inputbroker_pin_b == 0) { + // Input device is disabled. + disable(); + return false; + } + + eventCw = static_cast(moduleConfig.canned_message.inputbroker_event_cw); + eventCcw = static_cast(moduleConfig.canned_message.inputbroker_event_ccw); + eventPressed = static_cast(moduleConfig.canned_message.inputbroker_event_press); + + rotary = new RotaryEncoder(moduleConfig.canned_message.inputbroker_pin_a, moduleConfig.canned_message.inputbroker_pin_b, + moduleConfig.canned_message.inputbroker_pin_press); + rotary->resetButton(); + + inputBroker->registerSource(this); + + LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a, + moduleConfig.canned_message.inputbroker_pin_b, moduleConfig.canned_message.inputbroker_pin_press, eventCw, eventCcw, + eventPressed); + return true; +} + +int32_t RotaryEncoderImpl::runOnce() +{ + InputEvent e; + e.inputEvent = INPUT_BROKER_NONE; + e.source = this->originName; + + static uint32_t lastPressed = millis(); + if (rotary->readButton() == RotaryEncoder::ButtonState::BUTTON_PRESSED) { + if (lastPressed + 200 < millis()) { + LOG_DEBUG("Rotary event Press"); + lastPressed = millis(); + e.inputEvent = this->eventPressed; + } + } else { + switch (rotary->process()) { + case RotaryEncoder::DIRECTION_CW: + LOG_DEBUG("Rotary event CW"); + e.inputEvent = this->eventCw; + break; + case RotaryEncoder::DIRECTION_CCW: + LOG_DEBUG("Rotary event CCW"); + e.inputEvent = this->eventCcw; + break; + default: + break; + } + } + + if (e.inputEvent != INPUT_BROKER_NONE) { + this->notifyObservers(&e); + } + + return 20; +} + +#endif \ No newline at end of file diff --git a/src/input/RotaryEncoderImpl.h b/src/input/RotaryEncoderImpl.h new file mode 100644 index 000000000..ae2a7c6fd --- /dev/null +++ b/src/input/RotaryEncoderImpl.h @@ -0,0 +1,28 @@ +#pragma once + +// This is a non-interrupt version of RotaryEncoder which is based on a debounce inherent FSM table (see RotaryEncoder library) + +#include "InputBroker.h" +#include "concurrency/OSThread.h" +#include "mesh/NodeDB.h" + +class RotaryEncoder; + +class RotaryEncoderImpl : public Observable, public concurrency::OSThread +{ + public: + RotaryEncoderImpl(); + bool init(void); + + protected: + virtual int32_t runOnce() override; + + input_broker_event eventCw = INPUT_BROKER_NONE; + input_broker_event eventCcw = INPUT_BROKER_NONE; + input_broker_event eventPressed = INPUT_BROKER_NONE; + + RotaryEncoder *rotary; + const char *originName; +}; + +extern RotaryEncoderImpl *rotaryEncoderImpl; diff --git a/src/input/TLoraPagerKeyboard.cpp b/src/input/TLoraPagerKeyboard.cpp new file mode 100644 index 000000000..b3f62a36c --- /dev/null +++ b/src/input/TLoraPagerKeyboard.cpp @@ -0,0 +1,230 @@ +#if defined(T_LORA_PAGER) + +#include "TLoraPagerKeyboard.h" +#include "main.h" + +#ifndef LEDC_BACKLIGHT_CHANNEL +#define LEDC_BACKLIGHT_CHANNEL 4 +#endif + +#ifndef LEDC_BACKLIGHT_BIT_WIDTH +#define LEDC_BACKLIGHT_BIT_WIDTH 8 +#endif + +#ifndef LEDC_BACKLIGHT_FREQ +#define LEDC_BACKLIGHT_FREQ 1000 // Hz +#endif + +#define _TCA8418_COLS 10 +#define _TCA8418_ROWS 4 +#define _TCA8418_NUM_KEYS 31 + +#define _TCA8418_MULTI_TAP_THRESHOLD 1500 + +using Key = TCA8418KeyboardBase::TCA8418Key; + +constexpr uint8_t modifierRightShiftKey = 29 - 1; // keynum -1 +constexpr uint8_t modifierRightShift = 0b0001; +constexpr uint8_t modifierSymKey = 21 - 1; +constexpr uint8_t modifierSym = 0b0010; + +// Num chars per key, Modulus for rotating through characters +static uint8_t TLoraPagerTapMod[_TCA8418_NUM_KEYS] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}; + +static unsigned char TLoraPagerTapMap[_TCA8418_NUM_KEYS][3] = {{'q', 'Q', '1'}, + {'w', 'W', '2'}, + {'e', 'E', '3'}, + {'r', 'R', '4'}, + {'t', 'T', '5'}, + {'y', 'Y', '6'}, + {'u', 'U', '7'}, + {'i', 'I', '8'}, + {'o', 'O', '9'}, + {'p', 'P', '0'}, + {'a', 'A', '*'}, + {'s', 'S', '/'}, + {'d', 'D', '+'}, + {'f', 'F', '-'}, + {'g', 'G', '='}, + {'h', 'H', ':'}, + {'j', 'J', '\''}, + {'k', 'K', '"'}, + {'l', 'L', '@'}, + {Key::SELECT, 0x00, Key::TAB}, + {0x00, 0x00, 0x00}, + {'z', 'Z', '_'}, + {'x', 'X', '$'}, + {'c', 'C', ';'}, + {'v', 'V', '?'}, + {'b', 'B', '!'}, + {'n', 'N', ','}, + {'m', 'M', '.'}, + {0x00, 0x00, 0x00}, + {Key::BSP, 0x00, Key::ESC}, + {' ', 0x00, Key::BL_TOGGLE}}; + +TLoraPagerKeyboard::TLoraPagerKeyboard() + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), + last_tap(0L), char_idx(0), tap_interval(0) +{ +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH); +#else + ledcSetup(LEDC_BACKLIGHT_CHANNEL, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH); + ledcAttachPin(KB_BL_PIN, LEDC_BACKLIGHT_CHANNEL); +#endif + reset(); +} + +void TLoraPagerKeyboard::reset(void) +{ + TCA8418KeyboardBase::reset(); + pinMode(KB_BL_PIN, OUTPUT); + digitalWrite(KB_BL_PIN, LOW); + setBacklight(false); +} + +// handle multi-key presses (shift and alt) +void TLoraPagerKeyboard::trigger() +{ + uint8_t count = keyCount(); + if (count == 0) + return; + for (uint8_t i = 0; i < count; ++i) { + uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i); + uint8_t key = k & 0x7F; + if (k & 0x80) { + pressed(key); + } else { + released(); + state = Idle; + } + } +} + +void TLoraPagerKeyboard::setBacklight(bool on) +{ + toggleBacklight(!on); +} + +void TLoraPagerKeyboard::pressed(uint8_t key) +{ + if (state == Init || state == Busy) { + return; + } + if (config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_ALL_ENABLED || + config.device.buzzer_mode == meshtastic_Config_DeviceConfig_BuzzerMode_SYSTEM_ONLY) { + hapticFeedback(); + } + + if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) { + modifierFlag = 0; + } + + uint8_t next_key = 0; + int row = (key - 1) / 10; + int col = (key - 1) % 10; + + if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) { + return; // Invalid key + } + + next_key = row * _TCA8418_COLS + col; + state = Held; + + uint32_t now = millis(); + tap_interval = now - last_tap; + + updateModifierFlag(next_key); + if (isModifierKey(next_key)) { + last_modifier_time = now; + } + + if (tap_interval < 0) { + last_tap = 0; + state = Busy; + return; + } + + if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) { + char_idx = 0; + } else { + char_idx += 1; + } + + last_key = next_key; + last_tap = now; +} + +void TLoraPagerKeyboard::released() +{ + if (state != Held) { + return; + } + + if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) { + last_key = -1; + state = Idle; + return; + } + + uint32_t now = millis(); + last_tap = now; + + if (TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]] == Key::BL_TOGGLE) { + toggleBacklight(); + return; + } + + queueEvent(TLoraPagerTapMap[last_key][modifierFlag % TLoraPagerTapMod[last_key]]); + if (isModifierKey(last_key) == false) + modifierFlag = 0; +} + +void TLoraPagerKeyboard::hapticFeedback() +{ + drv.setWaveform(0, 14); // strong buzz 100% + drv.setWaveform(1, 0); // end waveform + drv.go(); +} + +// toggle brightness of the backlight in three steps +void TLoraPagerKeyboard::toggleBacklight(bool off) +{ + static uint32_t brightness = 0; + if (off) { + brightness = 0; + } else { + if (brightness == 0) { + brightness = 40; + } else if (brightness == 40) { + brightness = 127; + } else if (brightness >= 127) { + brightness = 0; + } + } + LOG_DEBUG("Toggle backlight: %d", brightness); + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + ledcWrite(KB_BL_PIN, brightness); +#else + ledcWrite(LEDC_BACKLIGHT_CHANNEL, brightness); +#endif +} + +void TLoraPagerKeyboard::updateModifierFlag(uint8_t key) +{ + if (key == modifierRightShiftKey) { + modifierFlag ^= modifierRightShift; + } else if (key == modifierSymKey) { + modifierFlag ^= modifierSym; + } +} + +bool TLoraPagerKeyboard::isModifierKey(uint8_t key) +{ + return (key == modifierRightShiftKey || key == modifierSymKey); +} + +#endif \ No newline at end of file diff --git a/src/input/TLoraPagerKeyboard.h b/src/input/TLoraPagerKeyboard.h index d31b05978..4dabbac64 100644 --- a/src/input/TLoraPagerKeyboard.h +++ b/src/input/TLoraPagerKeyboard.h @@ -4,9 +4,26 @@ class TLoraPagerKeyboard : public TCA8418KeyboardBase { public: TLoraPagerKeyboard(); - void setBacklight(bool on) override{}; + void reset(void); + void trigger(void) override; + void setBacklight(bool on) override; + virtual ~TLoraPagerKeyboard() {} protected: - void pressed(uint8_t key) override{}; - void released(void) override{}; + void pressed(uint8_t key) override; + void released(void) override; + void hapticFeedback(void); + + void updateModifierFlag(uint8_t key); + bool isModifierKey(uint8_t key); + void toggleBacklight(bool off = false); + + private: + uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed + uint32_t last_modifier_time; // Timestamp of the last modifier key press + int8_t last_key; + int8_t next_key; + uint32_t last_tap; + uint8_t char_idx; + int32_t tap_interval; }; diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp index fcbdd0a3f..9b0926a1d 100644 --- a/src/input/cardKbI2cImpl.cpp +++ b/src/input/cardKbI2cImpl.cpp @@ -12,8 +12,8 @@ void CardKbI2cImpl::init() #if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(I2C_NO_RESCAN) if (cardkb_found.address == 0x00) { LOG_DEBUG("Rescan for I2C keyboard"); - uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR, XPOWERS_AXP192_AXP2101_ADDRESS}; - uint8_t i2caddr_asize = 5; + uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR, TCA8418_KB_ADDR}; + uint8_t i2caddr_asize = sizeof(i2caddr_scan) / sizeof(i2caddr_scan[0]); auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); #if WIRE_INTERFACES_COUNT == 2 diff --git a/src/main.cpp b/src/main.cpp index 0260cbc07..338487914 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -135,8 +135,9 @@ AccelerometerThread *accelerometerThread = nullptr; AudioThread *audioThread = nullptr; #endif -#ifdef USE_PCA9557 -PCA9557 IOEXP; +#ifdef USE_XL9555 +#include "ExtensionIOXL9555.hpp" +ExtensionIOXL9555 io; #endif #if HAS_TFT @@ -201,7 +202,7 @@ ScanI2C::FoundDevice rgb_found = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE, /// The I2C address of our Air Quality Indicator (if found) ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE; -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(T_LORA_PAGER) Adafruit_DRV2605 drv; #endif @@ -359,6 +360,30 @@ void setup() digitalWrite(SDCARD_CS, HIGH); pinMode(PIN_EINK_CS, OUTPUT); digitalWrite(PIN_EINK_CS, HIGH); +#elif defined(T_LORA_PAGER) + pinMode(LORA_CS, OUTPUT); + digitalWrite(LORA_CS, HIGH); + pinMode(SDCARD_CS, OUTPUT); + digitalWrite(SDCARD_CS, HIGH); + pinMode(TFT_CS, OUTPUT); + digitalWrite(TFT_CS, HIGH); + // io expander + io.begin(Wire, XL9555_SLAVE_ADDRESS0, SDA, SCL); + io.pinMode(EXPANDS_DRV_EN, OUTPUT); + io.digitalWrite(EXPANDS_DRV_EN, HIGH); + io.pinMode(EXPANDS_AMP_EN, OUTPUT); + io.digitalWrite(EXPANDS_AMP_EN, HIGH); + io.pinMode(EXPANDS_LORA_EN, OUTPUT); + io.digitalWrite(EXPANDS_LORA_EN, HIGH); + io.pinMode(EXPANDS_GPS_EN, OUTPUT); + io.digitalWrite(EXPANDS_GPS_EN, HIGH); + io.pinMode(EXPANDS_KB_EN, OUTPUT); + io.digitalWrite(EXPANDS_KB_EN, HIGH); + io.pinMode(EXPANDS_SD_EN, OUTPUT); + io.digitalWrite(EXPANDS_SD_EN, HIGH); + io.pinMode(EXPANDS_GPIO_EN, OUTPUT); + io.digitalWrite(EXPANDS_GPIO_EN, HIGH); + io.pinMode(EXPANDS_SD_PULLEN, INPUT); #endif concurrency::hasBeenSetup = true; @@ -805,7 +830,7 @@ void setup() #endif #endif -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(T_LORA_PAGER) drv.begin(); drv.selectLibrary(1); // I2C trigger by sending 'go' command @@ -851,7 +876,7 @@ 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(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) screen = new graphics::Screen(screen_found, screen_model, screen_geometry); #elif defined(ARCH_PORTDUINO) if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) && @@ -1114,7 +1139,7 @@ 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(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) if (screen) screen->setup(); #elif defined(ARCH_PORTDUINO) diff --git a/src/main.h b/src/main.h index 3568daad2..ef1f241ef 100644 --- a/src/main.h +++ b/src/main.h @@ -41,7 +41,7 @@ extern bool eink_found; extern bool pmu_found; extern bool isUSBPowered; -#ifdef T_WATCH_S3 +#if defined(T_WATCH_S3) || defined(T_LORA_PAGER) #include extern Adafruit_DRV2605 drv; #endif diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 18014eb02..d544d0174 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -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(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS) 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); @@ -830,6 +830,15 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.external_notification.alert_message = true; moduleConfig.external_notification.output_ms = 1000; moduleConfig.external_notification.nag_timeout = 60; +#endif +#ifdef T_LORA_PAGER + moduleConfig.canned_message.updown1_enabled = true; + moduleConfig.canned_message.inputbroker_pin_a = ROTARY_A; + moduleConfig.canned_message.inputbroker_pin_b = ROTARY_B; + moduleConfig.canned_message.inputbroker_pin_press = ROTARY_PRESS; + moduleConfig.canned_message.inputbroker_event_cw = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar(28); + moduleConfig.canned_message.inputbroker_event_ccw = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar(29); + moduleConfig.canned_message.inputbroker_event_press = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; #endif moduleConfig.has_canned_message = true; #if USERPREFS_MQTT_ENABLED && !MESHTASTIC_EXCLUDE_MQTT diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index 3528f57f5..0d405fa81 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -3,6 +3,7 @@ #include "buzz/BuzzerFeedbackThread.h" #include "input/ExpressLRSFiveWay.h" #include "input/InputBroker.h" +#include "input/RotaryEncoderImpl.h" #include "input/RotaryEncoderInterruptImpl1.h" #include "input/SerialKeyboardImpl.h" #include "input/TrackballInterruptImpl1.h" @@ -170,11 +171,20 @@ void setupModules() delete rotaryEncoderInterruptImpl1; rotaryEncoderInterruptImpl1 = nullptr; } +#ifdef T_LORA_PAGER + // use a special FSM based rotary encoder version for T-LoRa Pager + rotaryEncoderImpl = new RotaryEncoderImpl(); + if (!rotaryEncoderImpl->init()) { + delete rotaryEncoderImpl; + rotaryEncoderImpl = nullptr; + } +#else upDownInterruptImpl1 = new UpDownInterruptImpl1(); if (!upDownInterruptImpl1->init()) { delete upDownInterruptImpl1; upDownInterruptImpl1 = nullptr; } +#endif cardKbI2cImpl = new CardKbI2cImpl(); cardKbI2cImpl->init(); #ifdef INPUTBROKER_MATRIX_TYPE diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 522e862ac..cb0f0dab3 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -192,6 +192,8 @@ #define HW_VENDOR meshtastic_HardwareModel_LINK_32 #elif defined(T_DECK_PRO) #define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO +#elif defined(T_LORA_PAGER) +#define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER #endif // ----------------------------------------------------------------------------- diff --git a/src/platform/extra_variants/t_lora_pager/variant.cpp b/src/platform/extra_variants/t_lora_pager/variant.cpp new file mode 100644 index 000000000..ea5773d30 --- /dev/null +++ b/src/platform/extra_variants/t_lora_pager/variant.cpp @@ -0,0 +1,27 @@ +#include "configuration.h" + +#ifdef T_LORA_PAGER + +#include "AudioBoard.h" + +DriverPins PinsAudioBoardES8311; +AudioBoard board(AudioDriverES8311, PinsAudioBoardES8311); + +// TLora Pager specific init +void lateInitVariant() +{ + // AudioDriverLogger.begin(Serial, AudioDriverLogLevel::Debug); + // I2C: function, scl, sda + PinsAudioBoardES8311.addI2C(PinFunction::CODEC, Wire); + // I2S: function, mclk, bck, ws, data_out, data_in + PinsAudioBoardES8311.addI2S(PinFunction::CODEC, DAC_I2S_MCLK, DAC_I2S_BCK, DAC_I2S_WS, DAC_I2S_DOUT, DAC_I2S_DIN); + + // configure codec + CodecConfig cfg; + cfg.input_device = ADC_INPUT_LINE1; + cfg.output_device = DAC_OUTPUT_ALL; + cfg.i2s.bits = BIT_LENGTH_16BITS; + cfg.i2s.rate = RATE_44K; + board.begin(cfg); +} +#endif \ No newline at end of file diff --git a/variants/esp32s3/tlora-pager/pins_arduino.h b/variants/esp32s3/tlora-pager/pins_arduino.h new file mode 100644 index 000000000..a6321f510 --- /dev/null +++ b/variants/esp32s3/tlora-pager/pins_arduino.h @@ -0,0 +1,19 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// used for keyboard, battery gauge, charger and haptic driver +static const uint8_t SDA = 3; +static const uint8_t SCL = 2; + +// Default SPI will be mapped to Radio +static const uint8_t SS = 36; +static const uint8_t MOSI = 34; +static const uint8_t MISO = 33; +static const uint8_t SCK = 35; + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini new file mode 100644 index 000000000..3d77d879c --- /dev/null +++ b/variants/esp32s3/tlora-pager/platformio.ini @@ -0,0 +1,70 @@ +; LilyGo T-Lora-Pager +[env:tlora-pager] +extends = esp32s3_base +board = t-deck-pro ; same as T-Deck Pro +board_check = true +board_build.partitions = default_16MB.csv +upload_protocol = esptool + +build_flags = ${esp32s3_base.build_flags} + -I variants/esp32s3/tlora-pager + -D T_LORA_PAGER + -D BOARD_HAS_PSRAM + -D GPS_POWER_TOGGLE + -D HAS_SDCARD + -D SDCARD_USE_SPI1 + -D ENABLE_ROTARY_PULLUP + -D ENABLE_BUTTON_PULLUP + -D HALF_STEP + +lib_deps = ${esp32s3_base.lib_deps} + lovyan03/LovyanGFX@1.2.7 + earlephilhower/ESP8266Audio@1.9.9 + earlephilhower/ESP8266SAM@1.0.1 + adafruit/Adafruit DRV2605 Library@1.2.4 + lewisxhe/PCF8563_Library@1.0.1 + lewisxhe/SensorLib@0.3.1 + https://github.com/pschatzmann/arduino-audio-driver/archive/refs/tags/v0.1.3.zip + https://github.com/mverch67/BQ27220/archive/07d92be846abd8a0258a50c23198dac0858b22ed.zip + https://github.com/mverch67/RotaryEncoder + +[env:tlora-pager-tft] +extends = env:tlora-pager +build_flags = + ${env:tlora-pager.build_flags} + -D CONFIG_DISABLE_HAL_LOCKS=1 + -D INPUTDRIVER_ROTARY_TYPE=1 + -D INPUTDRIVER_ROTARY_UP=40 + -D INPUTDRIVER_ROTARY_DOWN=41 + -D INPUTDRIVER_ROTARY_BTN=7 + -D INPUTDRIVER_BUTTON_TYPE=0 + -D HAS_SCREEN=1 + -D HAS_TFT=1 + -D USE_I2S_BUZZER + -D RAM_SIZE=5120 + -D LV_LVGL_H_INCLUDE_SIMPLE + -D LV_CONF_INCLUDE_SIMPLE + -D LV_COMP_CONF_INCLUDE_SIMPLE + -D LV_USE_SYSMON=0 + -D LV_USE_PROFILER=0 + -D LV_USE_PERF_MONITOR=0 + -D LV_USE_MEM_MONITOR=0 + -D LV_USE_LOG=0 + -D USE_LOG_DEBUG + -D LOG_DEBUG_INC=\"DebugConfiguration.h\" + -D RADIOLIB_SPI_PARANOID=0 + -D LGFX_SCREEN_WIDTH=222 + -D LGFX_SCREEN_HEIGHT=480 + -D DISPLAY_SIZE=480x222 ; landscape mode + -D DISPLAY_SET_RESOLUTION + -D LGFX_DRIVER=LGFX_TLORA_PAGER + -D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_T_LORA_PAGER.h\" +; -D LVGL_DRIVER=LVGL_T_LORA_PAGER +; -D LV_USE_ST7796=1 + -D VIEW_480x222 + -D USE_PACKET_API + -D MAP_FULL_REDRAW + +lib_deps = + ${env:tlora-pager.lib_deps} + ${device-ui_base.lib_deps} diff --git a/variants/esp32s3/tlora-pager/variant.h b/variants/esp32s3/tlora-pager/variant.h new file mode 100644 index 000000000..ee48088c8 --- /dev/null +++ b/variants/esp32s3/tlora-pager/variant.h @@ -0,0 +1,125 @@ +// ST7796 TFT LCD +#define TFT_CS 38 +#define ST7796_CS TFT_CS +#define ST7796_RS 37 // DC +#define ST7796_SDA MOSI // MOSI +#define ST7796_SCK SCK +#define ST7796_RESET -1 +#define ST7796_MISO MISO +#define ST7796_BUSY -1 +#define ST7796_BL 42 +#define ST7796_SPI_HOST SPI2_HOST +#define TFT_BL 42 +#define SPI_FREQUENCY 75000000 +#define SPI_READ_FREQUENCY 16000000 +#define TFT_HEIGHT 480 +#define TFT_WIDTH 222 +#define TFT_OFFSET_X 49 +#define TFT_OFFSET_Y 0 +#define TFT_OFFSET_ROTATION 3 +#define SCREEN_ROTATE +#define SCREEN_TRANSITION_FRAMERATE 5 +#define BRIGHTNESS_DEFAULT 130 // Medium Low Brightness + +#define I2C_SDA SDA +#define I2C_SCL SCL + +#define USE_POWERSAVE +#define SLEEP_TIME 120 + +// GNNS +#define HAS_GPS 1 +#define GPS_BAUDRATE 38400 +#define GPS_RX_PIN 4 +#define GPS_TX_PIN 12 +#define PIN_GPS_PPS 13 + +// PCF8563 RTC Module +#if __has_include("pcf8563.h") +#include "pcf8563.h" +#endif +#define PCF8563_RTC 0x51 +#define HAS_RTC 1 + +// Rotary +#define ROTARY_A (40) +#define ROTARY_B (41) +#define ROTARY_PRESS (7) + +#define BUTTON_PIN 0 + +// SPI interface SD card slot +#define SPI_MOSI MOSI +#define SPI_SCK SCK +#define SPI_MISO MISO +#define SPI_CS 21 +#define SDCARD_CS SPI_CS +#define SD_SPI_FREQUENCY 75000000U + +// TCA8418 keyboard +#define I2C_NO_RESCAN +#define KB_BL_PIN 46 +#define KB_INT 6 +#define CANNED_MESSAGE_MODULE_ENABLE 1 + +// audio codec ES8311 +#define HAS_I2S +#define DAC_I2S_BCK 11 +#define DAC_I2S_WS 18 +#define DAC_I2S_DOUT 45 +#define DAC_I2S_DIN 17 +#define DAC_I2S_MCLK 10 + +// gyroscope BHI260AP +#define HAS_BHI260AP + +// battery charger BQ25896 +#define HAS_PPM 1 +#define XPOWERS_CHIP_BQ25896 + +// battery quality management BQ27220 +#define HAS_BQ27220 1 +#define BQ27220_I2C_SDA SDA +#define BQ27220_I2C_SCL SCL +#define BQ27220_DESIGN_CAPACITY 1500 + +// NFC ST25R3916 +#define NFC_INT 5 +#define NFC_CS 39 + +// External expansion chip XL9555 +#define USE_XL9555 +#define EXPANDS_DRV_EN (0) +#define EXPANDS_AMP_EN (1) +#define EXPANDS_KB_RST (2) +#define EXPANDS_LORA_EN (3) +#define EXPANDS_GPS_EN (4) +#define EXPANDS_NFC_EN (5) +#define EXPANDS_GPS_RST (7) +#define EXPANDS_KB_EN (8) +#define EXPANDS_GPIO_EN (9) +#define EXPANDS_SD_DET (10) +#define EXPANDS_SD_PULLEN (11) +#define EXPANDS_SD_EN (12) + +// LoRa +#define USE_SX1262 +#define USE_SX1268 + +#define LORA_SCK 35 +#define LORA_MISO 33 +#define LORA_MOSI 34 +#define LORA_CS 36 + +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 47 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 48 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TXCO is enabled + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 3.0 From 596cd7e0b6c4225ea21509601ede589bce205937 Mon Sep 17 00:00:00 2001 From: Manuel <71137295+mverch67@users.noreply.github.com> Date: Tue, 26 Aug 2025 20:39:43 +0200 Subject: [PATCH 05/24] enable device telemetry (#7757) --- variants/esp32s3/elecrow_panel/platformio.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/variants/esp32s3/elecrow_panel/platformio.ini b/variants/esp32s3/elecrow_panel/platformio.ini index 59bc26000..065f22538 100644 --- a/variants/esp32s3/elecrow_panel/platformio.ini +++ b/variants/esp32s3/elecrow_panel/platformio.ini @@ -19,8 +19,6 @@ build_flags = ${esp32s3_base.build_flags} -Os -D MESHTASTIC_EXCLUDE_SERIAL=1 -D MESHTASTIC_EXCLUDE_SOCKETAPI=1 -D MESHTASTIC_EXCLUDE_SCREEN=1 - -D MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1 - -D HAS_TELEMETRY=0 -D CONFIG_DISABLE_HAL_LOCKS=1 -D USE_PIN_BUZZER -D HAS_SCREEN=0 From 2c071a32836ff2837f36c2b6e62d1028b4c8d7c6 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 26 Aug 2025 13:41:33 -0500 Subject: [PATCH 06/24] Don't use pin 0 on RAK for input (#7755) * Don't use pin 0 on RAK for input * Use boolean instead of define --------- Co-authored-by: Ben Meadors --- src/input/RotaryEncoderInterruptBase.cpp | 23 ++++++++++++++++------- src/input/UpDownInterruptBase.cpp | 23 ++++++++++++++++------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/input/RotaryEncoderInterruptBase.cpp b/src/input/RotaryEncoderInterruptBase.cpp index 0557bc180..88b07a389 100644 --- a/src/input/RotaryEncoderInterruptBase.cpp +++ b/src/input/RotaryEncoderInterruptBase.cpp @@ -18,14 +18,23 @@ void RotaryEncoderInterruptBase::init( this->_eventCcw = eventCcw; this->_eventPressed = eventPressed; - pinMode(pinPress, INPUT_PULLUP); - pinMode(this->_pinA, INPUT_PULLUP); - pinMode(this->_pinB, INPUT_PULLUP); + bool isRAK = false; +#ifdef RAK_4631 + isRAK = true; +#endif - // attachInterrupt(pinPress, onIntPress, RISING); - attachInterrupt(pinPress, onIntPress, RISING); - attachInterrupt(this->_pinA, onIntA, CHANGE); - attachInterrupt(this->_pinB, onIntB, CHANGE); + if (!isRAK || pinPress != 0) { + pinMode(pinPress, INPUT_PULLUP); + attachInterrupt(pinPress, onIntPress, RISING); + } + if (!isRAK || this->_pinA != 0) { + pinMode(this->_pinA, INPUT_PULLUP); + attachInterrupt(this->_pinA, onIntA, CHANGE); + } + if (!isRAK || this->_pinA != 0) { + pinMode(this->_pinB, INPUT_PULLUP); + attachInterrupt(this->_pinB, onIntB, CHANGE); + } this->rotaryLevelA = digitalRead(this->_pinA); this->rotaryLevelB = digitalRead(this->_pinB); diff --git a/src/input/UpDownInterruptBase.cpp b/src/input/UpDownInterruptBase.cpp index c66eb13d0..26b281aaf 100644 --- a/src/input/UpDownInterruptBase.cpp +++ b/src/input/UpDownInterruptBase.cpp @@ -15,14 +15,23 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, this->_eventDown = eventDown; this->_eventUp = eventUp; this->_eventPressed = eventPressed; + bool isRAK = false; +#ifdef RAK_4631 + isRAK = true; +#endif - pinMode(pinPress, INPUT_PULLUP); - pinMode(this->_pinDown, INPUT_PULLUP); - pinMode(this->_pinUp, INPUT_PULLUP); - - attachInterrupt(pinPress, onIntPress, RISING); - attachInterrupt(this->_pinDown, onIntDown, RISING); - attachInterrupt(this->_pinUp, onIntUp, RISING); + if (!isRAK || pinPress != 0) { + pinMode(pinPress, INPUT_PULLUP); + attachInterrupt(pinPress, onIntPress, RISING); + } + if (!isRAK || this->_pinDown != 0) { + pinMode(this->_pinDown, INPUT_PULLUP); + attachInterrupt(this->_pinDown, onIntDown, RISING); + } + if (!isRAK || this->_pinUp != 0) { + pinMode(this->_pinUp, INPUT_PULLUP); + attachInterrupt(this->_pinUp, onIntUp, RISING); + } LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)", this->_pinUp, this->_pinDown, pinPress); From 3dd384dd53e56cd3ecf0b6721790ac968ad926f8 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 26 Aug 2025 19:45:26 -0500 Subject: [PATCH 07/24] Null check --- src/modules/AdminModule.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 9e8ce2e6b..407003f7e 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -505,7 +505,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta if (mp.decoded.want_response && !myReply) { myReply = allocErrorResponse(meshtastic_Routing_Error_NONE, &mp); } - if (mp.pki_encrypted) { + if (mp.pki_encrypted && myReply) { myReply->pki_encrypted = true; } return handled; From f8ba392a2413fdf749d7e2a649dfd8246f737baf Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Tue, 26 Aug 2025 20:29:11 -0500 Subject: [PATCH 08/24] Add BaseUI support for L1 EInk (#7751) * Add BaseUI support for L1 EInk * Fix Eink offset * Add joystick * Updates * Adjust Seeed Wio Tracker L1 E-Ink variant (#7326) * Rename variant Needs the -inkhud suffix to work correctly with the web flasher * Display driver for ZJY122250_0213BAAMFGN * Remove dead code from nicheGraphics.h Remnants of T-Echo's nicheGraphics.h file, which was used as a template. * Use ZJY122250_0213BAAMFGN driver Improves display health. We don't need as many full refreshes now. * Tidying * board_check = true --------- Co-authored-by: Ben Meadors * Consolidation * Add hack for existing InkHUD button functionality --------- Co-authored-by: todd-herbert --- src/configuration.h | 2 +- src/graphics/EInkDisplay2.cpp | 19 ++++-- src/graphics/EInkDisplay2.h | 2 +- .../Drivers/EInk/ZJY122250_0213BAAMFGN.cpp | 68 +++++++++++++++++++ .../Drivers/EInk/ZJY122250_0213BAAMFGN.h | 42 ++++++++++++ src/graphics/niche/InkHUD/DisplayHealth.cpp | 5 -- .../seeed_wio_tracker_L1_eink/nicheGraphics.h | 30 ++------ .../seeed_wio_tracker_L1_eink/platformio.ini | 38 +++++++++-- .../seeed_wio_tracker_L1_eink/variant.h | 25 ++++--- 9 files changed, 181 insertions(+), 50 deletions(-) create mode 100644 src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.cpp create mode 100644 src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h diff --git a/src/configuration.h b/src/configuration.h index 0e24990b5..8b4fd82c7 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -135,7 +135,7 @@ along with this program. If not, see . // ----------------------------------------------------------------------------- // OLED & Input // ----------------------------------------------------------------------------- -#if defined(SEEED_WIO_TRACKER_L1) +#if defined(SEEED_WIO_TRACKER_L1) && !defined(SEEED_WIO_TRACKER_L1_EINK) #define SSD1306_ADDRESS 0x3D #define USE_SH1106 #else diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index 1c9f290b6..c0c09cc27 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -67,20 +67,28 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit) // FIXME - only draw bits have changed (use backbuf similar to the other displays) const bool flipped = config.display.flip_screen; + // HACK for L1 EInk +#if defined(SEEED_WIO_TRACKER_L1_EINK) + // For SEEED_WIO_TRACKER_L1_EINK, setRotation(3) is correct but mirrored; flip both axes + for (uint32_t y = 0; y < displayHeight; y++) { + for (uint32_t x = 0; x < displayWidth; x++) { + auto b = buffer[x + (y / 8) * displayWidth]; + auto isset = b & (1 << (y & 7)); + adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE); + } + } +#else for (uint32_t y = 0; y < displayHeight; y++) { for (uint32_t x = 0; x < displayWidth; x++) { - // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficient auto b = buffer[x + (y / 8) * displayWidth]; auto isset = b & (1 << (y & 7)); - - // Handle flip here, rather than with setRotation(), - // Avoids issues when display width is not a multiple of 8 if (flipped) adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE); else adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE); } } +#endif // Trigger the refresh in GxEPD2 LOG_DEBUG("Update E-Paper"); @@ -235,7 +243,7 @@ bool EInkDisplay::connect() adafruitDisplay->setRotation(1); adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT); } -#elif defined(HELTEC_MESH_POCKET) +#elif defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) { spi1 = &SPI1; spi1->begin(); @@ -249,6 +257,7 @@ bool EInkDisplay::connect() // Init GxEPD2 adafruitDisplay->init(); adafruitDisplay->setRotation(3); + adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT); } #elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213) diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index b840ce9ba..b4cee81fe 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -84,7 +84,7 @@ class EInkDisplay : public OLEDDisplay SPIClass *hspi = NULL; #endif -#if defined(HELTEC_MESH_POCKET) +#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) SPIClass *spi1 = NULL; #endif diff --git a/src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.cpp b/src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.cpp new file mode 100644 index 000000000..e83588905 --- /dev/null +++ b/src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.cpp @@ -0,0 +1,68 @@ +#include "./ZJY122250_0213BAAMFGN.h" + +#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS + +using namespace NicheGraphics::Drivers; + +// Map the display controller IC's output to the connected panel +void ZJY122250_0213BAAMFGN::configScanning() +{ + // "Driver output control" + // Scan gates from 0 to 249 (vertical resolution 250px) + sendCommand(0x01); + sendData(0xF9); + sendData(0x00); + sendData(0x00); +} + +// Specify which information is used to control the sequence of voltages applied to move the pixels +// - For this display, configUpdateSequence() specifies that a suitable LUT will be loaded from +// the controller IC's OTP memory, when the update procedure begins. +void ZJY122250_0213BAAMFGN::configWaveform() +{ + switch (updateType) { + case FAST: + sendCommand(0x3C); // Border waveform: + sendData(0x80); // VCOM + break; + case FULL: + default: + sendCommand(0x3C); // Border waveform: + sendData(0x01); // Follow LUT 1 (blink same as white pixels) + break; + } + + sendCommand(0x18); // Temperature sensor: + sendData(0x80); // Use internal temperature sensor to select an appropriate refresh waveform +} + +void ZJY122250_0213BAAMFGN::configUpdateSequence() +{ + switch (updateType) { + case FAST: + sendCommand(0x22); // Set "update sequence" + sendData(0xFF); // Will load LUT from OTP memory, Display mode 2 "differential refresh" + break; + + case FULL: + default: + sendCommand(0x22); // Set "update sequence" + sendData(0xF7); // Will load LUT from OTP memory + break; + } +} + +// Once the refresh operation has been started, +// begin periodically polling the display to check for completion, using the normal Meshtastic threading code +// Only used when refresh is "async" +void ZJY122250_0213BAAMFGN::detachFromUpdate() +{ + switch (updateType) { + case FAST: + return beginPolling(50, 500); // At least 500ms for fast refresh + case FULL: + default: + return beginPolling(100, 2000); // At least 2 seconds for full refresh + } +} +#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS \ No newline at end of file diff --git a/src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h b/src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h new file mode 100644 index 000000000..82c4ec107 --- /dev/null +++ b/src/graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h @@ -0,0 +1,42 @@ +/* + +E-Ink display driver + - ZJY122250_0213BAAMFGN + - Manufacturer: Zhongjingyuan + - Size: 2.13 inch + - Resolution: 250px x 122px + - Flex connector marking (not a unique identifier): FPC-A002 + +*/ + +#pragma once + +#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS + +#include "configuration.h" + +#include "./SSD16XX.h" + +namespace NicheGraphics::Drivers +{ +class ZJY122250_0213BAAMFGN : public SSD16XX +{ + // Display properties + private: + static constexpr uint32_t width = 122; + static constexpr uint32_t height = 250; + static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST); + + public: + ZJY122250_0213BAAMFGN() : SSD16XX(width, height, supported) {} + + protected: + virtual void configScanning() override; + virtual void configWaveform() override; + virtual void configUpdateSequence() override; + void detachFromUpdate() override; +}; + +} // namespace NicheGraphics::Drivers + +#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS \ No newline at end of file diff --git a/src/graphics/niche/InkHUD/DisplayHealth.cpp b/src/graphics/niche/InkHUD/DisplayHealth.cpp index 7e1accafd..e8849b72e 100644 --- a/src/graphics/niche/InkHUD/DisplayHealth.cpp +++ b/src/graphics/niche/InkHUD/DisplayHealth.cpp @@ -7,12 +7,7 @@ using namespace NicheGraphics; // Timing for "maintenance" // Paying off full-refresh debt with unprovoked updates, if the display is not very active - -#ifdef SEEED_WIO_TRACKER_L1 -static constexpr uint32_t MAINTENANCE_MS_INITIAL = 5 * 1000UL; -#else static constexpr uint32_t MAINTENANCE_MS_INITIAL = 60 * 1000UL; -#endif static constexpr uint32_t MAINTENANCE_MS = 60 * 60 * 1000UL; InkHUD::DisplayHealth::DisplayHealth() : concurrency::OSThread("Mediator") diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h index a32753343..7fb890303 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/nicheGraphics.h @@ -18,16 +18,9 @@ // Shared NicheGraphics components // -------------------------------- -#include "graphics/niche/Drivers/Backlight/LatchingBacklight.h" -#include "graphics/niche/Drivers/EInk/GDEY0213B74.h" +#include "graphics/niche/Drivers/EInk/ZJY122250_0213BAAMFGN.h" #include "graphics/niche/Inputs/TwoButton.h" -// Special case - fix T-Echo's touch button -// ---------------------------------------- -// On a handful of T-Echos, LoRa TX triggers the capacitive touch -// To avoid this, we lockout the button during TX -#include "mesh/RadioLibInterface.h" - void setupNicheGraphics() { using namespace NicheGraphics; @@ -41,7 +34,7 @@ void setupNicheGraphics() // E-Ink Driver // ----------------------------- - Drivers::EInk *driver = new Drivers::GDEY0213B74; + Drivers::EInk *driver = new Drivers::ZJY122250_0213BAAMFGN; driver->begin(&SPI1, PIN_EINK_DC, PIN_EINK_CS, PIN_EINK_BUSY, PIN_EINK_RES); // InkHUD @@ -53,8 +46,7 @@ void setupNicheGraphics() inkhud->setDriver(driver); // Set how many FAST updates per FULL update - // Set how unhealthy additional FAST updates beyond this number are - inkhud->setDisplayResilience(7, 1.5); + inkhud->setDisplayResilience(15); // Select fonts InkHUD::Applet::fontLarge = FREESANS_12PT_WIN1252; @@ -62,16 +54,10 @@ void setupNicheGraphics() InkHUD::Applet::fontSmall = FREESANS_6PT_WIN1252; // Customize default settings - inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side - // 270 degrees clockwise + inkhud->persistence->settings.rotation = 1; // 90 degrees clockwise inkhud->persistence->settings.optionalFeatures.batteryIcon = true; // Device definitely has a battery - inkhud->persistence->settings.optionalMenuItems.backlight = true; // Until proves capacitive button works by touching it - inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users - - // Setup backlight controller - // Note: AUX button attached further down - Drivers::LatchingBacklight *backlight = Drivers::LatchingBacklight::getInstance(); - backlight->setPin(PIN_EINK_EN); + inkhud->persistence->settings.userTiles.count = 1; // One tile only by default, keep things simple for new users + inkhud->persistence->settings.userTiles.maxCount = 2; // Two applets side-by-side // Pick applets // Note: order of applets determines priority of "auto-show" feature @@ -83,11 +69,9 @@ void setupNicheGraphics() inkhud->addApplet("Recents List", new InkHUD::RecentsListApplet); // - inkhud->addApplet("Heard", new InkHUD::HeardApplet, true, false, 0); // Activated, no autoshow, default on tile 0 - inkhud->persistence->settings.rotation = 1; - // inkhud->persistence->printSettings(&inkhud->persistence->settings); // Start running InkHUD inkhud->begin(); - // inkhud->persistence->printSettings(&inkhud->persistence->settings); + // Buttons // -------------------------- diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini b/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini index 52ff39d49..7f9eb0e2c 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/platformio.ini @@ -1,17 +1,47 @@ [env:seeed_wio_tracker_L1_eink] board = seeed_wio_tracker_L1 -extends = nrf52840_base, inkhud +extends = nrf52840_base ;board_level = extra build_flags = ${nrf52840_base.build_flags} - ${inkhud.build_flags} -I variants/nrf52840/seeed_wio_tracker_L1_eink -D SEEED_WIO_TRACKER_L1_EINK -D SEEED_WIO_TRACKER_L1 -I src/platform/nrf52/softdevice -I src/platform/nrf52/softdevice/nrf52 + -DUSE_EINK + -DEINK_DISPLAY_MODEL=GxEPD2_213_B74 + -DEINK_WIDTH=250 + -DEINK_HEIGHT=122 + -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted + -DEINK_LIMIT_RATE_BACKGROUND_SEC=30 ; Minimum interval between BACKGROUND updates + -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates +; -D EINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. + -DEINK_HASQUIRK_GHOSTING ; Display model is identified as "prone to ghosting" + -DEINK_HASQUIRK_WEAKFASTREFRESH ; Pixels set with fast-refresh are easy to clear, disrupted by sunlight board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld -build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_wio_tracker_L1_eink> ${inkhud.build_src_filter} +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/seeed_wio_tracker_L1_eink> lib_deps = - ${inkhud.lib_deps} ${nrf52840_base.lib_deps} + https://github.com/meshtastic/GxEPD2#b202ebfec6a4821e098cf7a625ba0f6f2400292d debug_tool = jlink + +[env:seeed_wio_tracker_L1_eink-inkhud] +board = seeed_wio_tracker_L1 +extends = nrf52840_base, inkhud +build_flags = + ${nrf52840_base.build_flags} + ${inkhud.build_flags} + -I variants/nrf52840/seeed_wio_tracker_L1_eink + -D SEEED_WIO_TRACKER_L1 + -D BUTTON_PIN=D13 +board_build.ldscript = src/platform/nrf52/nrf52840_s140_v7.ld +build_src_filter = + ${nrf52_base.build_src_filter} + ${inkhud.build_src_filter} + +<../variants/nrf52840/seeed_wio_tracker_L1_eink> +lib_deps = + ${inkhud.lib_deps} ; Before base libs_deps, so we use ZinggJM/GFXRoot instead of AdafruitGFX (saves space) + ${nrf52840_base.lib_deps} +debug_tool = jlink \ No newline at end of file diff --git a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h index 98a7b2c39..f33d200b1 100644 --- a/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h +++ b/variants/nrf52840/seeed_wio_tracker_L1_eink/variant.h @@ -33,17 +33,10 @@ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Button Configuration // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - -#ifdef BUTTON_PIN -#undef BUTTON_PIN -#endif - -#define BUTTON_PIN D13 // This is the Program Button +#define CANCEL_BUTTON_PIN D13 // This is the Program Button // #define BUTTON_NEED_PULLUP 1 -#define BUTTON_ACTIVE_LOW true -#define BUTTON_ACTIVE_PULLUP false - -#define BUTTON_PIN_TOUCH 13 // Touch button +#define CANCEL_BUTTON_ACTIVE_LOW true +#define CANCEL_BUTTON_ACTIVE_PULLUP false // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Digital Pin Mapping (D0-D10) // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ @@ -116,7 +109,7 @@ static const uint8_t SCL = PIN_WIRE_SCL; #define PIN_EINK_SCLK 31 #define PIN_EINK_MOSI 33 #define PIN_EINK_EN 14 // unused -#define PIN_SPI1_MISO 15 // unused +#define PIN_SPI1_MISO -1 // 15 unused #define PIN_SPI1_MOSI PIN_EINK_MOSI #define PIN_SPI1_SCK PIN_EINK_SCLK @@ -175,7 +168,17 @@ static const uint8_t SCL = PIN_WIRE_SCL; // joystick // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +// trackball +#define HAS_TRACKBALL 1 +#define TB_UP 25 +#define TB_DOWN 26 +#define TB_LEFT 27 +#define TB_RIGHT 28 +#define TB_PRESS 29 +#define TB_DIRECTION FALLING + #define CANNED_MESSAGE_MODULE_ENABLE 1 +#define CANNED_MESSAGE_ADD_CONFIRMATION 1 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // Compatibility Definitions From 0903ed8232d6693c5f3aac948760b3f8ce294a14 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 27 Aug 2025 06:02:54 -0500 Subject: [PATCH 09/24] Mesh solar integrate (#7764) * Added HELTEC MeshSolar board. (#7499) * Added HELTEC MeshSolar board. * Set emergency shutdown pin as high impedance * Set emergency shutdown pin as high impedance Set emergency shutdown pin as high impedance * Update variants/nrf52840/heltec_mesh_solar/variant.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update variants/nrf52840/heltec_mesh_solar/variant.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update variants/nrf52840/heltec_mesh_solar/variant.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update I2C SCL pin definition in variant.h --------- Co-authored-by: Ben Meadors Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Updates --------- Co-authored-by: Quency-D <55523105+Quency-D@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- boards/heltec_mesh_solar.json | 54 ++++++ src/Power.cpp | 74 +++++++++ src/SerialConsole.cpp | 8 + src/mesh/StreamAPI.cpp | 119 ++++++++----- src/mesh/StreamAPI.h | 3 + src/modules/SerialModule.cpp | 23 ++- src/platform/nrf52/architecture.h | 2 + src/platform/nrf52/main-nrf52.cpp | 2 +- src/power.h | 2 + .../nrf52840/heltec_mesh_solar/platformio.ini | 19 +++ .../nrf52840/heltec_mesh_solar/variant.cpp | 36 ++++ variants/nrf52840/heltec_mesh_solar/variant.h | 157 ++++++++++++++++++ 12 files changed, 452 insertions(+), 47 deletions(-) create mode 100644 boards/heltec_mesh_solar.json create mode 100644 variants/nrf52840/heltec_mesh_solar/platformio.ini create mode 100644 variants/nrf52840/heltec_mesh_solar/variant.cpp create mode 100644 variants/nrf52840/heltec_mesh_solar/variant.h diff --git a/boards/heltec_mesh_solar.json b/boards/heltec_mesh_solar.json new file mode 100644 index 000000000..9e551c082 --- /dev/null +++ b/boards/heltec_mesh_solar.json @@ -0,0 +1,54 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + ["0x239A", "0x4405"], + ["0x239A", "0x0029"], + ["0x239A", "0x002A"], + ["0x239A", "0x0071"] + ], + "usb_product": "HT-n5262", + "mcu": "nrf52840", + "variant": "heltec_mesh_solar", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": ["jlink"], + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" + }, + "frameworks": ["arduino"], + "name": "Heltec nrf (Adafruit BSP)", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://heltec.org/project/meshsolar/", + "vendor": "Heltec" +} diff --git a/src/Power.cpp b/src/Power.cpp index 8a16132f1..bf74f6e53 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -681,6 +681,8 @@ bool Power::setup() found = true; } else if (lipoChargerInit()) { found = true; + } else if (meshSolarInit()) { + found = true; } else if (analogInit()) { found = true; } @@ -1450,3 +1452,75 @@ bool Power::lipoChargerInit() return false; } #endif + + + +#ifdef HELTEC_MESH_SOLAR +#include "meshSolarApp.h" + +/** + * meshSolar class for an SMBUS battery sensor. + */ +class meshSolarBatteryLevel : public HasBatteryLevel +{ + + public: + /** + * Init the I2C meshSolar battery level sensor + */ + bool runOnce() + { + meshSolarStart(); + return true; + } + + /** + * Battery state of charge, from 0 to 100 or -1 for unknown + */ + virtual int getBatteryPercent() override { return meshSolarGetBatteryPercent(); } + + /** + * The raw voltage of the battery in millivolts, or NAN if unknown + */ + virtual uint16_t getBattVoltage() override { return meshSolarGetBattVoltage(); } + + /** + * return true if there is a battery installed in this unit + */ + virtual bool isBatteryConnect() override { return meshSolarIsBatteryConnect(); } + + /** + * return true if there is an external power source detected + */ + virtual bool isVbusIn() override { return meshSolarIsVbusIn();} + + /** + * return true if the battery is currently charging + */ + virtual bool isCharging() override { return meshSolarIsCharging(); } +}; + +meshSolarBatteryLevel meshSolarLevel; + +/** + * Init the meshSolar battery level sensor + */ +bool Power::meshSolarInit() +{ + bool result = meshSolarLevel.runOnce(); + LOG_DEBUG("Power::meshSolarInit mesh solar sensor is %s", result ? "ready" : "not ready yet"); + if (!result) + return false; + batteryLevel = &meshSolarLevel; + return true; +} + +#else +/** + * The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel + */ +bool Power::meshSolarInit() +{ + return false; +} +#endif \ No newline at end of file diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 68c41980d..093a24678 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -64,6 +64,14 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con int32_t SerialConsole::runOnce() { +#ifdef HELTEC_MESH_SOLAR + //After enabling the mesh solar serial port module configuration, command processing is handled by the serial port module. + if(moduleConfig.serial.enabled && moduleConfig.serial.override_console_serial_port + && moduleConfig.serial.mode==meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG) + { + return 250; + } +#endif return runOncePart(); } diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 4a42e5197..3d652b6d6 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -15,9 +15,65 @@ int32_t StreamAPI::runOncePart() checkConnectionTimeout(); return result; } +int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen) +{ + auto result = readStream(buf, bufLen); + writeStream(); + checkConnectionTimeout(); + return result; +} + +int32_t StreamAPI::handleRecStream(char *buf, uint16_t bufLen) +{ + uint16_t index = 0; + while (bufLen > index) { // Currently we never want to block + int cInt = buf[index++]; + if (cInt < 0) + break; // We ran out of characters (even though available said otherwise) - this can happen on rf52 adafruit + // arduino + + uint8_t c = (uint8_t)cInt; + + // Use the read pointer for a little state machine, first look for framing, then length bytes, then payload + size_t ptr = rxPtr; + + rxPtr++; // assume we will probably advance the rxPtr + rxBuf[ptr] = c; // store all bytes (including framing) + + // console->printf("rxPtr %d ptr=%d c=0x%x\n", rxPtr, ptr, c); + + if (ptr == 0) { // looking for START1 + if (c != START1) + rxPtr = 0; // failed to find framing + } else if (ptr == 1) { // looking for START2 + if (c != START2) + rxPtr = 0; // failed to find framing + } else if (ptr >= HEADER_LEN - 1) { // we have at least read our 4 byte framing + uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing + + // console->printf("len %d\n", len); + + if (ptr == HEADER_LEN - 1) { + // we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid + // protobuf also) + if (len > MAX_TO_FROM_RADIO_SIZE) + rxPtr = 0; // length is bogus, restart search for framing + } + + if (rxPtr != 0) // Is packet still considered 'good'? + if (ptr + 1 >= len + HEADER_LEN) { // have we received all of the payload? + rxPtr = 0; // start over again on the next packet + + // If we didn't just fail the packet and we now have the right # of bytes, parse it + handleToRadio(rxBuf + HEADER_LEN, len); + } + } + } + return 0; +} /** - * Read any rx chars from the link and call handleToRadio + * Read any rx chars from the link and call handleRecStream */ int32_t StreamAPI::readStream() { @@ -26,50 +82,29 @@ int32_t StreamAPI::readStream() bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000); return recentRx ? 5 : 250; } else { + char buf[1]; while (stream->available()) { // Currently we never want to block - int cInt = stream->read(); - if (cInt < 0) - break; // We ran out of characters (even though available said otherwise) - this can happen on rf52 adafruit - // arduino - - uint8_t c = (uint8_t)cInt; - - // Use the read pointer for a little state machine, first look for framing, then length bytes, then payload - size_t ptr = rxPtr; - - rxPtr++; // assume we will probably advance the rxPtr - rxBuf[ptr] = c; // store all bytes (including framing) - - // console->printf("rxPtr %d ptr=%d c=0x%x\n", rxPtr, ptr, c); - - if (ptr == 0) { // looking for START1 - if (c != START1) - rxPtr = 0; // failed to find framing - } else if (ptr == 1) { // looking for START2 - if (c != START2) - rxPtr = 0; // failed to find framing - } else if (ptr >= HEADER_LEN - 1) { // we have at least read our 4 byte framing - uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing - - // console->printf("len %d\n", len); - - if (ptr == HEADER_LEN - 1) { - // we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid - // protobuf also) - if (len > MAX_TO_FROM_RADIO_SIZE) - rxPtr = 0; // length is bogus, restart search for framing - } - - if (rxPtr != 0) // Is packet still considered 'good'? - if (ptr + 1 >= len + HEADER_LEN) { // have we received all of the payload? - rxPtr = 0; // start over again on the next packet - - // If we didn't just fail the packet and we now have the right # of bytes, parse it - handleToRadio(rxBuf + HEADER_LEN, len); - } - } + buf[0] = stream->read(); + handleRecStream(buf, 1); } + // we had bytes available this time, so assume we might have them next time also + lastRxMsec = millis(); + return 0; + } +} +/** + * Read any rx chars from the link and call handleRecStream + */ +int32_t StreamAPI::readStream(char *buf, uint16_t bufLen) +{ + uint16_t index = 0; + if (bufLen < 1) { + // Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time + bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000); + return recentRx ? 5 : 250; + } else { + handleRecStream(buf, bufLen); // we had bytes available this time, so assume we might have them next time also lastRxMsec = millis(); return 0; diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h index 6e0364bc1..547dd0175 100644 --- a/src/mesh/StreamAPI.h +++ b/src/mesh/StreamAPI.h @@ -50,12 +50,15 @@ class StreamAPI : public PhoneAPI * phone. */ virtual int32_t runOncePart(); + virtual int32_t runOncePart(char *buf,uint16_t bufLen); private: /** * Read any rx chars from the link and call handleToRadio */ int32_t readStream(); + int32_t readStream(char *buf,uint16_t bufLen); + int32_t handleRecStream(char *buf,uint16_t bufLen); /** * call getFromRadio() and deliver encapsulated packets to the Stream diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 866497ecc..a2dbb07d3 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -45,6 +45,9 @@ */ +#ifdef HELTEC_MESH_SOLAR +#include "meshSolarApp.h" +#endif #if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ !defined(CONFIG_IDF_TARGET_ESP32C3) @@ -60,8 +63,9 @@ SerialModule *serialModule; SerialModuleRadio *serialModuleRadio; -#if defined(TTGO_T_ECHO) || defined(T_ECHO_LITE) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \ - defined(ELECROW_ThinkNode_M5) + +#if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \ + defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {} static Print *serialPrint = &Serial; #elif defined(CONFIG_IDF_TARGET_ESP32C6) @@ -78,7 +82,8 @@ size_t serialPayloadSize; bool SerialModule::isValidConfig(const meshtastic_ModuleConfig_SerialConfig &config) { if (config.override_console_serial_port && !IS_ONE_OF(config.mode, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA, - meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO)) { + meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO, + meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) { const char *warning = "Invalid Serial config: override console serial port is only supported in NMEA and CalTopo output-only modes."; LOG_ERROR(warning); @@ -241,7 +246,17 @@ int32_t SerialModule::runOnce() else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) { processWXSerial(); - } else { + } +#if defined(HELTEC_MESH_SOLAR) + else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) { + serialPayloadSize = Serial.readBytes(serialBytes, sizeof(serialBytes)-1); + //If the parsing fails, the following parsing will be performed. + if((serialPayloadSize > 0) && (meshSolarCmdHandle(serialBytes)!=0)) { + return runOncePart(serialBytes,serialPayloadSize); + } + } +#endif + else { #if defined(CONFIG_IDF_TARGET_ESP32C6) while (Serial1.available()) { serialPayloadSize = Serial1.readBytes(serialBytes, meshtastic_Constants_DATA_PAYLOAD_LEN); diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h index 064bd8ef0..c9938062e 100644 --- a/src/platform/nrf52/architecture.h +++ b/src/platform/nrf52/architecture.h @@ -98,6 +98,8 @@ #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1_EINK #elif defined(SEEED_WIO_TRACKER_L1) #define HW_VENDOR meshtastic_HardwareModel_SEEED_WIO_TRACKER_L1 +#elif defined(HELTEC_MESH_SOLAR) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_MESH_SOLAR #else #define HW_VENDOR meshtastic_HardwareModel_NRF52_UNKNOWN #endif diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp index 590d2f0ae..8ce74d5f7 100644 --- a/src/platform/nrf52/main-nrf52.cpp +++ b/src/platform/nrf52/main-nrf52.cpp @@ -323,7 +323,7 @@ void cpuDeepSleep(uint32_t msecToWake) #endif #endif -#ifdef HELTEC_MESH_NODE_T114 +#if defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_MESH_SOLAR) nrf_gpio_cfg_default(PIN_GPS_PPS); detachInterrupt(PIN_GPS_PPS); detachInterrupt(PIN_BUTTON1); diff --git a/src/power.h b/src/power.h index 1c078c06d..e96f5b022 100644 --- a/src/power.h +++ b/src/power.h @@ -128,6 +128,8 @@ class Power : private concurrency::OSThread bool lipoInit(); /// Setup a Lipo charger bool lipoChargerInit(); + /// Setup a meshSolar battery sensor + bool meshSolarInit(); private: void shutdown(); diff --git a/variants/nrf52840/heltec_mesh_solar/platformio.ini b/variants/nrf52840/heltec_mesh_solar/platformio.ini new file mode 100644 index 000000000..65d26dc40 --- /dev/null +++ b/variants/nrf52840/heltec_mesh_solar/platformio.ini @@ -0,0 +1,19 @@ +; First prototype nrf52840/sx1262 device +[env:heltec-mesh-solar] +extends = nrf52840_base +board = heltec_mesh_solar +board_level = pr +debug_tool = jlink + +# add -DCFG_SYSVIEW if you want to use the Segger systemview tool for OS profiling. +build_flags = ${nrf52840_base.build_flags} + -Ivariants/nrf52840/heltec_mesh_solar + -DGPS_POWER_TOGGLE + -DHELTEC_MESH_SOLAR + +build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/heltec_mesh_solar> +lib_deps = + ${nrf52840_base.lib_deps} + https://github.com/NMIoT/meshsolar/archive/dfc5330dad443982e6cdd37a61d33fc7252f468b.zip + lewisxhe/PCF8563_Library@^1.0.1 + ArduinoJson@6.21.4 diff --git a/variants/nrf52840/heltec_mesh_solar/variant.cpp b/variants/nrf52840/heltec_mesh_solar/variant.cpp new file mode 100644 index 000000000..8236d7cf4 --- /dev/null +++ b/variants/nrf52840/heltec_mesh_solar/variant.cpp @@ -0,0 +1,36 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 - pins 0 and 1 are hardwired for xtal and should never be enabled + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + pinMode(BQ4050_EMERGENCY_SHUTDOWN_PIN, INPUT); +} diff --git a/variants/nrf52840/heltec_mesh_solar/variant.h b/variants/nrf52840/heltec_mesh_solar/variant.h new file mode 100644 index 000000000..33c2b2556 --- /dev/null +++ b/variants/nrf52840/heltec_mesh_solar/variant.h @@ -0,0 +1,157 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_HELTEC_NRF_ +#define _VARIANT_HELTEC_NRF_ +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + + +#define PIN_LED1 (0 + 12) // green (confirmed on 1.0 board) +#define LED_BLUE PIN_LED1 // fake for bluefruit library +#define LED_GREEN PIN_LED1 +#define LED_BUILTIN LED_GREEN +#define LED_STATE_ON 0 // State when LED is lit + +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 1 // How many neopixels are connected +#define NEOPIXEL_DATA (32+15) // gpio pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use + +/* + * Buttons + */ +#define PIN_BUTTON1 (32 + 10) +// #define PIN_BUTTON2 (0 + 18) // 0.18 is labeled on the board as RESET but we configure it in the bootloader as a regular +// GPIO + +/* +No longer populated on PCB +*/ +#define PIN_SERIAL2_RX (0 + 9) +#define PIN_SERIAL2_TX (0 + 10) +// #define PIN_SERIAL2_EN (0 + 17) + +/* + * I2C + */ + +#define WIRE_INTERFACES_COUNT 2 + +// I2C bus 0 +// Routed to footprint for PCF8563TS RTC +// Not populated on T114 V1, maybe in future? +#define PIN_WIRE_SDA (0 + 6) // P0.26 +#define PIN_WIRE_SCL (0 + 26) // P0.26 + +// I2C bus 1 +// Available on header pins, for general use +#define PIN_WIRE1_SDA (0 + 30) // P0.30 +#define PIN_WIRE1_SCL (0 + 5) // P0.13 + +/* + * Lora radio + */ + +#define USE_SX1262 +// #define USE_SX1268 +#define SX126X_CS (0 + 24) // FIXME - we really should define LORA_CS instead +#define LORA_CS (0 + 24) +#define SX126X_DIO1 (0 + 20) +// Note DIO2 is attached internally to the module to an analog switch for TX/RX switching +// #define SX1262_DIO3 (0 + 21) +// This is used as an *output* from the sx1262 and connected internally to power the tcxo, do not drive from the +// main +// CPU? +#define SX126X_BUSY (0 + 17) +#define SX126X_RESET (0 + 25) +// Not really an E22 but TTGO seems to be trying to clone that +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +/* + * GPS pins + */ + +#define GPS_L76K + +// #define PIN_GPS_RESET (32 + 6) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K +// #define GPS_RESET_MODE LOW +// #define PIN_GPS_EN (21) +#define PERIPHERAL_WARMUP_MS 1000 // Make sure I2C QuickLink has stable power before continuing +#define VEXT_ON_VALUE HIGH +// #define GPS_EN_ACTIVE HIGH +#define PIN_GPS_STANDBY (32 + 2) // An output to wake GPS, low means allow sleep, high means force wake +#define PIN_GPS_PPS (32 + 4) +// Seems to be missing on this new board +// #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS +#define GPS_TX_PIN (32 + 5) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (32 + 7) // This is for bits going TOWARDS the GPS + +#define GPS_THREAD_INTERVAL 50 + +#define PIN_SERIAL1_RX GPS_TX_PIN +#define PIN_SERIAL1_TX GPS_RX_PIN + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 1 + +// For LORA, spi 0 +#define PIN_SPI_MISO (0 + 23) +#define PIN_SPI_MOSI (0 + 22) +#define PIN_SPI_SCK (0 + 19) + +// #define PIN_PWR_EN (0 + 6) + +// To debug via the segger JLINK console rather than the CDC-ACM serial device +// #define USE_SEGGER + +#define BQ4050_SDA_PIN (32+1) // I2C data line pin +#define BQ4050_SCL_PIN (32+0) // I2C clock line pin +#define BQ4050_EMERGENCY_SHUTDOWN_PIN (32+3) // Emergency shutdown pin + +#define HAS_RTC 0 +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif From 3120bb8fd77bd3dc340ca14b923a0847b95f6ad4 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 27 Aug 2025 06:50:53 -0500 Subject: [PATCH 10/24] Fix check --- src/input/RotaryEncoderImpl.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/input/RotaryEncoderImpl.cpp b/src/input/RotaryEncoderImpl.cpp index b71e800e0..d3fcbbf9d 100644 --- a/src/input/RotaryEncoderImpl.cpp +++ b/src/input/RotaryEncoderImpl.cpp @@ -8,7 +8,10 @@ RotaryEncoderImpl *rotaryEncoderImpl; -RotaryEncoderImpl::RotaryEncoderImpl() : concurrency::OSThread(ORIGIN_NAME), originName(ORIGIN_NAME) {} +RotaryEncoderImpl::RotaryEncoderImpl() : concurrency::OSThread(ORIGIN_NAME), originName(ORIGIN_NAME) +{ + rotary = nullptr; +} bool RotaryEncoderImpl::init() { From 06bccef46204d073f72c7b98b9f5e9e3cc424b64 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 27 Aug 2025 07:17:46 -0500 Subject: [PATCH 11/24] Reinstitute previous streamapi readStream --- src/mesh/StreamAPI.cpp | 80 +++++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 3d652b6d6..a45e11ac3 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -73,7 +73,7 @@ int32_t StreamAPI::handleRecStream(char *buf, uint16_t bufLen) } /** - * Read any rx chars from the link and call handleRecStream + * Read any rx chars from the link and call handleToRadio */ int32_t StreamAPI::readStream() { @@ -82,50 +82,56 @@ int32_t StreamAPI::readStream() bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000); return recentRx ? 5 : 250; } else { - char buf[1]; while (stream->available()) { // Currently we never want to block - buf[0] = stream->read(); - handleRecStream(buf, 1); + int cInt = stream->read(); + if (cInt < 0) + break; // We ran out of characters (even though available said otherwise) - this can happen on rf52 adafruit + // arduino + + uint8_t c = (uint8_t)cInt; + + // Use the read pointer for a little state machine, first look for framing, then length bytes, then payload + size_t ptr = rxPtr; + + rxPtr++; // assume we will probably advance the rxPtr + rxBuf[ptr] = c; // store all bytes (including framing) + + // console->printf("rxPtr %d ptr=%d c=0x%x\n", rxPtr, ptr, c); + + if (ptr == 0) { // looking for START1 + if (c != START1) + rxPtr = 0; // failed to find framing + } else if (ptr == 1) { // looking for START2 + if (c != START2) + rxPtr = 0; // failed to find framing + } else if (ptr >= HEADER_LEN - 1) { // we have at least read our 4 byte framing + uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing + + // console->printf("len %d\n", len); + + if (ptr == HEADER_LEN - 1) { + // we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid + // protobuf also) + if (len > MAX_TO_FROM_RADIO_SIZE) + rxPtr = 0; // length is bogus, restart search for framing + } + + if (rxPtr != 0) // Is packet still considered 'good'? + if (ptr + 1 >= len + HEADER_LEN) { // have we received all of the payload? + rxPtr = 0; // start over again on the next packet + + // If we didn't just fail the packet and we now have the right # of bytes, parse it + handleToRadio(rxBuf + HEADER_LEN, len); + } + } } + // we had bytes available this time, so assume we might have them next time also lastRxMsec = millis(); return 0; } } -/** - * Read any rx chars from the link and call handleRecStream - */ -int32_t StreamAPI::readStream(char *buf, uint16_t bufLen) -{ - uint16_t index = 0; - if (bufLen < 1) { - // Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time - bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000); - return recentRx ? 5 : 250; - } else { - handleRecStream(buf, bufLen); - // we had bytes available this time, so assume we might have them next time also - lastRxMsec = millis(); - return 0; - } -} - -/** - * call getFromRadio() and deliver encapsulated packets to the Stream - */ -void StreamAPI::writeStream() -{ - if (canWrite) { - uint32_t len; - do { - // Send every packet we can - len = getFromRadio(txBuf + HEADER_LEN); - emitTxBuffer(len); - } while (len); - } -} - /** * Send the current txBuffer over our stream */ From 237b8908f75bab38c18cc8ec735b86ea59983b76 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 27 Aug 2025 09:54:39 -0500 Subject: [PATCH 12/24] Chainsaw took too much off the top --- src/mesh/StreamAPI.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index a45e11ac3..20026767e 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -15,6 +15,7 @@ int32_t StreamAPI::runOncePart() checkConnectionTimeout(); return result; } + int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen) { auto result = readStream(buf, bufLen); @@ -23,6 +24,38 @@ int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen) return result; } +/** + * Read any rx chars from the link and call handleRecStream + */ +int32_t StreamAPI::readStream(char *buf, uint16_t bufLen) +{ + if (bufLen < 1) { + // Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time + bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000); + return recentRx ? 5 : 250; + } else { + handleRecStream(buf, bufLen); + // we had bytes available this time, so assume we might have them next time also + lastRxMsec = millis(); + return 0; + } +} + +/** + * call getFromRadio() and deliver encapsulated packets to the Stream + */ +void StreamAPI::writeStream() +{ + if (canWrite) { + uint32_t len; + do { + // Send every packet we can + len = getFromRadio(txBuf + HEADER_LEN); + emitTxBuffer(len); + } while (len); + } +} + int32_t StreamAPI::handleRecStream(char *buf, uint16_t bufLen) { uint16_t index = 0; From 26c38ffc8e3f181e004445a1e86d4da3ecbaf1da Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 27 Aug 2025 11:55:27 -0500 Subject: [PATCH 13/24] Remove debug logging --- variants/esp32s3/unphone/platformio.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/variants/esp32s3/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini index ecb1cbd67..f6c3e2855 100644 --- a/variants/esp32s3/unphone/platformio.ini +++ b/variants/esp32s3/unphone/platformio.ini @@ -52,8 +52,6 @@ build_flags = -D LV_USE_PERF_MONITOR=0 -D LV_USE_MEM_MONITOR=0 -D LV_USE_LOG=0 - -D USE_LOG_DEBUG - -D LOG_DEBUG_INC=\"DebugConfiguration.h\" -D LGFX_SCREEN_WIDTH=320 -D LGFX_SCREEN_HEIGHT=480 -D DISPLAY_SIZE=320x480 ; portrait mode From d21d6d208542b8ca645333e67e3b1e743811eb75 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 13:53:34 -0500 Subject: [PATCH 14/24] Update meshtastic/device-ui digest to a3e0e1b (#7766) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 543205996..ef0fef791 100644 --- a/platformio.ini +++ b/platformio.ini @@ -118,7 +118,7 @@ lib_deps = [device-ui_base] lib_deps = # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master - https://github.com/meshtastic/device-ui/archive/0f32b64dca418c6465763ec576509a6a2bfbc50a.zip + https://github.com/meshtastic/device-ui/archive/a3e0e1be372d069f47b4c19d718f5267251744d7.zip ; Common libs for environmental measurements in telemetry module [environmental_base] From a4d96bebfbe8c699fdef8662a0e28fa8a0c6014b Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 27 Aug 2025 14:35:29 -0500 Subject: [PATCH 15/24] Drop for now --- variants/esp32s3/unphone/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/esp32s3/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini index f6c3e2855..476858ff5 100644 --- a/variants/esp32s3/unphone/platformio.ini +++ b/variants/esp32s3/unphone/platformio.ini @@ -32,6 +32,7 @@ lib_deps = ${esp32s3_base.lib_deps} [env:unphone-tft] +board_level = extra extends = env:unphone build_flags = ${env:unphone.build_flags} From 25a19b49ad9a20fd8296b120183154976b46c0cc Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 27 Aug 2025 15:18:26 -0500 Subject: [PATCH 16/24] This one is not working yet --- variants/esp32s3/tlora-pager/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/esp32s3/tlora-pager/platformio.ini b/variants/esp32s3/tlora-pager/platformio.ini index 3d77d879c..b16e516a7 100644 --- a/variants/esp32s3/tlora-pager/platformio.ini +++ b/variants/esp32s3/tlora-pager/platformio.ini @@ -29,6 +29,7 @@ lib_deps = ${esp32s3_base.lib_deps} https://github.com/mverch67/RotaryEncoder [env:tlora-pager-tft] +board_level = extra extends = env:tlora-pager build_flags = ${env:tlora-pager.build_flags} From 834c3c5cc2be305cf3ee465c600e4282e0928f46 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Wed, 27 Aug 2025 16:24:57 -0500 Subject: [PATCH 17/24] Add this back in --- src/modules/SerialModule.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index a2dbb07d3..880768839 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -45,7 +45,7 @@ */ -#ifdef HELTEC_MESH_SOLAR +#ifdef HELTEC_MESH_SOLAR #include "meshSolarApp.h" #endif @@ -63,9 +63,8 @@ SerialModule *serialModule; SerialModuleRadio *serialModuleRadio; - #if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \ - defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) + defined(ELECROW_ThinkNode_M5) || defined(HELTEC_MESH_SOLAR) || defined(T_ECHO_LITE) SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {} static Print *serialPrint = &Serial; #elif defined(CONFIG_IDF_TARGET_ESP32C6) @@ -83,7 +82,7 @@ bool SerialModule::isValidConfig(const meshtastic_ModuleConfig_SerialConfig &con { if (config.override_console_serial_port && !IS_ONE_OF(config.mode, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO, - meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) { + meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) { const char *warning = "Invalid Serial config: override console serial port is only supported in NMEA and CalTopo output-only modes."; LOG_ERROR(warning); @@ -249,11 +248,11 @@ int32_t SerialModule::runOnce() } #if defined(HELTEC_MESH_SOLAR) else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MS_CONFIG)) { - serialPayloadSize = Serial.readBytes(serialBytes, sizeof(serialBytes)-1); - //If the parsing fails, the following parsing will be performed. - if((serialPayloadSize > 0) && (meshSolarCmdHandle(serialBytes)!=0)) { - return runOncePart(serialBytes,serialPayloadSize); - } + serialPayloadSize = Serial.readBytes(serialBytes, sizeof(serialBytes) - 1); + // If the parsing fails, the following parsing will be performed. + if ((serialPayloadSize > 0) && (meshSolarCmdHandle(serialBytes) != 0)) { + return runOncePart(serialBytes, serialPayloadSize); + } } #endif else { From 6c7cff7de2a13f7665e98f85050d8df4d968cf3f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 28 Aug 2025 06:02:24 -0500 Subject: [PATCH 18/24] Merge pull request #7777 from meshtastic/create-pull-request/bump-version Bump release version --- bin/org.meshtastic.meshtasticd.metainfo.xml | 3 +++ debian/changelog | 5 +++-- version.properties | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bin/org.meshtastic.meshtasticd.metainfo.xml b/bin/org.meshtastic.meshtasticd.metainfo.xml index f3b3bb14d..1d97e2a66 100644 --- a/bin/org.meshtastic.meshtasticd.metainfo.xml +++ b/bin/org.meshtastic.meshtasticd.metainfo.xml @@ -87,6 +87,9 @@ + + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.7 + https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.6 diff --git a/debian/changelog b/debian/changelog index b36a22168..ff59db89e 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -meshtasticd (2.7.6.0) UNRELEASED; urgency=medium +meshtasticd (2.7.7.0) UNRELEASED; urgency=medium [ Austin Lane ] * Initial packaging @@ -39,5 +39,6 @@ meshtasticd (2.7.6.0) UNRELEASED; urgency=medium [ ] * GitHub Actions Automatic version bump + * GitHub Actions Automatic version bump - -- Tue, 12 Aug 2025 23:48:48 +0000 + -- Ubuntu Thu, 28 Aug 2025 10:33:25 +0000 diff --git a/version.properties b/version.properties index f9e2cb279..2e5193d49 100644 --- a/version.properties +++ b/version.properties @@ -1,4 +1,4 @@ [VERSION] major = 2 minor = 7 -build = 6 +build = 7 From b0e8321514d91da0f5a90c324e3d9dce0b36af5b Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Fri, 29 Aug 2025 09:45:46 +1000 Subject: [PATCH 19/24] Only send Neighbours if we have some to send. (#7493) * Only send Neighbours if we have some to send. The original intent of NeighborInfo was that when a NeighbourInfo was sent all of the nodes that saw it would reply with NeighbourInfo. So, NeighbourInfo was sent even if there were no hop-zero nodes in the NodeDB. Since 2023, when this was implemented, our understanding of running city-wide meshes has improved substantially. We have taken steps to reduce the impact of NeighborInfo over LoRa. This change aligns with those ideas: we will now only send NeighborInfo if we have some neighbors to contribute. The impact of this change is that a node must first see another directly connected node in another packet type before NeighborInfo is sent. This means that a node with no neighbors is no longer able to trigger other nodes to broadcast NeighborInfo. It will, however, receive the regular periodic broadcast of NeighborInfo, and will be able to send NeighborInfo if it has at least 1 neighbor. * Include all the things * AvOid memleak --- src/modules/NeighborInfoModule.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp index eebf428a4..97dc17001 100644 --- a/src/modules/NeighborInfoModule.cpp +++ b/src/modules/NeighborInfoModule.cpp @@ -105,14 +105,15 @@ void NeighborInfoModule::sendNeighborInfo(NodeNum dest, bool wantReplies) { meshtastic_NeighborInfo neighborInfo = meshtastic_NeighborInfo_init_zero; collectNeighborInfo(&neighborInfo); - meshtastic_MeshPacket *p = allocDataProtobuf(neighborInfo); - // send regardless of whether or not we have neighbors in our DB, - // because we want to get neighbors for the next cycle - p->to = dest; - p->decoded.want_response = wantReplies; - p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; - printNeighborInfo("SENDING", &neighborInfo); - service->sendToMesh(p, RX_SRC_LOCAL, true); + // only send neighbours if we have some to send + if (neighborInfo.neighbors_count > 0) { + meshtastic_MeshPacket *p = allocDataProtobuf(neighborInfo); + p->to = dest; + p->decoded.want_response = wantReplies; + p->priority = meshtastic_MeshPacket_Priority_BACKGROUND; + printNeighborInfo("SENDING", &neighborInfo); + service->sendToMesh(p, RX_SRC_LOCAL, true); + } } /* @@ -214,4 +215,4 @@ meshtastic_Neighbor *NeighborInfoModule::getOrCreateNeighbor(NodeNum originalSen neighbors.push_back(new_nbr); } return &neighbors.back(); -} \ No newline at end of file +} From d3e3a91096f2189ea710d94c7dda145f1046c6ad Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 30 Aug 2025 00:08:33 +1000 Subject: [PATCH 20/24] We don't gotTime if time is 2019. (#7772) There are certain GPS chips that have a hard-coded time in firmware that they will return before lock. We set our own hard-coded time, BUILD_EPOCH, that should be newer and use the comparison to not set a bad time. In https://github.com/meshtastic/firmware/pull/7261 we introduced the RTCSetResult and improved it in https://github.com/meshtastic/firmware/pull/7375 . However, the original try-fix left logic in GPS.cpp that could still result in broadcasting the bad time. Further, as part of our fix we cleared the GPS buffer if we didn't get a good time. The mesh was hurting at the time, so this was a reasonable approach. However, given time tends to come in when we're trying to get early lock, this had the potential side effect of throwing away valuable information to get position lock. This change reverses the clearBuffer and changes the logic so if time is not set it will not be broadcast. Fixes https://github.com/meshtastic/firmware/issues/7771 Fixes https://github.com/meshtastic/firmware/issues/7750 --- src/gps/GPS.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index ae74f0fe2..881021975 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -1504,7 +1504,7 @@ static int32_t toDegInt(RawDegrees d) * Perform any processing that should be done only while the GPS is awake and looking for a fix. * Override this method to check for new locations * - * @return true if we've acquired a new location + * @return true if we've set a new time */ bool GPS::lookForTime() { @@ -1544,11 +1544,12 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s if (t.tm_mon > -1) { LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, ti.age()); - if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultInvalidTime) { - // Clear the GPS buffer if we got an invalid time - clearBuffer(); + if (perhapsSetRTC(RTCQualityGPS, t) == RTCSetResultSuccess) { + LOG_DEBUG("Time set."); + return true; + } else { + return false; } - return true; } else return false; } else From 4e03df5ea7a30ac6458545df59ec24557a46598d Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Fri, 29 Aug 2025 12:09:22 -0500 Subject: [PATCH 21/24] Fix freetext hang (#7781) * Fixed freetext hangs by adding canned modules back to self-sourced packets and transition to SENDING_ACTIVE state * Update meshmodule handling --- src/mesh/MeshModule.cpp | 11 ++--------- src/mesh/MeshModule.h | 2 +- src/mesh/NodeDB.cpp | 8 ++++---- src/mesh/Router.cpp | 6 +++--- src/modules/CannedMessageModule.cpp | 3 +-- src/modules/RoutingModule.cpp | 2 +- src/modules/RoutingModule.h | 2 -- 7 files changed, 12 insertions(+), 22 deletions(-) diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index 409c52179..22fcec663 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -85,11 +85,8 @@ meshtastic_MeshPacket *MeshModule::allocErrorResponse(meshtastic_Routing_Error e return r; } -void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src, const char *specificModule) +void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src) { - if (specificModule) { - LOG_DEBUG("Calling specific module: %s", specificModule); - } // LOG_DEBUG("In call modules"); bool moduleFound = false; @@ -103,15 +100,11 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src, const char // Was this message directed to us specifically? Will be false if we are sniffing someone elses packets auto ourNodeNum = nodeDB->getNodeNum(); bool toUs = isBroadcast(mp.to) || isToUs(&mp); + bool fromUs = mp.from == ourNodeNum; for (auto i = modules->begin(); i != modules->end(); ++i) { auto &pi = **i; - // If specificModule is provided, only call that specific module - if (specificModule && (!pi.name || strcmp(pi.name, specificModule) != 0)) { - continue; - } - pi.currentRequest = ∓ /// We only call modules that are interested in the packet (and the message is destined to us or we are promiscious) diff --git a/src/mesh/MeshModule.h b/src/mesh/MeshModule.h index bf735439f..eda3f8881 100644 --- a/src/mesh/MeshModule.h +++ b/src/mesh/MeshModule.h @@ -73,7 +73,7 @@ class MeshModule /** For use only by MeshService */ - static void callModules(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO, const char *specificModule = nullptr); + static void callModules(meshtastic_MeshPacket &mp, RxSource src = RX_SRC_RADIO); static std::vector GetMeshModulesWithUIFrames(int startIndex); static void observeUIEvents(Observer *observer); diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index d544d0174..c8eba1b2e 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1711,10 +1711,10 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde /// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw void NodeDB::updateFrom(const meshtastic_MeshPacket &mp) { - // if (mp.from == getNodeNum()) { - // LOG_DEBUG("Ignore update from self"); - // return; - // } + if (mp.from == getNodeNum()) { + LOG_DEBUG("Ignore update from self"); + return; + } if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) { LOG_DEBUG("Update DB node 0x%x, rx_time=%u", mp.from, mp.rx_time); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 1f835bca7..c7e32c4a1 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -562,7 +562,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) // Now that we are encrypting the packet channel should be the hash (no longer the index) p->channel = hash; if (hash < 0) { - // No suitable channel could be found for sending + // No suitable channel could be found for return meshtastic_Routing_Error_NO_CHANNEL; } crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes); @@ -578,7 +578,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p) // Now that we are encrypting the packet channel should be the hash (no longer the index) p->channel = hash; if (hash < 0) { - // No suitable channel could be found for sending + // No suitable channel could be found for return meshtastic_Routing_Error_NO_CHANNEL; } crypto->encryptPacket(getFrom(p), p->id, numbytes, bytes); @@ -671,7 +671,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src) mqtt->onSend(*p_encrypted, *p, p->channel); #endif } else if (p->from == nodeDB->getNodeNum() && !skipHandle) { - MeshModule::callModules(*p, src, ROUTING_MODULE); + MeshModule::callModules(*p, src); } packetPool.release(p_encrypted); // Release the encrypted packet diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index d40dcd24f..e9165e57c 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -632,10 +632,10 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo // Normal canned message selection if (runState == CANNED_MESSAGE_RUN_STATE_INACTIVE || runState == CANNED_MESSAGE_RUN_STATE_DISABLED) { } else { +#if CANNED_MESSAGE_ADD_CONFIRMATION // Show confirmation dialog before sending canned message NodeNum destNode = dest; ChannelIndex chan = channel; -#if CANNED_MESSAGE_ADD_CONFIRMATION graphics::menuHandler::showConfirmationBanner("Send message?", [this, destNode, chan, current]() { this->sendText(destNode, chan, current, false); payload = runState; @@ -991,7 +991,6 @@ int32_t CannedMessageModule::runOnce() this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE; } } - e.action = UIFrameEvent::Action::REGENERATE_FRAMESET; this->currentMessageIndex = -1; this->freetext = ""; this->cursor = 0; diff --git a/src/modules/RoutingModule.cpp b/src/modules/RoutingModule.cpp index b10413cc8..e7e92c79a 100644 --- a/src/modules/RoutingModule.cpp +++ b/src/modules/RoutingModule.cpp @@ -73,7 +73,7 @@ uint8_t RoutingModule::getHopLimitForResponse(uint8_t hopStart, uint8_t hopLimit return Default::getConfiguredOrDefaultHopLimit(config.lora.hop_limit); // Use the default hop limit } -RoutingModule::RoutingModule() : ProtobufModule(ROUTING_MODULE, meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg) +RoutingModule::RoutingModule() : ProtobufModule("routing", meshtastic_PortNum_ROUTING_APP, &meshtastic_Routing_msg) { isPromiscuous = true; diff --git a/src/modules/RoutingModule.h b/src/modules/RoutingModule.h index 7b43a6e98..c047f6e29 100644 --- a/src/modules/RoutingModule.h +++ b/src/modules/RoutingModule.h @@ -2,8 +2,6 @@ #include "Channels.h" #include "ProtobufModule.h" -static const char *ROUTING_MODULE = "routing"; - /** * Routing module for router control messages */ From 11db6d4dcc1d886f79696b797659e3ac58cb9d43 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 30 Aug 2025 04:22:23 +1000 Subject: [PATCH 22/24] Can't trust RTCs to tell the time. (#7779) Further to https://github.com/meshtastic/firmware/pull/7772 , we discovered that some RTCs have hard-coded start times well in the past. This patch gives RTCs the same treatment as GPS - if the time is earlier than BUILD_EPOCH, we don't use it. Fixes #7771 Fixes #7750 --- src/gps/RTC.cpp | 26 ++++++++++++++++++++++---- src/gps/RTC.h | 2 +- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp index d574c9ad0..185adacd9 100644 --- a/src/gps/RTC.cpp +++ b/src/gps/RTC.cpp @@ -23,7 +23,7 @@ static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only upda * Reads the current date and time from the RTC module and updates the system time. * @return True if the RTC was successfully read and the system time was updated, false otherwise. */ -void readFromRTC() +RTCSetResult readFromRTC() { struct timeval tv; /* btw settimeofday() is helpful here too*/ #ifdef RV3028_RTC @@ -44,8 +44,15 @@ void readFromRTC() t.tm_sec = rtc.getSecond(); tv.tv_sec = gm_mktime(&t); tv.tv_usec = 0; - 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); + return RTCSetResultInvalidTime; + } +#endif + LOG_DEBUG("Read RTC time from RV3028 getTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch); timeStartMsec = now; @@ -53,6 +60,7 @@ void readFromRTC() if (currentQuality == RTCQualityNone) { currentQuality = RTCQualityDevice; } + return RTCSetResultSuccess; } #elif defined(PCF8563_RTC) if (rtc_found.address == PCF8563_RTC) { @@ -75,8 +83,15 @@ void readFromRTC() t.tm_sec = tc.second; tv.tv_sec = gm_mktime(&t); tv.tv_usec = 0; - 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); + return RTCSetResultInvalidTime; + } +#endif + LOG_DEBUG("Read RTC time from PCF8563 getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch); timeStartMsec = now; @@ -84,6 +99,7 @@ void readFromRTC() if (currentQuality == RTCQualityNone) { currentQuality = RTCQualityDevice; } + return RTCSetResultSuccess; } #else if (!gettimeofday(&tv, NULL)) { @@ -92,8 +108,10 @@ void readFromRTC() LOG_DEBUG("Read RTC time as %ld", printableEpoch); timeStartMsec = now; zeroOffsetSecs = tv.tv_sec; + return RTCSetResultSuccess; } #endif + return RTCSetResultNotSet; } /** @@ -101,7 +119,7 @@ void readFromRTC() * * @param q The quality of the provided time. * @param tv A pointer to a timeval struct containing the time to potentially set the RTC to. - * @return True if the RTC was set, false otherwise. + * @return RTCSetResult * * If we haven't yet set our RTC this boot, set it from a GPS derived time */ diff --git a/src/gps/RTC.h b/src/gps/RTC.h index 96dec575b..010be6886 100644 --- a/src/gps/RTC.h +++ b/src/gps/RTC.h @@ -48,7 +48,7 @@ uint32_t getTime(bool local = false); /// Return time since 1970 in secs. If quality is RTCQualityNone return zero uint32_t getValidTime(RTCQuality minQuality, bool local = false); -void readFromRTC(); +RTCSetResult readFromRTC(); time_t gm_mktime(struct tm *tm); From ed394f5f9df0cc854d97ab342ab364e027f58006 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 13:58:32 -0500 Subject: [PATCH 23/24] Update protobufs (#7784) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protobufs b/protobufs index 8985852d7..4c4427c4a 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 8985852d752de3f7210f9a4a3e0923120ec438b3 +Subproject commit 4c4427c4a73c86fed7dc8632188bb8be95349d81 From 5ae4ff9162c4c9d90bc33b249b5f28a36d263d40 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 29 Aug 2025 13:59:40 -0500 Subject: [PATCH 24/24] Upgrade trunk (#7763) Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com> --- .trunk/trunk.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index a0dcf2ff5..c57c16319 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -8,8 +8,8 @@ plugins: uri: https://github.com/trunk-io/plugins lint: enabled: - - checkov@3.2.465 - - renovate@41.82.10 + - checkov@3.2.467 + - renovate@41.88.0 - prettier@3.6.2 - trufflehog@3.90.5 - yamllint@1.37.1