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
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/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]
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/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/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/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/Screen.cpp b/src/graphics/Screen.cpp
index 5e29814cb..9ef7cce86 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -356,7 +356,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)
@@ -588,7 +588,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 e02948864..a4f25797a 100644
--- a/src/graphics/draw/MenuHandler.cpp
+++ b/src/graphics/draw/MenuHandler.cpp
@@ -437,8 +437,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
@@ -728,7 +728,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);
@@ -771,7 +771,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;
@@ -1048,7 +1048,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/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/src/input/RotaryEncoderImpl.cpp b/src/input/RotaryEncoderImpl.cpp
new file mode 100644
index 000000000..d3fcbbf9d
--- /dev/null
+++ b/src/input/RotaryEncoderImpl.cpp
@@ -0,0 +1,76 @@
+#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)
+{
+ rotary = nullptr;
+}
+
+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/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/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/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);
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 ea40a625b..bdabf5e37 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
@@ -203,7 +204,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
@@ -361,6 +362,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;
@@ -807,7 +832,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
@@ -853,7 +878,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]) &&
@@ -1116,7 +1141,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 2ddd4862f..414752b5c 100644
--- a/src/main.h
+++ b/src/main.h
@@ -42,7 +42,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/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp
index 4a42e5197..20026767e 100644
--- a/src/mesh/StreamAPI.cpp
+++ b/src/mesh/StreamAPI.cpp
@@ -16,6 +16,95 @@ int32_t StreamAPI::runOncePart()
return result;
}
+int32_t StreamAPI::runOncePart(char *buf, uint16_t bufLen)
+{
+ auto result = readStream(buf, bufLen);
+ writeStream();
+ checkConnectionTimeout();
+ 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;
+ 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
*/
@@ -76,21 +165,6 @@ int32_t StreamAPI::readStream()
}
}
-/**
- * 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
*/
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/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;
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/modules/SerialModule.cpp b/src/modules/SerialModule.cpp
index 866497ecc..880768839 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,8 @@
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) || defined(T_ECHO_LITE)
SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {}
static Print *serialPrint = &Serial;
#elif defined(CONFIG_IDF_TARGET_ESP32C6)
@@ -78,7 +81,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 +245,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/esp32/architecture.h b/src/platform/esp32/architecture.h
index 80749ee6b..f3954840d 100644
--- a/src/platform/esp32/architecture.h
+++ b/src/platform/esp32/architecture.h
@@ -195,6 +195,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/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/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
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..b16e516a7
--- /dev/null
+++ b/variants/esp32s3/tlora-pager/platformio.ini
@@ -0,0 +1,71 @@
+; 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]
+board_level = extra
+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
diff --git a/variants/esp32s3/unphone/platformio.ini b/variants/esp32s3/unphone/platformio.ini
index ecb1cbd67..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}
@@ -52,8 +53,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
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
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