T-Watch Ultra support (#8171)

* feat: T-Watch Ultra support

* fix init touch controller

* add framebuffer

* update to device-ui

* trunk fmt

* update amoled driver reference

* PMU cosmetics

* power off lora

* fix NodeDB defaults

* trySetRTC when fixedPosition

* haptic touch (only BaseUI)

* init lora RF switch

* update LovyanGFX 1.2.19

* earlyInitVariant() adaptations acc. #9438

* update device-ui / touch handling

* Set NFC_CS disabled on boot

* Get t-watch-ultra working better on BaseUI

* Fix compilation

* Fix flash reads on t-watch-ultra

* Get baseui drawing to the screen correctly again on t-watch and add touch IRQ handling

* Add PMU IRQ handling

* Add IMU support

* Change define to avoid collision

* BaseUI changes to support t-watch-s3 rounded screen (#10786)

* BaseUI changes to support t-watch-s3 rounded screen

* Extend margin work to CannedMessages

* Finish merge

* Get audio working on watch-ultra

* trunk fmt

* added custom_meshtastic boilerplate

* T-Echo-Plus: disable BHI260AP while assumingly not implemented

* Drop the duplicate origBold declaration from the merge

* Inset incoming message bubbles on rounded screens

* Fix RTTTL tempo, WiFi screen margins, PMU guard and a duplicate define

* fix compile errror (the 2nd time)

* fix SDcard

* fix/workaround CO5300 pixel flush to SPI

* trunk fmt

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
This commit is contained in:
Manuel
2026-08-20 12:28:57 +00:00
committed by GitHub
co-authored by Jonathan Bennett Thomas Göttgens
parent 389559bddb
commit 0b906b4d15
35 changed files with 1060 additions and 166 deletions
+1
View File
@@ -79,6 +79,7 @@ class AudioThread : public concurrency::OSThread
auto sam = std::unique_ptr<ESP8266SAM>(new ESP8266SAM);
sam->Say(audioOut.get(), text);
setCPUFast(false);
audioOut->stop();
#ifdef AUDIO_AMP_ENABLE
AUDIO_AMP_ENABLE(false);
#endif
+46 -2
View File
@@ -1142,8 +1142,10 @@ int32_t Power::runOnce()
// cancel action also turns the screen on and off.
if (PMU->isPekeyShortPressIrq()) {
LOG_INFO("Input: Corona Button Click");
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_CANCEL, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
if (inputBroker) {
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_CANCEL, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event);
}
}
#endif
/*
@@ -1446,6 +1448,48 @@ bool Power::axpChipInit()
PMU->disablePowerOutput(XPOWERS_DLDO1); // Invalid power channel, it does not exist
PMU->disablePowerOutput(XPOWERS_DLDO2); // Invalid power channel, it does not exist
PMU->disablePowerOutput(XPOWERS_VBACKUP);
} else if (HW_VENDOR == meshtastic_HardwareModel_T_WATCH_ULTRA) {
PMU->clearIrqStatus();
// Turn off the PMU charging indicator light, no physical connection
PMU->setChargingLedMode(XPOWERS_CHG_LED_OFF); // NO LED
PMU->setPowerChannelVoltage(XPOWERS_ALDO1, 3300); // SD Card
PMU->enablePowerOutput(XPOWERS_ALDO1);
PMU->setPowerChannelVoltage(XPOWERS_ALDO2, 3300); // Display
PMU->enablePowerOutput(XPOWERS_ALDO2);
PMU->setPowerChannelVoltage(XPOWERS_ALDO3, 3300); // LoRa
PMU->enablePowerOutput(XPOWERS_ALDO3);
PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 1800); // Sensor
PMU->enablePowerOutput(XPOWERS_ALDO4);
PMU->setPowerChannelVoltage(XPOWERS_BLDO1, 3300); // GPS
PMU->enablePowerOutput(XPOWERS_BLDO1);
PMU->setPowerChannelVoltage(XPOWERS_BLDO2, 3300); // Speaker
PMU->enablePowerOutput(XPOWERS_BLDO2);
PMU->setPowerChannelVoltage(XPOWERS_VBACKUP, 3300); // RTC Button battery
PMU->enablePowerOutput(XPOWERS_VBACKUP);
// PMU->enablePowerOutput(XPOWERS_DLDO1); // NFC
// UNUSED POWER CHANNEL
PMU->disablePowerOutput(XPOWERS_DCDC2);
PMU->disablePowerOutput(XPOWERS_DCDC3);
PMU->disablePowerOutput(XPOWERS_DCDC4);
PMU->disablePowerOutput(XPOWERS_DCDC5);
PMU->disablePowerOutput(XPOWERS_CPULDO);
// Enable Measure
PMU->enableBattDetection();
PMU->enableVbusVoltageMeasure();
PMU->enableBattVoltageMeasure();
PMU->enableSystemVoltageMeasure();
PMU->enableTemperatureMeasure();
} else if (HW_VENDOR == meshtastic_HardwareModel_TBEAM_BPF) {
// T-Beam BPF rail map (per schematic LilyGo_TBeam_BPF r2025-05-08):
// DCDC1 -> ESP32 + OLED 3V3 (always on, protected)
+22 -19
View File
@@ -62,20 +62,17 @@ const int DURATION_1_1 = 1000; // 1/1 note
#ifdef HAS_I2S
void playTonesRTTTL(const ToneDuration *tone_durations, int size)
{
// translate ToneDuration[] to RTTTL string and play using audioThread
static std::unordered_map<int, std::string> freqToNote = {
{NOTE_C3, "c4"}, {NOTE_CS3, "c#4"}, {NOTE_D3, "d4"}, {NOTE_DS3, "d#4"}, {NOTE_E3, "e4"}, {NOTE_F3, "f4"},
{NOTE_FS3, "f#4"}, {NOTE_G3, "g4"}, {NOTE_GS3, "g#4"}, {NOTE_A3, "a4"}, {NOTE_AS3, "a#4"}, {NOTE_B3, "b4"},
{NOTE_C4, "c5"}, {NOTE_E4, "e5"}, {NOTE_G4, "g5"}, {NOTE_A4, "a5"}, {NOTE_C5, "c6"}, {NOTE_E5, "e6"},
{NOTE_G5, "g6"}, {NOTE_F5, "f6"}, {NOTE_G6, "g7"}, {NOTE_E7, "e8"}};
// translate ToneDuration[] to a single RTTTL string and play it via audioThread
static std::unordered_map<int, const char *> freqToNote = {
{NOTE_SILENT, "p"}, // rest
{NOTE_C3, "c4"}, {NOTE_CS3, "c#4"}, {NOTE_D3, "d4"}, {NOTE_DS3, "d#4"}, {NOTE_E3, "e4"}, {NOTE_F3, "f4"},
{NOTE_FS3, "f#4"}, {NOTE_G3, "g4"}, {NOTE_GS3, "g#4"}, {NOTE_A3, "a4"}, {NOTE_AS3, "a#4"}, {NOTE_B3, "b4"},
{NOTE_C4, "c5"}, {NOTE_CS4, "c#5"}, {NOTE_E4, "e5"}, {NOTE_G4, "g5"}, {NOTE_A4, "a5"}, {NOTE_B4, "b5"},
{NOTE_C5, "c6"}, {NOTE_E5, "e6"}, {NOTE_G5, "g6"}, {NOTE_F5, "f6"}, {NOTE_G6, "g7"}, {NOTE_E7, "e8"}};
char rtttl[128] = "tone:d=32,o=4,b=200:"; // default duration and octave
char rtttl[128] = "tone:d=32,o=4,b=240:"; // b=240 makes 240000/(bpm*d) match the ms durations above
for (int i = 0; i < size; i++) {
const auto &td = tone_durations[i];
std::string note = "b4";
if (freqToNote.find(td.frequency_khz) != freqToNote.end()) {
note = freqToNote[td.frequency_khz];
}
int dur = 32; // default duration
if (td.duration_ms >= 1000)
dur = 1;
@@ -90,16 +87,22 @@ void playTonesRTTTL(const ToneDuration *tone_durations, int size)
else
dur = 32;
char noteStr[64];
snprintf(noteStr, sizeof(noteStr), "%s,%d", note.c_str(), dur);
strncat(rtttl, noteStr, sizeof(rtttl) - strlen(rtttl) - 1);
auto it = freqToNote.find(td.frequency_khz);
const char *note = (it != freqToNote.end()) ? it->second : "p"; // unknown freq -> rest
audioThread->beginRttl(rtttl, strlen(rtttl));
while (audioThread->isPlaying()) {
delay(10);
}
return;
// RTTTL grammar puts duration before the note; notes are comma-separated
char noteStr[64];
snprintf(noteStr, sizeof(noteStr), "%s%d%s", i ? "," : "", dur, note);
strncat(rtttl, noteStr, sizeof(rtttl) - strlen(rtttl) - 1);
}
// trailing rest flushes the last note out of the I2S DMA buffer before teardown
strncat(rtttl, ",32p", sizeof(rtttl) - strlen(rtttl) - 1);
audioThread->beginRttl(rtttl, strlen(rtttl));
while (audioThread->isPlaying()) {
delay(10);
}
audioThread->stop(); // release I2S so the amp goes silent instead of looping the last buffer
}
#endif
+3 -3
View File
@@ -37,9 +37,9 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
{
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, SC7A20, BMA423, LSM6DS3, BMX160, STK8BAXX,
ICM20948, BMM150, BMI270, ICM42607P, ISM330DHCX, QMA6100P, QMI8658};
return firstOfOrNONE(14, types);
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, SC7A20, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948,
BMM150, BMI270, BHI260AP, ICM42607P, ISM330DHCX, QMA6100P, QMI8658};
return firstOfOrNONE(15, types);
}
ScanI2C::FoundDevice ScanI2C::firstMagnetometer() const
+2 -2
View File
@@ -1281,8 +1281,8 @@ void GPS::setPowerPMU(bool on)
} else if (HW_VENDOR == meshtastic_HardwareModel_LILYGO_TBEAM_S3_CORE) {
// t-beam-s3-core GNSS power channel
on ? PMU->enablePowerOutput(XPOWERS_ALDO4) : PMU->disablePowerOutput(XPOWERS_ALDO4);
} else if (HW_VENDOR == meshtastic_HardwareModel_T_WATCH_S3) {
// t-watch-s3-plus GNSS power channel
} else if (HW_VENDOR == meshtastic_HardwareModel_T_WATCH_ULTRA || HW_VENDOR == meshtastic_HardwareModel_T_WATCH_S3) {
// t-watch-ultra / t-watch-s3-plus GNSS power channel
on ? PMU->enablePowerOutput(XPOWERS_BLDO1) : PMU->disablePowerOutput(XPOWERS_BLDO1);
}
} else if (model == XPOWERS_AXP192) {
+5 -4
View File
@@ -681,12 +681,13 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
if (on) {
LOG_INFO("Turn on screen");
powerMon->setState(meshtastic_PowerMon_State_Screen_On);
#ifdef T_WATCH_S3
PMU->enablePowerOutput(XPOWERS_ALDO2);
#if defined(T_WATCH_S3) || defined(T_WATCH_ULTRA)
if (PMU) // cleared when both AXP init attempts failed
PMU->enablePowerOutput(XPOWERS_ALDO2);
#endif
// some screens seem to need a kick in the pants to turn back on
#if defined(MUZI_BASE) || defined(M5STACK_CARDPUTER_ADV)
#if defined(MUZI_BASE) || defined(M5STACK_CARDPUTER_ADV) || defined(TFT_RESET_AFTER_SLEEP)
dispdev->init();
dispdev->setBrightness(brightness);
dispdev->flipScreenVertically();
@@ -819,7 +820,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
#endif
#endif
#ifdef T_WATCH_S3
#if defined(T_WATCH_S3) // on T_WATCH_ULTRA, powering down this pin seems to goober the i2c bus.
PMU->disablePowerOutput(XPOWERS_ALDO2);
#endif
enabled = false;
+3
View File
@@ -287,6 +287,9 @@ class Screen : public concurrency::OSThread
// FIXME: Needs refactoring and getMacAddr needs to be moved to a utility class
char ourId[5];
// if we have a step counter, this stores the number of steps.
uint32_t steps = 0;
/// Initializes the UI, turns on the display, starts showing boot screen.
//
// Not thread safe - must be called before any other methods are called.
+5 -5
View File
@@ -104,14 +104,14 @@ void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w,
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert, bool show_date,
bool transparent_background, bool use_title_color_override, uint16_t title_color_override)
{
constexpr int HEADER_OFFSET_Y = 1;
constexpr int HEADER_OFFSET_Y = 1 + BASEUI_HEADER_MARGIN;
y += HEADER_OFFSET_Y;
display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT);
const int xOffset = 4;
const int highlightHeight = FONT_HEIGHT_SMALL - 1;
const int xOffset = 4 + BASEUI_HEADER_LR_MARGIN;
const int highlightHeight = FONT_HEIGHT_SMALL - 1 + BASEUI_HEADER_MARGIN;
const bool isInverted = (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_INVERTED);
const bool isBold = config.display.heading_bold;
@@ -250,8 +250,8 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
}
#endif
int batteryX = 1;
int batteryY = HEADER_OFFSET_Y + 1;
int batteryX = x + 1 + BASEUI_HEADER_LR_MARGIN;
int batteryY = HEADER_OFFSET_Y + 1 + BASEUI_HEADER_MARGIN / 2;
#if !defined(OLED_TINY)
// === Battery Icons ===
if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging
+17 -1
View File
@@ -21,7 +21,7 @@ namespace graphics
#define textSixthLine (textFifthLine + (FONT_HEIGHT_SMALL - 5))
// Consistent Line Spacing for devices like T114 and TEcho/ThinkNode M1 of devices
#define textFirstLine_medium (FONT_HEIGHT_SMALL + 1)
#define textFirstLine_medium (FONT_HEIGHT_SMALL + 1 + BASEUI_HEADER_MARGIN)
#define textSecondLine_medium (textFirstLine_medium + FONT_HEIGHT_SMALL)
#define textThirdLine_medium (textSecondLine_medium + FONT_HEIGHT_SMALL)
#define textFourthLine_medium (textThirdLine_medium + FONT_HEIGHT_SMALL)
@@ -36,6 +36,22 @@ namespace graphics
#define textFifthLine_large (textFourthLine_large + (FONT_HEIGHT_SMALL + 5))
#define textSixthLine_large (textFifthLine_large + (FONT_HEIGHT_SMALL + 5))
#ifndef BASEUI_HEADER_MARGIN
#define BASEUI_HEADER_MARGIN 0
#endif
#ifndef BASEUI_HEADER_LR_MARGIN
#define BASEUI_HEADER_LR_MARGIN 0
#endif
#ifndef BASEUI_BODY_LR_MARGIN
#define BASEUI_BODY_LR_MARGIN 0
#endif
#ifndef BASEUI_BELOW_HEADER_MARGIN
#define BASEUI_BELOW_HEADER_MARGIN 0
#endif
#ifndef ROUNDED_SCREEN
#define ROUNDED_SCREEN false
#endif
// Quick screen access
#define SCREEN_WIDTH display->getWidth()
#define SCREEN_HEIGHT display->getHeight()
+162 -5
View File
@@ -17,6 +17,92 @@
extern SX1509 gpioExtender;
#endif
#ifdef TFT_MESH_OVERRIDE
uint16_t TFT_MESH = TFT_MESH_OVERRIDE;
#else
uint16_t TFT_MESH = COLOR565(0x67, 0xEA, 0x94);
#endif
#if defined(CO5300_CS)
#include <LovyanGFX.hpp> // Graphics and font library for AMOLED driver chip
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_CO5300 _panel_instance;
lgfx::Bus_SPI _bus_instance;
public:
LGFX(void)
{
{
auto cfg = _bus_instance.config();
// configure SPI
cfg.spi_host = CO5300_SPI_HOST; // ESP32-S2,S3,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
cfg.spi_mode = SPI_MODE0;
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; // Set to true if reception is done on the MOSI pin
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 = CO5300_SCK; // Set SPI SCLK pin number
cfg.pin_io0 = CO5300_IO0;
cfg.pin_io1 = CO5300_IO1;
cfg.pin_io2 = CO5300_IO2;
cfg.pin_io3 = CO5300_IO3;
_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 = CO5300_CS; // Pin number where CS is connected (-1 = disable)
cfg.pin_rst = CO5300_RESET; // Pin number where RST is connected (-1 = disable)
cfg.panel_width = TFT_WIDTH; // actual displayable width
cfg.panel_height = TFT_HEIGHT; // actual displayable height
cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is upside down)
cfg.offset_x = TFT_OFFSET_X;
cfg.offset_y = TFT_OFFSET_Y;
cfg.dummy_read_pixel = 8; // Number of bits for dummy read before pixel readout
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 = false; // 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
cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
// Set the following only when the display is shifted with a driver with a variable number of pixels
cfg.memory_width = TFT_WIDTH; // Maximum width supported by the driver IC
cfg.memory_height = TFT_HEIGHT; // Maximum height supported by the driver IC
_panel_instance.config(cfg);
}
setPanel(&_panel_instance);
}
bool init()
{
#ifdef CO5300_RESET
LOG_DEBUG("LGFX_Panel_CO5300::init()");
lgfx::pinMode(CO5300_RESET, lgfx::pin_mode_t::output);
lgfx::gpio_hi(CO5300_RESET);
delay(20);
lgfx::gpio_lo(CO5300_RESET);
delay(30);
lgfx::gpio_hi(CO5300_RESET);
delay(20);
#endif
return lgfx::LGFX_Device::init();
}
};
static LGFX *tft = nullptr;
#endif
#if defined(ST7735S)
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
@@ -1394,6 +1480,70 @@ void TFTDisplay::display(bool fromBlank)
}
// Step 3: Copy only the changed span into the pixel line buffer.
#if defined(CO5300_CS)
constexpr uint32_t kCO5300MinTransferBytes = 80;
constexpr uint32_t kCO5300BytesPerColumn = sizeof(uint16_t) * 2; // two rows, RGB565
constexpr uint32_t kCO5300MinColumns = (kCO5300MinTransferBytes + kCO5300BytesPerColumn - 1) / kCO5300BytesPerColumn;
// CO5300 workaround: widen very small updates so LovyanGFX avoids tiny SPI writes.
uint32_t span = x_LastPixelUpdate - x_FirstPixelUpdate + 1;
if (span < kCO5300MinColumns) {
uint32_t needed = kCO5300MinColumns - span;
uint32_t growLeft = needed / 2;
uint32_t growRight = needed - growLeft;
const uint32_t availableLeft = x_FirstPixelUpdate;
if (growLeft > availableLeft)
growLeft = availableLeft;
x_FirstPixelUpdate -= growLeft;
needed -= growLeft;
const uint32_t availableRight = (displayWidth - 1) - x_LastPixelUpdate;
const uint32_t extendRight = (needed < availableRight) ? needed : availableRight;
x_LastPixelUpdate += extendRight;
needed -= extendRight;
const uint32_t extendLeft = (needed < x_FirstPixelUpdate) ? needed : x_FirstPixelUpdate;
x_FirstPixelUpdate -= extendLeft;
}
// Keep transfer edges aligned as before for DMA-friendly boundaries.
x_FirstPixelUpdate &= ~1U;
x_LastPixelUpdate = (x_LastPixelUpdate | 1U);
if (x_LastPixelUpdate >= displayWidth) {
x_LastPixelUpdate = displayWidth - 1;
}
// snap y down to the even-row pair (AMOLED requires 2-row aligned writes)
const uint32_t y_draw = y & ~1U;
span = x_LastPixelUpdate - x_FirstPixelUpdate + 1;
const int y_offset = (int)y_draw - (int)y;
for (x = x_FirstPixelUpdate; x <= x_LastPixelUpdate; x++) {
const uint32_t col = x - x_FirstPixelUpdate;
uint32_t bi = (y_draw / 8) * displayWidth;
isset = buffer[x + bi] & (1 << (y_draw & 7));
#if GRAPHICS_TFT_COLORING_ENABLED
linePixelBuffer[x_FirstPixelUpdate + col] =
hasColorRegions ? graphics::resolveTFTColorPixel(static_cast<int16_t>(x), static_cast<int16_t>(y_draw), isset,
colorTftWhite, colorTftBlack)
: (isset ? colorTftWhite : colorTftBlack);
#else
linePixelBuffer[x_FirstPixelUpdate + col] = isset ? colorTftWhite : colorTftBlack;
#endif
bi = ((y_draw + 1) / 8) * displayWidth;
isset = buffer[x + bi] & (1 << ((y_draw + 1) & 7));
#if GRAPHICS_TFT_COLORING_ENABLED
linePixelBuffer[x_FirstPixelUpdate + span + col] =
hasColorRegions ? graphics::resolveTFTColorPixel(static_cast<int16_t>(x), static_cast<int16_t>(y_draw + 1),
isset, colorTftWhite, colorTftBlack)
: (isset ? colorTftWhite : colorTftBlack);
#else
linePixelBuffer[x_FirstPixelUpdate + span + col] = isset ? colorTftWhite : colorTftBlack;
#endif
}
const uint8_t lines_updated = 2;
#else
int y_offset = 0;
#if GRAPHICS_TFT_COLORING_ENABLED
if (hasColorRegions)
graphics::beginTFTColorRow(static_cast<int16_t>(y));
@@ -1411,13 +1561,16 @@ void TFTDisplay::display(bool fromBlank)
linePixelBuffer[x] = isset ? colorTftWhite : colorTftBlack;
#endif
}
const uint8_t lines_updated = 1;
#endif
#if defined(HACKADAY_COMMUNICATOR)
tft->draw16bitBeRGBBitmap(x_FirstPixelUpdate, y, &linePixelBuffer[x_FirstPixelUpdate],
(x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1);
#else
// 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->pushImage(x_FirstPixelUpdate, y, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), 1,
tft->pushImage(x_FirstPixelUpdate, y + y_offset, (x_LastPixelUpdate - x_FirstPixelUpdate + 1), lines_updated,
&linePixelBuffer[x_FirstPixelUpdate]);
#endif
somethingChanged = true;
@@ -1485,7 +1638,7 @@ void TFTDisplay::sendCommand(uint8_t com)
// handle display on/off directly
switch (com) {
case DISPLAYON: {
// LOG_DEBUG("Display on");
LOG_DEBUG("Display on");
backlightEnable->set(true);
#if ARCH_PORTDUINO
display(true);
@@ -1513,7 +1666,7 @@ void TFTDisplay::sendCommand(uint8_t com)
break;
}
case DISPLAYOFF: {
// LOG_DEBUG("Display off");
LOG_DEBUG("Display off");
backlightEnable->set(false);
#if ARCH_PORTDUINO
tft->clear();
@@ -1620,8 +1773,8 @@ bool TFTDisplay::connect()
#endif
}
backlightEnable->set(true);
LOG_INFO("Power to TFT Backlight");
backlightEnable->set(true);
#ifdef UNPHONE
unphone.backlight(true); // using unPhone library
@@ -1649,7 +1802,7 @@ 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) || defined(T_LORA_PAGER)
#elif ARCH_PORTDUINO || defined(SENSECAP_INDICATOR) || defined(T_LORA_PAGER) || defined(T_WATCH_ULTRA)
tft->setRotation(0); // use config.yaml to set rotation
#else
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
@@ -1657,7 +1810,11 @@ bool TFTDisplay::connect()
tft->fillScreen(getThemeDefaultOffColor());
if (this->linePixelBuffer == NULL) {
#if defined(CO5300_CS)
this->linePixelBuffer = (uint16_t *)malloc(sizeof(uint16_t) * displayWidth * 2);
#else
this->linePixelBuffer = (uint16_t *)malloc(sizeof(uint16_t) * displayWidth);
#endif
if (!this->linePixelBuffer) {
LOG_ERROR("Not enough memory to create TFT line buffer");
+38 -30
View File
@@ -59,17 +59,18 @@ void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr);
y += BASEUI_BELOW_HEADER_MARGIN;
const char *wifiName = config.network.wifi_ssid;
if (WiFi.status() != WL_CONNECTED) {
display->drawString(x, getTextPositions(display)[line++], "WiFi: Not Connected");
display->drawString(x + BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line++] + y, "WiFi: Not Connected");
} else {
display->drawString(x, getTextPositions(display)[line++], "WiFi: Connected");
display->drawString(x + BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line++] + y, "WiFi: Connected");
char rssiStr[32];
snprintf(rssiStr, sizeof(rssiStr), "RSSI: %d", WiFi.RSSI());
display->drawString(x, getTextPositions(display)[line++], rssiStr);
display->drawString(x + BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line++] + y, rssiStr);
}
/*
@@ -87,36 +88,36 @@ void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i
if (WiFi.status() == WL_CONNECTED) {
char ipStr[64];
snprintf(ipStr, sizeof(ipStr), "IP: %s", WiFi.localIP().toString().c_str());
display->drawString(x, getTextPositions(display)[line++], ipStr);
display->drawString(x + BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line++] + y, ipStr);
} else if (WiFi.status() == WL_NO_SSID_AVAIL) {
display->drawString(x, getTextPositions(display)[line++], "SSID Not Found");
display->drawString(x + BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line++] + y, "SSID Not Found");
} else if (WiFi.status() == WL_CONNECTION_LOST) {
display->drawString(x, getTextPositions(display)[line++], "Connection Lost");
display->drawString(x + BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line++] + y, "Connection Lost");
} else if (WiFi.status() == WL_IDLE_STATUS) {
display->drawString(x, getTextPositions(display)[line++], "Idle ... Reconnecting");
display->drawString(x + BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line++] + y, "Idle ... Reconnecting");
} else if (WiFi.status() == WL_CONNECT_FAILED) {
display->drawString(x, getTextPositions(display)[line++], "Connection Failed");
display->drawString(x + BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line++] + y, "Connection Failed");
}
#ifdef ARCH_ESP32
else {
// Codes:
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code
display->drawString(x, getTextPositions(display)[line++],
display->drawString(x + BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line++] + y,
WiFi.disconnectReasonName(static_cast<wifi_err_reason_t>(getWifiDisconnectReason())));
}
#else
else {
char statusStr[32];
snprintf(statusStr, sizeof(statusStr), "Unknown status: %d", WiFi.status());
display->drawString(x, getTextPositions(display)[line++], statusStr);
display->drawString(x + BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line++] + y, statusStr);
}
#endif
char ssidStr[64];
snprintf(ssidStr, sizeof(ssidStr), "SSID: %s", wifiName);
display->drawString(x, getTextPositions(display)[line++], ssidStr);
display->drawString(x + BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line++] + y, ssidStr);
display->drawString(x, getTextPositions(display)[line++], "URL: http://meshtastic.local");
display->drawString(x + BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line++] + y, "URL: http://meshtastic.local");
graphics::drawCommonFooter(display, x, y);
@@ -144,9 +145,11 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr);
y += BASEUI_BELOW_HEADER_MARGIN;
// === First Row: Region / BLE Name ===
graphics::UIRenderer::drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, 0, true, "");
graphics::UIRenderer::drawNodes(display, x + BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line] + 2 + y, nodeStatus, 0,
true, "");
uint8_t dmac[6];
char shortnameble[35];
@@ -158,8 +161,8 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
snprintf(shortnameble, sizeof(shortnameble), "BLE: %s", screen->ourId);
}
int textWidth = display->getStringWidth(shortnameble);
int nameX = (SCREEN_WIDTH - textWidth);
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
int nameX = (SCREEN_WIDTH - textWidth - BASEUI_BODY_LR_MARGIN);
display->drawString(nameX, getTextPositions(display)[line++] + y, shortnameble);
if (!graphics::isCompactPanel(display)) {
// === Second Row: Role ===
@@ -168,7 +171,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
snprintf(device_role, sizeof(device_role), "Role: %s", role);
textWidth = display->getStringWidth(device_role);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], device_role);
display->drawString(nameX, getTextPositions(display)[line++] + y, device_role);
}
// === Third Row: Radio Preset ===
@@ -194,7 +197,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
}
textWidth = display->getStringWidth(regionradiopreset);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], regionradiopreset);
display->drawString(nameX, getTextPositions(display)[line++] + y, regionradiopreset);
// === Fourth Row: Frequency / ChanNum ===
char frequencyslot[35];
@@ -220,14 +223,14 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
}
textWidth = display->getStringWidth(frequencyslot);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
display->drawString(nameX, getTextPositions(display)[line++] + y, frequencyslot);
#if !defined(OLED_TINY)
// === Fifth Row: Channel Utilization ===
if (!config.lora.tx_enabled) {
const char *txdisabled = "Transmit Disabled";
textWidth = display->getStringWidth(txdisabled);
display->drawString((SCREEN_WIDTH - textWidth) / 2, getTextPositions(display)[line], txdisabled);
display->drawString((SCREEN_WIDTH - textWidth) / 2, getTextPositions(display)[line] + y, txdisabled);
} else {
const char *chUtil = "ChUtil:";
@@ -236,7 +239,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
int chUtil_x = (currentResolution == ScreenResolution::High) ? display->getStringWidth(chUtil) + 10
: display->getStringWidth(chUtil) + 5;
int chUtil_y = getTextPositions(display)[line] + 3;
int chUtil_y = getTextPositions(display)[line] + 3 + y;
int chutil_bar_width = (currentResolution == ScreenResolution::High) ? 100 : 50;
int chutil_bar_max_fill = chutil_bar_width - 2; // Account for border
@@ -250,7 +253,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
(chUtil_x + chutil_bar_width + display->getStringWidth(chUtilPercentage) + extraoffset) / 2;
int starting_position = centerofscreen - total_line_content_width;
display->drawString(starting_position, getTextPositions(display)[line], chUtil);
display->drawString(starting_position, getTextPositions(display)[line] + y, chUtil);
// Force 61% or higher to show a full 100% bar, text would still show related percent.
if (chutil_percent >= 61) {
@@ -297,7 +300,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
display->fillRect(starting_position + chUtil_x + 1, chUtil_y + 1, fillRight, chutil_bar_height - 2);
}
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++],
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++] + y,
chUtilPercentage);
}
#endif
@@ -318,11 +321,12 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr);
y += BASEUI_BELOW_HEADER_MARGIN;
// === Layout ===
int line = 1;
const int barHeight = 6;
const int labelX = x;
const int labelX = x + BASEUI_BODY_LR_MARGIN;
int barsOffset = (currentResolution == ScreenResolution::High) ? 24 : 0;
#ifdef USE_EINK
#ifndef T_DECK_PRO
@@ -353,7 +357,11 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
}
int textWidth = display->getStringWidth(combinedStr);
int adjustedBarWidth = SCREEN_WIDTH - barX - textWidth - 6;
int labelWidth = display->getStringWidth(label);
if (barX < BASEUI_BODY_LR_MARGIN + labelWidth) {
barX = BASEUI_BODY_LR_MARGIN + labelWidth;
}
int adjustedBarWidth = SCREEN_WIDTH - barX - textWidth - 6 - BASEUI_BODY_LR_MARGIN;
if (adjustedBarWidth < 10)
adjustedBarWidth = 10;
@@ -361,10 +369,10 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
// Label
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawString(labelX, getTextPositions(display)[line], label);
display->drawString(labelX, getTextPositions(display)[line] + y, label);
#if !defined(OLED_TINY)
// Bar
int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2;
int barY = getTextPositions(display)[line] + y + (FONT_HEIGHT_SMALL - barHeight) / 2;
display->setColor(WHITE);
display->drawRect(barX, barY, adjustedBarWidth, barHeight);
@@ -384,7 +392,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
#endif
// Value string
display->setTextAlignment(TEXT_ALIGN_RIGHT);
display->drawString(SCREEN_WIDTH, getTextPositions(display)[line], combinedStr);
display->drawString(SCREEN_WIDTH - BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line] + y, combinedStr);
};
// === Memory values ===
@@ -473,7 +481,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
int textWidth = display->getStringWidth(appversionstr);
int nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], appversionstr);
display->drawString(nameX, getTextPositions(display)[line++] + y, appversionstr);
if (!graphics::isCompactPanel(display) &&
(SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5))) { // Only show uptime if the screen can show it
@@ -481,7 +489,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
getUptimeStr(millis(), "Up: ", uptimeStr, sizeof(uptimeStr));
textWidth = display->getStringWidth(uptimeStr);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], uptimeStr);
display->drawString(nameX, getTextPositions(display)[line++] + y, uptimeStr);
}
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show API state if the screen can show it
@@ -528,7 +536,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
}
#endif
if (api_state[0] != '\0') {
display->drawString((SCREEN_WIDTH - display->getStringWidth(api_state)) / 2, getTextPositions(display)[line++],
display->drawString((SCREEN_WIDTH - display->getStringWidth(api_state)) / 2, getTextPositions(display)[line++] + y,
api_state);
}
}
+12 -9
View File
@@ -437,12 +437,13 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
display->setFont(FONT_SMALL);
const bool compactPanel = graphics::isCompactPanel(display);
// Compact panels: no bottom nav row anymore (see UIRenderer::drawNavigationBar), full height available.
const int navHeight = compactPanel ? 0 : FONT_HEIGHT_SMALL;
const int navHeight = compactPanel ? 0 : FONT_HEIGHT_SMALL + BASEUI_BELOW_HEADER_MARGIN + BASEUI_HEADER_MARGIN;
const int scrollBottom = SCREEN_HEIGHT - navHeight;
const int contentTop = compactPanel ? 0 : getTextPositions(display)[1];
// Rounded screens start the body below the header margin; getTextPositions(display)[1] + BASEUI_BELOW_HEADER_MARGIN
const int contentTop = compactPanel ? 0 : navHeight;
const int usableHeight = compactPanel ? scrollBottom - contentTop : scrollBottom;
constexpr int LEFT_MARGIN = 2;
constexpr int RIGHT_MARGIN = 2;
constexpr int LEFT_MARGIN = 2 + BASEUI_BODY_LR_MARGIN;
constexpr int RIGHT_MARGIN = 2 + BASEUI_BODY_LR_MARGIN;
constexpr int SCROLLBAR_WIDTH = 3;
constexpr int BUBBLE_PAD_X = 3;
constexpr int BUBBLE_PAD_Y = 4;
@@ -453,6 +454,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// Check if bubbles are enabled
const bool showBubbles = config.display.enable_message_bubbles && !compactPanel;
const int textIndent = showBubbles ? (BUBBLE_PAD_X + BUBBLE_TEXT_INDENT) : LEFT_MARGIN;
// Bubbles carry their own padding, so the rounded-screen inset has to come from here
const int contentLeft = x + (showBubbles ? BASEUI_BODY_LR_MARGIN : 0);
// Derived widths
const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - (showBubbles ? (BUBBLE_PAD_X * 2) : 0);
@@ -873,10 +876,10 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
if (b.mine) {
bubbleX = rightEdge - bubbleW;
} else {
bubbleX = x;
bubbleX = contentLeft;
}
if (bubbleX < x)
bubbleX = x;
if (bubbleX < contentLeft)
bubbleX = contentLeft;
if (bubbleX + bubbleW > rightEdge)
bubbleW = std::max(1, rightEdge - bubbleX);
@@ -953,7 +956,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
if (headerX < LEFT_MARGIN)
headerX = LEFT_MARGIN;
} else {
headerX = x + textIndent;
headerX = contentLeft + textIndent;
}
graphics::UIRenderer::drawStringWithEmotes(display, headerX, lineY, cachedLines[i].c_str(), FONT_HEIGHT_SMALL, 1,
true);
@@ -1002,7 +1005,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
drawStringWithEmotes(display, rightX, lineY, cachedLines[i], emotes, numEmotes);
} else {
drawStringWithEmotes(display, x + textIndent, lineY, cachedLines[i], emotes, numEmotes);
drawStringWithEmotes(display, contentLeft + textIndent, lineY, cachedLines[i], emotes, numEmotes);
}
}
}
+28 -5
View File
@@ -47,6 +47,20 @@ void drawScaledXBitmap16x16(int x, int y, int width, int height, const uint8_t *
}
}
void drawScaledXBitmap3x(int x, int y, int width, int height, const uint8_t *bitmapXBM, OLEDDisplay *display)
{
for (int row = 0; row < height; row++) {
uint8_t rowMask = (1 << row);
for (int col = 0; col < width; col++) {
uint8_t colData = pgm_read_byte(&bitmapXBM[col]);
if (colData & rowMask) {
// Note: rows become X, columns become Y after transpose
display->fillRect(x + row * 3, y + col * 3, 3, 3);
}
}
}
}
// Static variables for dynamic cycling
static ListMode_Node currentMode_Nodes = MODE_LAST_HEARD;
static ListMode_Location currentMode_Location = MODE_DISTANCE;
@@ -606,7 +620,7 @@ void drawCompassUnknown(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *title,
EntryRenderer renderer, NodeExtrasRenderer extras, float headingRadian, double lat, double lon)
{
const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1 + BASEUI_HEADER_MARGIN;
// Compact panels: 4 rows fit (0,9,18,27), a 5th pages instead of cramming in.
const int rowYOffset = graphics::isCompactPanel(display) ? (FONT_HEIGHT_SMALL - 4) : (FONT_HEIGHT_SMALL - 3);
bool locationScreen = false;
@@ -622,7 +636,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
// Compact panels have no header (see drawCommonHeader) - don't reserve space for one.
if (!graphics::isCompactPanel(display))
y += COMMON_HEADER_HEIGHT;
y += COMMON_HEADER_HEIGHT + BASEUI_BELOW_HEADER_MARGIN;
firstRowY = y;
int totalColumns = 1; // Default to 1 column
@@ -638,7 +652,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
} else {
if (SCREEN_WIDTH <= 64) {
totalColumns = 1;
} else if (SCREEN_WIDTH > 64 && SCREEN_WIDTH <= 240) {
} else if ((SCREEN_WIDTH > 64 && SCREEN_WIDTH <= 240) || ROUNDED_SCREEN) {
totalColumns = 2;
} else {
totalColumns = 3;
@@ -691,11 +705,20 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
auto *node = nodeDB->getMeshNode(nodeNum);
int xPos = x + (col * columnWidth);
int yPos = y + yOffset;
int effectiveColumnWidth = columnWidth;
if (BASEUI_BODY_LR_MARGIN) {
if (col == 0) {
xPos += BASEUI_BODY_LR_MARGIN;
effectiveColumnWidth -= BASEUI_BODY_LR_MARGIN;
} else if (col == (totalColumns - 1)) {
effectiveColumnWidth -= BASEUI_BODY_LR_MARGIN;
}
}
renderer(display, node, xPos, yPos, columnWidth);
renderer(display, node, xPos, yPos, effectiveColumnWidth);
if (extras)
extras(display, node, xPos, yPos, columnWidth, headingRadian, lat, lon);
extras(display, node, xPos, yPos, effectiveColumnWidth, headingRadian, lat, lon);
lastNodeY = max(lastNodeY, yPos + FONT_HEIGHT_SMALL);
yOffset += rowYOffset;
+1
View File
@@ -65,6 +65,7 @@ void scrollDown();
// Bitmap drawing function
void drawScaledXBitmap16x16(int x, int y, int width, int height, const uint8_t *bitmapXBM, OLEDDisplay *display);
void drawScaledXBitmap3x(int x, int y, int width, int height, const uint8_t *bitmapXBM, OLEDDisplay *display);
} // namespace NodeListRenderer
+81 -48
View File
@@ -451,7 +451,8 @@ static bool computeBottomCompassPlacement(OLEDDisplay *display, int16_t xOffset,
int16_t margin, int16_t *compassX, int16_t *compassY, int16_t *compassRadius)
{
// Return false when content leaves no room for a readable compass.
int availableHeight = SCREEN_HEIGHT - yBelowContent - bottomReserved - margin;
int availableHeight =
SCREEN_HEIGHT - yBelowContent - bottomReserved - margin - BASEUI_HEADER_MARGIN - BASEUI_BELOW_HEADER_MARGIN;
if (availableHeight < FONT_HEIGHT_SMALL * 2) {
return false;
}
@@ -581,12 +582,12 @@ void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y,
if (!gps->getIsConnected() && !config.position.fixed_position) {
if (strcmp(mode, "line1") == 0) {
strcpy(displayLine, "No GPS present");
display->drawString(x, y, displayLine);
display->drawString(x + BASEUI_BODY_LR_MARGIN, y, displayLine);
}
} else if (!gps->getHasLock() && !config.position.fixed_position) {
if (strcmp(mode, "line1") == 0) {
strcpy(displayLine, gps->getHasTime() ? "GPS Time Only" : "No GPS Lock");
display->drawString(x, y, displayLine);
display->drawString(x + BASEUI_BODY_LR_MARGIN, y, displayLine);
}
} else {
@@ -665,13 +666,14 @@ void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y,
}
if (strcmp(mode, "line1") == 0) {
display->drawString(x, y, coordinateLine_1);
display->drawString(x + BASEUI_BODY_LR_MARGIN, y, coordinateLine_1);
} else if (strcmp(mode, "line2") == 0) {
display->drawString(x, y, coordinateLine_2);
display->drawString(x + BASEUI_BODY_LR_MARGIN, y, coordinateLine_2);
} else if (strcmp(mode, "combined") == 0) {
display->drawString(x, y, coordinateLine_1);
if (coordinateLine_2[0] != '\0') {
display->drawString(x + display->getStringWidth(coordinateLine_1), y, coordinateLine_2);
display->drawString(x + BASEUI_BODY_LR_MARGIN + display->getStringWidth(coordinateLine_1), y,
coordinateLine_2);
}
}
@@ -683,12 +685,12 @@ void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y,
snprintf(coordinateLine_2, sizeof(coordinateLine_2), "Lon: %3i° %2i' %2u\" %1c", geoCoord.getDMSLonDeg(),
geoCoord.getDMSLonMin(), geoCoord.getDMSLonSec(), geoCoord.getDMSLonCP());
if (strcmp(mode, "line1") == 0) {
display->drawString(x, y, coordinateLine_1);
display->drawString(x + BASEUI_BODY_LR_MARGIN, y, coordinateLine_1);
} else if (strcmp(mode, "line2") == 0) {
display->drawString(x, y, coordinateLine_2);
display->drawString(x + BASEUI_BODY_LR_MARGIN, y, coordinateLine_2);
} else { // both
display->drawString(x, y, coordinateLine_1);
display->drawString(x, y + 10, coordinateLine_2);
display->drawString(x + BASEUI_BODY_LR_MARGIN, y, coordinateLine_1);
display->drawString(x + BASEUI_BODY_LR_MARGIN, y + 10, coordinateLine_2);
}
}
}
@@ -933,6 +935,7 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
}
#endif
y += BASEUI_BELOW_HEADER_MARGIN;
// ===== DYNAMIC ROW STACKING WITH YOUR MACROS =====
// 1. Each potential info row has a macro-defined Y position (not regular increments!).
// 2. Each row is only shown if it has valid data.
@@ -1304,7 +1307,7 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
}
// ****************************
// * Device Focused Screen *
// * Home Frame *
// ****************************
void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
@@ -1313,6 +1316,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
display->setFont(FONT_SMALL);
int line = 1;
const meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
bool origBold = config.display.heading_bold;
// === Header ===
if (currentResolution == ScreenResolution::UltraLow) {
@@ -1320,11 +1324,11 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
} else {
graphics::drawCommonHeader(display, x, y, "");
}
y += BASEUI_BELOW_HEADER_MARGIN;
// === Content below header ===
// === First Row: Region / Channel Utilization and Uptime ===
bool origBold = config.display.heading_bold;
config.display.heading_bold = false;
const bool compactPanel = graphics::isCompactPanel(display);
@@ -1333,19 +1337,20 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
const char *txdisabled = "Transmit Disabled";
if (compactPanel) {
int textWidth = display->getStringWidth(txdisabled);
display->drawString((SCREEN_WIDTH - textWidth) / 2, getTextPositions(display)[line], txdisabled);
display->drawString((SCREEN_WIDTH - textWidth) / 2, getTextPositions(display)[line] + y, txdisabled);
} else {
display->drawString(x, getTextPositions(display)[line], txdisabled);
display->drawString(x + BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line] + y, txdisabled);
}
} else if (compactPanel) {
// No room for a separate left/right column layout - center it instead.
drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online", true);
drawNodes(display, x, getTextPositions(display)[line] + y + 2, nodeStatus, -1, false, "online", true);
} else {
// Display Region and Channel Utilization
if (currentResolution == ScreenResolution::UltraLow) {
drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
drawNodes(display, x, getTextPositions(display)[line] + y + 2, nodeStatus, -1, false, "online");
} else {
drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
drawNodes(display, x + BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line] + y + 2, nodeStatus, -1, false,
"online");
}
}
char uptimeStr[32] = "";
@@ -1353,7 +1358,8 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
getUptimeStr(millis(), "Up: ", uptimeStr, sizeof(uptimeStr));
}
if (!compactPanel) {
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr) - BASEUI_BODY_LR_MARGIN,
getTextPositions(display)[line++] + y, uptimeStr);
} else {
line++;
}
@@ -1362,7 +1368,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
config.display.heading_bold = false;
#if HAS_GPS
UIRenderer::drawGps(display, x, getTextPositions(display)[line], gpsStatus, compactPanel);
UIRenderer::drawGps(display, x + BASEUI_BODY_LR_MARGIN, getTextPositions(display)[line] + y, gpsStatus, compactPanel);
#endif
#if defined(OLED_TINY)
@@ -1374,7 +1380,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
char chUtilStr[16];
snprintf(chUtilStr, sizeof(chUtilStr), "ChUtil %d%%", chutil_percent);
int chUtilWidth = display->getStringWidth(chUtilStr);
display->drawString((SCREEN_WIDTH - chUtilWidth) / 2, getTextPositions(display)[line++], chUtilStr);
display->drawString((SCREEN_WIDTH - chUtilWidth) / 2, getTextPositions(display)[line++] + y, chUtilStr);
// === Node Identity: long name (falls back to short), truncated with "..." if too wide ===
const char *longName = (nodeInfoLiteHasUser(ourNode) && ourNode->long_name[0]) ? ourNode->long_name : "";
@@ -1384,14 +1390,14 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
UIRenderer::truncateStringWithEmotes(display, rawName, nodeName, sizeof(nodeName), SCREEN_WIDTH - 4);
int textWidth = UIRenderer::measureStringWithEmotes(display, nodeName);
int nameX = (SCREEN_WIDTH - textWidth) / 2;
UIRenderer::drawStringWithEmotes(display, nameX, getTextPositions(display)[line++], nodeName, FONT_HEIGHT_SMALL, 1,
UIRenderer::drawStringWithEmotes(display, nameX, getTextPositions(display)[line++] + y, nodeName, FONT_HEIGHT_SMALL, 1,
false);
} else {
// === Node Identity ===
const char *shortName = owner.short_name[0] ? owner.short_name : "";
int textWidth = UIRenderer::measureStringWithEmotes(display, shortName);
int nameX = (SCREEN_WIDTH - textWidth) / 2;
UIRenderer::drawStringWithEmotes(display, nameX, getTextPositions(display)[line++], shortName, FONT_HEIGHT_SMALL, 1,
UIRenderer::drawStringWithEmotes(display, nameX, getTextPositions(display)[line++] + y, shortName, FONT_HEIGHT_SMALL, 1,
false);
}
#else
@@ -1400,9 +1406,11 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
int batV = powerStatus->getBatteryVoltageMv() / 1000;
int batCv = (powerStatus->getBatteryVoltageMv() % 1000) / 10;
snprintf(batStr, sizeof(batStr), "%01d.%02dV", batV, batCv);
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(batStr), getTextPositions(display)[line++], batStr);
display->drawString(x + SCREEN_WIDTH - BASEUI_BODY_LR_MARGIN - display->getStringWidth(batStr),
getTextPositions(display)[line++] + y, batStr);
} else {
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("USB"), getTextPositions(display)[line++], "USB");
display->drawString(x + SCREEN_WIDTH - BASEUI_BODY_LR_MARGIN - display->getStringWidth("USB"),
getTextPositions(display)[line++] + y, "USB");
}
config.display.heading_bold = origBold;
@@ -1413,9 +1421,8 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
int chutil_percent = static_cast<int>(airTime->channelUtilizationPercent() + 0.5f);
snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%d%%", chutil_percent);
int chUtil_x = (currentResolution == ScreenResolution::High) ? display->getStringWidth(chUtil) + 10
: display->getStringWidth(chUtil) + 5;
int chUtil_y = getTextPositions(display)[line] + 3;
int chUtil_width = display->getStringWidth(chUtil);
int chUtil_y = getTextPositions(display)[line] + 3 + y;
int chutil_bar_width = (currentResolution == ScreenResolution::High) ? 100 : 50;
int chutil_bar_max_fill = chutil_bar_width - 2; // Account for border
@@ -1433,10 +1440,15 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
}
const int raw_chutil_percent = chutil_percent;
// With BT disabled we pin this row left to make room for the extra "BT off" indicator.
const int starting_position = config.bluetooth.enabled ? x : 0;
// Center the row; with BT disabled reserve the width of the extra "BT off" indicator.
int starting_position =
(SCREEN_WIDTH - chUtil_width - chutil_bar_width - extraoffset - display->getStringWidth(chUtilPercentage));
if (!config.bluetooth.enabled) {
starting_position -= (display->getStringWidth("BT off") + extraoffset);
}
starting_position /= 2;
display->drawString(starting_position, getTextPositions(display)[line], chUtil);
display->drawString(starting_position, getTextPositions(display)[line] + y, chUtil);
// Force 61% or higher to show a full 100% bar, text would still show related percent.
if (chutil_percent >= 61) {
@@ -1446,7 +1458,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
int fillRight = computeChannelUtilizationFill(chutil_percent, chutil_bar_max_fill);
// Draw outline
display->drawRect(starting_position + chUtil_x, chUtil_y, chutil_bar_width, chutil_bar_height);
display->drawRect(starting_position + chUtil_width, chUtil_y, chutil_bar_width, chutil_bar_height);
// Fill progress
if (fillRight > 0) {
@@ -1458,16 +1470,18 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
UtilizationFillColor = TFTPalette::Medium;
}
setAndRegisterTFTColorRole(TFTColorRole::UtilizationFill, UtilizationFillColor, TFTPalette::Black,
starting_position + chUtil_x + 1, chUtil_y + 1, fillRight, chutil_bar_height - 2);
starting_position + chUtil_width + 1, chUtil_y + 1, fillRight, chutil_bar_height - 2);
#endif
display->fillRect(starting_position + chUtil_x + 1, chUtil_y + 1, fillRight, chutil_bar_height - 2);
display->fillRect(starting_position + chUtil_width + 1, chUtil_y + 1, fillRight, chutil_bar_height - 2);
}
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line],
display->drawString(starting_position + chUtil_width + chutil_bar_width + extraoffset, getTextPositions(display)[line] + y,
chUtilPercentage);
if (!config.bluetooth.enabled) {
display->drawString(SCREEN_WIDTH - display->getStringWidth("BT off"), getTextPositions(display)[line], "BT off");
display->drawString(starting_position + chUtil_width + chutil_bar_width + extraoffset +
display->getStringWidth(chUtilPercentage) + extraoffset,
getTextPositions(display)[line] + y, "BT off");
}
line += 1;
@@ -1491,21 +1505,28 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
if (SCREEN_WIDTH - UIRenderer::measureStringWithEmotes(display, combinedName) > 10) {
textWidth = UIRenderer::measureStringWithEmotes(display, combinedName);
nameX = (SCREEN_WIDTH - textWidth) / 2;
UIRenderer::drawStringWithEmotes(display, nameX, getTextPositions(display)[line++] + yOffset, combinedName,
UIRenderer::drawStringWithEmotes(display, nameX, getTextPositions(display)[line++] + yOffset + y, combinedName,
FONT_HEIGHT_SMALL, 1, false);
} else {
// === LongName Centered ===
textWidth = UIRenderer::measureStringWithEmotes(display, longName);
nameX = (SCREEN_WIDTH - textWidth) / 2;
UIRenderer::drawStringWithEmotes(display, nameX, getTextPositions(display)[line++], longName, FONT_HEIGHT_SMALL, 1,
UIRenderer::drawStringWithEmotes(display, nameX, getTextPositions(display)[line++] + y, longName, FONT_HEIGHT_SMALL, 1,
false);
// === ShortName Centered ===
textWidth = UIRenderer::measureStringWithEmotes(display, shortName);
nameX = (SCREEN_WIDTH - textWidth) / 2;
UIRenderer::drawStringWithEmotes(display, nameX, getTextPositions(display)[line++], shortName, FONT_HEIGHT_SMALL, 1,
UIRenderer::drawStringWithEmotes(display, nameX, getTextPositions(display)[line++] + y, shortName, FONT_HEIGHT_SMALL, 1,
false);
}
#ifdef SHOW_STEP_COUNTER
std::string stepsLine = "Steps: " + std::to_string(screen->steps);
textWidth = UIRenderer::measureStringWithEmotes(display, stepsLine.c_str());
nameX = (SCREEN_WIDTH - textWidth) / 2;
UIRenderer::drawStringWithEmotes(display, nameX, getTextPositions(display)[line++] + y, stepsLine.c_str(), FONT_HEIGHT_SMALL,
1, false);
#endif
#endif
graphics::drawCommonFooter(display, x, y);
}
@@ -1776,6 +1797,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr);
y += BASEUI_BELOW_HEADER_MARGIN;
const int *textPos = getTextPositions(display);
const bool compactPanel = graphics::isCompactPanel(display);
@@ -1804,7 +1826,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
bool origBold = config.display.heading_bold;
config.display.heading_bold = false;
UIRenderer::drawGps(display, x, textPos[line++], gpsStatus, compactPanel);
UIRenderer::drawGps(display, x + BASEUI_BODY_LR_MARGIN, textPos[line++] + y, gpsStatus, compactPanel);
config.display.heading_bold = origBold;
@@ -1914,18 +1936,18 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
getUptimeStr(delta, "Last: ", uptimeStr, sizeof(uptimeStr), true);
#endif
display->drawString(0, textPos[line++], uptimeStr);
display->drawString(x + BASEUI_BODY_LR_MARGIN, textPos[line++] + y, uptimeStr);
} else {
display->drawString(0, textPos[line++], "Last: ?");
display->drawString(x + BASEUI_BODY_LR_MARGIN, textPos[line++] + y, "Last: ?");
}
// === Third Row: Line 1 GPS Info ===
UIRenderer::drawGpsCoordinates(display, x, textPos[line++], gpsStatus, "line1");
UIRenderer::drawGpsCoordinates(display, x, textPos[line++] + y, gpsStatus, "line1");
if (uiconfig.gps_format != meshtastic_DeviceUIConfig_GpsCoordinateFormat_OLC &&
uiconfig.gps_format != meshtastic_DeviceUIConfig_GpsCoordinateFormat_MLS) {
// === Fourth Row: Line 2 GPS Info ===
UIRenderer::drawGpsCoordinates(display, x, textPos[line++], gpsStatus, "line2");
UIRenderer::drawGpsCoordinates(display, x, textPos[line++] + y, gpsStatus, "line2");
}
// === Final Row: Altitude ===
@@ -1936,21 +1958,21 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
} else {
snprintf(altitudeLine, sizeof(altitudeLine), "Alt: %.0im", alt);
}
display->drawString(x, textPos[line++], altitudeLine);
display->drawString(x + BASEUI_BODY_LR_MARGIN, textPos[line++] + y, altitudeLine);
}
#if !defined(OLED_TINY)
// === Draw Compass ===
if (validHeading || statusLine1) {
// --- Compass Rendering: landscape (wide) screens use original side-aligned logic ---
if (SCREEN_WIDTH > SCREEN_HEIGHT) {
const int16_t topY = textPos[1];
const int16_t bottomY = SCREEN_HEIGHT - (FONT_HEIGHT_SMALL - 1); // nav row height
const int16_t topY = textPos[1] + y;
const int16_t bottomY = SCREEN_HEIGHT - (FONT_HEIGHT_SMALL - 1) - y; // nav row height
const int16_t usableHeight = bottomY - topY - 5;
int16_t compassRadius = usableHeight / 2;
if (compassRadius < 8)
compassRadius = 8;
const int16_t compassX = x + SCREEN_WIDTH - compassRadius - 8;
const int16_t compassX = x + BASEUI_BODY_LR_MARGIN + SCREEN_WIDTH - compassRadius - 8;
// Center vertically and nudge down slightly to keep "N" clear of header
const int16_t compassY = topY + (usableHeight / 2) + ((FONT_HEIGHT_SMALL - 1) / 2) + 2;
@@ -2085,7 +2107,11 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
lastFrameChangeTime = millis();
}
#ifdef OLED_HUGE
const int iconSize = 24;
#else
const int iconSize = (currentResolution == ScreenResolution::High) ? 16 : 8;
#endif
const int spacing = (currentResolution == ScreenResolution::High) ? 8 : 4;
const int bigOffset = (currentResolution == ScreenResolution::High) ? 1 : 0;
const bool compactPanel = graphics::isCompactPanel(display);
@@ -2153,7 +2179,11 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
}
#endif
#if BASEUI_HEADER_LR_MARGIN
const int navPadding = BASEUI_HEADER_LR_MARGIN;
#else
const int navPadding = compactPanel ? 8 : ((currentResolution == ScreenResolution::High) ? 24 : 12);
#endif
int usableWidth = SCREEN_WIDTH - (navPadding * 2);
if (usableWidth < iconSize)
@@ -2253,12 +2283,15 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
display->setColor(BLACK);
#endif
}
#ifdef OLED_HUGE
NodeListRenderer::drawScaledXBitmap3x(x, y, 8, 8, icon, display);
#else
if (currentResolution == ScreenResolution::High) {
NodeListRenderer::drawScaledXBitmap16x16(x, y, 8, 8, icon, display);
} else {
display->drawXbm(x, y, iconSize, iconSize, icon);
}
#endif
if (isActive) {
display->setColor(WHITE);
+4
View File
@@ -52,6 +52,10 @@ class UIRenderer
// though drawNavigationBar itself never ran while the screen (and its OSThread) was off.
static void notifyScreenWoke();
// screen frames
// First two pointers are self explanatory
// x and y are the offset everything should be drawn at, to support sliding transitions between frames.
static void drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
// Compact panels: toggle between compass+distance view and status/telemetry view
static void scrollFavoriteDown();
+1 -1
View File
@@ -192,7 +192,7 @@ int32_t TouchScreenBase::runOnce()
void TouchScreenBase::hapticFeedback()
{
#ifdef T_WATCH_S3
#if defined(T_WATCH_S3) || defined(T_WATCH_ULTRA)
drv.setWaveform(0, 75);
drv.setWaveform(1, 0); // end waveform
drv.go();
+2
View File
@@ -322,6 +322,8 @@ __attribute__((weak, noinline)) bool loopCanSleep()
__attribute__((noinline)) void lateInitVariant() __attribute__((weak));
__attribute__((noinline)) void lateInitVariant() {}
// earlyInitVariant() runs before consoleInit(): a LOG_* macro here CRASHES the device,
// it is not a silent no-op. Defer any logging to lateInitVariant() or later.
__attribute__((noinline)) void earlyInitVariant() __attribute__((weak));
__attribute__((noinline)) void earlyInitVariant() {}
+2 -2
View File
@@ -1033,7 +1033,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \
defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT) || defined(HELTEC_V4_R8_TFT) || defined(RAK_WISMESH_TAP_V2) || \
defined(ELECROW_ThinkNode_M9)) && \
defined(ELECROW_ThinkNode_M9) || defined(T_WATCH_ULTRA)) && \
HAS_TFT
// switch BT off by default; use TFT programming mode or hotkey to enable
config.bluetooth.enabled = false;
@@ -1117,7 +1117,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.display.wake_on_tap_or_motion = true;
#endif
#if defined(T_WATCH_S3) || defined(SENSECAP_INDICATOR)
#if defined(T_WATCH_S3) || defined(SENSECAP_INDICATOR) || defined(T_WATCH_ULTRA)
config.display.screen_on_secs = 30;
config.display.wake_on_tap_or_motion = true;
#endif
+42 -27
View File
@@ -211,10 +211,13 @@ void CannedMessageModule::drawHeader(OLEDDisplay *display, int16_t x, int16_t y,
snprintf(header, sizeof(header), "To: @%s", getNodeName(this->dest));
}
const int maxWidth = std::max(0, display->getWidth() - x);
// First row of text: inset horizontally by the header L/R margin and pushed down by the header margin
const int headerX = x + BASEUI_HEADER_LR_MARGIN;
const int headerY = y + BASEUI_HEADER_MARGIN;
const int maxWidth = std::max(0, display->getWidth() - headerX - BASEUI_HEADER_LR_MARGIN);
char truncatedHeader[96];
graphics::UIRenderer::truncateStringWithEmotes(display, header, truncatedHeader, sizeof(truncatedHeader), maxWidth);
graphics::UIRenderer::drawStringWithEmotes(display, x, y, truncatedHeader, FONT_HEIGHT_SMALL, 1, false);
graphics::UIRenderer::drawStringWithEmotes(display, headerX, headerY, truncatedHeader, FONT_HEIGHT_SMALL, 1, false);
}
void CannedMessageModule::resetSearch()
@@ -1475,7 +1478,9 @@ void CannedMessageModule::drawKeyboard(OLEDDisplay *display, OLEDDisplayUiState
{
int outerSize = *(&this->keyboard[this->charSet] + 1) - this->keyboard[this->charSet];
int xOffset = 0;
// Inset the key grid horizontally by the body L/R margin (keeps touch aligned since
// keyForCoordinates() reads the same per-key rects stored below)
int xOffset = BASEUI_BODY_LR_MARGIN;
int yOffset = 56;
@@ -1485,7 +1490,8 @@ void CannedMessageModule::drawKeyboard(OLEDDisplay *display, OLEDDisplayUiState
display->setColor(OLEDDISPLAY_COLOR::WHITE);
display->drawStringMaxWidth(0, 0, display->getWidth(),
// Free text being typed is the first row of text: inset by header margins
display->drawStringMaxWidth(BASEUI_HEADER_LR_MARGIN, BASEUI_HEADER_MARGIN, display->getWidth() - 2 * BASEUI_HEADER_LR_MARGIN,
cannedMessageModule->drawWithCursor(cannedMessageModule->freetext, cannedMessageModule->cursor));
display->setFont(FONT_MEDIUM);
@@ -1507,7 +1513,7 @@ void CannedMessageModule::drawKeyboard(OLEDDisplay *display, OLEDDisplayUiState
}
}
int cellWidth = display->width() / innerSize;
int cellWidth = (display->width() - 2 * BASEUI_BODY_LR_MARGIN) / innerSize;
for (int8_t innerIndex = 0; innerIndex < innerSize; innerIndex++) {
xOffset += innerIndex > 0 ? cellWidth : 0;
@@ -1580,7 +1586,7 @@ void CannedMessageModule::drawKeyboard(OLEDDisplay *display, OLEDDisplayUiState
}
}
xOffset = 0;
xOffset = BASEUI_BODY_LR_MARGIN;
}
this->highlight = 0x00;
@@ -1670,8 +1676,8 @@ void CannedMessageModule::drawDestinationSelectionScreen(OLEDDisplay *display, O
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
// Header
int titleY = 2;
// Header (first row): pushed down by the header margin; centered, so no L/R inset needed
int titleY = 2 + BASEUI_HEADER_MARGIN;
String titleText = "Select Destination";
titleText += searchQuery.length() > 0 ? " [" + searchQuery + "]" : " [ ]";
display->setTextAlignment(TEXT_ALIGN_CENTER);
@@ -1723,7 +1729,7 @@ void CannedMessageModule::drawDestinationSelectionScreen(OLEDDisplay *display, O
}
}
int availWidth = display->getWidth() -
int availWidth = display->getWidth() - 2 * BASEUI_BODY_LR_MARGIN -
((graphics::currentResolution == graphics::ScreenResolution::High) ? 40 : 20) -
((nodeInfoLiteIsFavorite(node)) ? 10 : 0);
if (availWidth < 0)
@@ -1749,12 +1755,14 @@ void CannedMessageModule::drawDestinationSelectionScreen(OLEDDisplay *display, O
// Highlight background (if selected)
if (itemIndex == destIndex) {
int scrollPadding = 8; // Reserve space for scrollbar
display->fillRect(0, yOffset + 2, display->getWidth() - scrollPadding, FONT_HEIGHT_SMALL - 5);
display->fillRect(BASEUI_BODY_LR_MARGIN, yOffset + 2, display->getWidth() - scrollPadding - 2 * BASEUI_BODY_LR_MARGIN,
FONT_HEIGHT_SMALL - 5);
display->setColor(BLACK);
}
// Draw entry text
graphics::UIRenderer::drawStringWithEmotes(display, xOffset + 2, yOffset, entryText.c_str(), FONT_HEIGHT_SMALL, 1, false);
graphics::UIRenderer::drawStringWithEmotes(display, xOffset + 2 + BASEUI_BODY_LR_MARGIN, yOffset, entryText.c_str(),
FONT_HEIGHT_SMALL, 1, false);
display->setColor(WHITE);
// Draw key icon (after highlight)
@@ -1783,7 +1791,7 @@ void CannedMessageModule::drawDestinationSelectionScreen(OLEDDisplay *display, O
if (totalEntries > visibleRows) {
int scrollbarHeight = visibleRows * (FONT_HEIGHT_SMALL - 4);
int totalScrollable = totalEntries;
int scrollTrackX = display->getWidth() - 6;
int scrollTrackX = display->getWidth() - 6 - BASEUI_BODY_LR_MARGIN;
display->drawRect(scrollTrackX, rowYOffset, 4, scrollbarHeight);
int scrollHeight = (scrollbarHeight * visibleRows) / totalScrollable;
int scrollPos = rowYOffset + (scrollbarHeight * scrollIndex) / totalScrollable;
@@ -1801,8 +1809,8 @@ void CannedMessageModule::drawEmotePickerScreen(OLEDDisplay *display, OLEDDispla
const int maxEmoteHeight = graphics::EmoteRenderer::maxEmoteHeight();
const int rowHeight = maxEmoteHeight + 2;
// Place header at top, then compute start of emote list
int headerY = y;
// Place header at top (pushed down by the header margin), then compute start of emote list
int headerY = y + BASEUI_HEADER_MARGIN;
int listTop = headerY + headerFontHeight + headerMargin;
int _visibleRows = (display->getHeight() - listTop - 2) / rowHeight;
@@ -1841,12 +1849,13 @@ void CannedMessageModule::drawEmotePickerScreen(OLEDDisplay *display, OLEDDispla
// Draw highlight box 2px taller than emote (1px margin above and below)
if (emoteIdx == emotePickerIndex) {
display->fillRect(x, rowY, display->getWidth() - 8, emote.height + 2);
display->fillRect(x + BASEUI_BODY_LR_MARGIN, rowY, display->getWidth() - 8 - 2 * BASEUI_BODY_LR_MARGIN,
emote.height + 2);
display->setColor(BLACK);
}
// Emote bitmap (left), centered inside the row
int labelStartX = x + bitmapGapX;
int labelStartX = x + BASEUI_BODY_LR_MARGIN + bitmapGapX;
const int emoteY = rowY + ((rowHeight - emote.height) / 2);
display->drawXbm(labelStartX, emoteY, emote.width, emote.height, emote.bitmap);
labelStartX += emote.width;
@@ -1863,7 +1872,7 @@ void CannedMessageModule::drawEmotePickerScreen(OLEDDisplay *display, OLEDDispla
// Draw scrollbar if needed
if (numEmotes > _visibleRows) {
int scrollbarHeight = _visibleRows * rowHeight;
int scrollTrackX = display->getWidth() - 6;
int scrollTrackX = display->getWidth() - 6 - BASEUI_BODY_LR_MARGIN;
display->drawRect(scrollTrackX, listTop, 4, scrollbarHeight);
int scrollBarLen = std::max(6, (scrollbarHeight * _visibleRows) / numEmotes);
int scrollBarPos = listTop + (scrollbarHeight * topIndex) / numEmotes;
@@ -1900,7 +1909,8 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
if (this->runState == CANNED_MESSAGE_RUN_STATE_DISABLED) {
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
display->drawString(10 + x, 0 + y + FONT_HEIGHT_SMALL, "Canned Message\nModule disabled.");
display->drawString(10 + x + BASEUI_BODY_LR_MARGIN, y + FONT_HEIGHT_SMALL + BASEUI_HEADER_MARGIN,
"Canned Message\nModule disabled.");
return;
}
@@ -1927,7 +1937,8 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
uint16_t charsLeft =
meshtastic_Constants_DATA_PAYLOAD_LEN - this->freetext.length() - (moduleConfig.canned_message.send_bell ? 1 : 0);
snprintf(buffer, sizeof(buffer), "%d left", charsLeft);
display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer);
display->drawString(x + display->getWidth() - display->getStringWidth(buffer) - BASEUI_HEADER_LR_MARGIN,
y + BASEUI_HEADER_MARGIN, buffer);
}
#if INPUTBROKER_SERIAL_TYPE == 1
@@ -2014,9 +2025,11 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
// Draw Free Text input with multi-emote support and proper line wrapping
display->setColor(WHITE);
{
int inputY = 0 + y + FONT_HEIGHT_SMALL;
int inputY = y + FONT_HEIGHT_SMALL + BASEUI_HEADER_MARGIN;
int inputX = x + BASEUI_BODY_LR_MARGIN;
String msgWithCursor = this->drawWithCursor(this->freetext, this->cursor);
drawWrappedEmoteText(display, x, inputY, msgWithCursor.c_str(), display->getWidth() - x, FONT_HEIGHT_SMALL);
drawWrappedEmoteText(display, inputX, inputY, msgWithCursor.c_str(),
display->getWidth() - inputX - BASEUI_BODY_LR_MARGIN, FONT_HEIGHT_SMALL);
}
#endif
return;
@@ -2037,7 +2050,8 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
drawHeader(display, x, y, buffer);
// Shift message list upward by 3 pixels to reduce spacing between header and first message
const int listYOffset = y + FONT_HEIGHT_SMALL - 3;
// Push the list below the header margin so the body starts clear of the reserved top area
const int listYOffset = y + FONT_HEIGHT_SMALL - 3 + BASEUI_HEADER_MARGIN;
_visibleRows = (display->getHeight() - listYOffset) / baseRowSpacing;
// Figure out which messages are visible and their needed heights
@@ -2059,16 +2073,17 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
int textYOffset = (rowHeight - FONT_HEIGHT_SMALL) / 2;
#ifdef USE_EINK
int nextX = x + (_highlight ? 12 : 0);
int nextX = x + BASEUI_BODY_LR_MARGIN + (_highlight ? 12 : 0);
if (_highlight)
display->drawString(x + 0, lineY + textYOffset, ">");
display->drawString(x + BASEUI_BODY_LR_MARGIN, lineY + textYOffset, ">");
#else
int scrollPadding = 8;
if (_highlight) {
display->fillRect(x + 0, lineY, display->getWidth() - scrollPadding, rowHeight);
display->fillRect(x + BASEUI_BODY_LR_MARGIN, lineY,
display->getWidth() - scrollPadding - 2 * BASEUI_BODY_LR_MARGIN, rowHeight);
display->setColor(BLACK);
}
int nextX = x + (_highlight ? 2 : 0);
int nextX = x + BASEUI_BODY_LR_MARGIN + (_highlight ? 2 : 0);
#endif
if (msg && *msg)
@@ -2084,7 +2099,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
// Scrollbar
if (messagesCount > _visibleRows) {
int scrollHeight = display->getHeight() - listYOffset;
int scrollTrackX = display->getWidth() - 6;
int scrollTrackX = display->getWidth() - 6 - BASEUI_BODY_LR_MARGIN;
display->drawRect(scrollTrackX, listYOffset, 4, scrollHeight);
int barHeight = (scrollHeight * _visibleRows) / messagesCount;
int scrollPos = listYOffset + (scrollHeight * topMsg) / messagesCount;
+1 -1
View File
@@ -71,7 +71,7 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
if (config.position.fixed_position) {
LOG_DEBUG("Ignore own position update except time: position.fixed_position true");
#ifdef T_WATCH_S3
#if defined(T_WATCH_S3) || defined(T_WATCH_ULTRA)
// Since we return early if position.fixed_position is true, set the T-Watch's RTC to the time received from the
// client device here
if (p.time && channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) {
+8
View File
@@ -30,6 +30,9 @@
#ifdef HAS_STK8XXX
#include "STK8XXXSensor.h"
#endif
#ifdef HAS_BHI260AP
#include "BHI260APSensor.h"
#endif
extern ScanI2C::DeviceAddress accelerometer_found;
@@ -152,6 +155,11 @@ class AccelerometerThread : public concurrency::OSThread
case ScanI2C::DeviceType::QMI8658:
sensor.reset(new QMI8658Sensor(device));
break;
#endif
#ifdef HAS_BHI260AP
case ScanI2C::DeviceType::BHI260AP:
sensor.reset(new BHI260APSensor(device));
break;
#endif
default:
disable();
+80
View File
@@ -0,0 +1,80 @@
#include "BHI260APSensor.h"
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && defined(HAS_BHI260AP) && __has_include(<SensorBHI260AP.hpp>)
#define BOSCH_BHI260_KLIO
#define USING_DATA_HELPER
#include <BoschFirmware.h>
BHI260APSensor::BHI260APSensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {}
// https://github.com/lewisxhe/SensorLib/blob/master/examples/Sensors/IMU/BHI260AP_InterruptSettings/BHI260AP_InterruptSettings.ino
bool BHI260APSensor::init()
{
LOG_WARN("Initializing BHI260AP sensor %u", deviceAddress());
sensor.setFirmware(bosch_firmware_image, bosch_firmware_size, bosch_firmware_type);
sensor.setBootFromFlash(bosch_firmware_type);
if (sensor.begin(Wire, deviceAddress())) {
sensor.setRemapAxes(SensorBHI260AP::TOP_LAYER_BOTTOM_RIGHT_CORNER);
BoschSensorInfo info = sensor.getSensorInfo();
LOG_INFO("Product ID : %02x\n", info.product_id);
LOG_INFO("Kernel version : %04u\n", info.kernel_version);
LOG_INFO("User version : %04u\n", info.user_version);
LOG_INFO("ROM version : %04u\n", info.rom_version);
LOG_INFO("Power state : %s\n", (info.host_status & BHY2_HST_POWER_STATE) ? "sleeping" : "active");
LOG_INFO("Host interface : %s\n", (info.host_status & BHY2_HST_HOST_PROTOCOL) ? "SPI" : "I2C");
LOG_INFO("Feature status : 0x%02x\n", info.feat_status);
stepCounter = new SensorStepCounter(sensor);
// stepDetector = new SensorStepDetector(sensor);
// sensor.configAccelerometer(sensor.RANGE_2G, sensor.ODR_100HZ, sensor.BW_NORMAL_AVG4, sensor.PERF_CONTINUOUS_MODE);
// sensor.enableAccelerometer();
// sensor.configInterrupt();
#ifdef BHI260AP_INT
pinMode(BHI260AP_INT, INPUT);
attachInterrupt(
BHI260AP_INT,
[] {
// Set interrupt to set irq value to true
},
RISING); // Select the interrupt mode according to the actual circuit
#endif
#ifdef T_WATCH_S3
// Need to raise the wrist function, need to set the correct axis
sensor.setRemapAxes(sensor.REMAP_TOP_LAYER_RIGHT_CORNER);
#else
// sensor.setRemapAxes(sensor.REMAP_BOTTOM_LAYER_BOTTOM_LEFT_CORNER);
#endif
// stepDetector->enable(1.0, 0);
stepCounter->enable(1.0, 0);
LOG_DEBUG("BHI260AP init ok");
return true;
}
LOG_DEBUG("BHI260AP init failed");
return false;
}
int32_t BHI260APSensor::runOnce()
{
sensor.update();
if (stepCounter->hasUpdated()) {
steps = stepCounter->getStepCount();
LOG_WARN("Step count updated: %u", steps);
if (screen)
screen->steps = steps;
}
// LOG_WARN("Step count: %u", stepCounter->getStepCount());
// if (sensor.readIrqStatus()) {
// if (sensor.isTilt() || sensor.isDoubleTap()) {
// wakeScreen();
// return 500;
// }
//}
return 1000;
}
#endif
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#ifndef _BHI260AP_SENSOR_H_
#define _BHI260AP_SENSOR_H_
#include "MotionSensor.h"
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && defined(HAS_BHI260AP) && __has_include(<SensorBHI260AP.hpp>)
// Sensor lib
#include <SensorBHI260AP.hpp>
#include <Wire.h>
#include <bosch/BoschSensorDataHelper.hpp>
class BHI260APSensor : public MotionSensor
{
private:
SensorBHI260AP sensor;
volatile bool BHI_IRQ = false;
SensorStepCounter *stepCounter;
SensorStepDetector *stepDetector;
uint32_t steps = 0;
public:
explicit BHI260APSensor(ScanI2C::FoundDevice foundDevice);
virtual bool init() override;
virtual int32_t runOnce() override;
};
#endif
#endif
+2
View File
@@ -208,6 +208,8 @@
#define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L
#elif defined(HELTEC_WIRELESS_TRACKER_V2)
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2
#elif defined(T_WATCH_ULTRA)
#define HW_VENDOR meshtastic_HardwareModel_T_WATCH_ULTRA
#elif defined(M5STACK_CARDPUTER_ADV)
#define HW_VENDOR meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV
#elif defined(MESHNOLOGY_W10)
@@ -0,0 +1,42 @@
// Workaround for the IDF 5.5 manual esp_flash read regression on t-watch-ultra.
//
// On this board (Winbond W25Q128JW, ef:8018), the IDF 5.5 *direct* flash read path
// (esp_flash_read / esp_partition_read) returns 0x00 for data that is physically
// correct on flash. We proved the *memory-mapped* (cache) read returns the right
// data, writes work, and it's not read-mode/HPM/timing-tuning/PSRAM. So route every
// esp_partition_read through esp_partition_mmap + memcpy, which uses the working
// cache path. Activated by `-Wl,--wrap=esp_partition_read` (t-watch-ultra only).
#if defined(T_WATCH_ULTRA)
#include "esp_partition.h"
#include <string.h>
extern esp_err_t __real_esp_partition_read(const esp_partition_t *partition, size_t src_offset, void *dst, size_t size);
esp_err_t __wrap_esp_partition_read(const esp_partition_t *partition, size_t src_offset, void *dst, size_t size)
{
if (partition == NULL || dst == NULL)
return ESP_ERR_INVALID_ARG;
if (size == 0)
return ESP_OK;
// mmap requires a 64KB-aligned start; map the containing page span and copy
// out from the requested offset.
const size_t PAGE = 0x10000;
size_t aligned = src_offset & ~(PAGE - 1);
size_t delta = src_offset - aligned;
const void *ptr = NULL;
esp_partition_mmap_handle_t handle;
esp_err_t err = esp_partition_mmap(partition, aligned, delta + size, ESP_PARTITION_MMAP_DATA, &ptr, &handle);
if (err != ESP_OK) {
// Encrypted partitions / regions mmap can't serve: fall back to the real
// read (may be wrong on this board, but better than failing the call).
return __real_esp_partition_read(partition, src_offset, dst, size);
}
memcpy(dst, (const uint8_t *)ptr + delta, size);
esp_partition_munmap(handle);
return ESP_OK;
}
#endif // T_WATCH_ULTRA
+2
View File
@@ -7,6 +7,8 @@ This directory tree is designed to solve two problems.
So we are borrowing the initVariant() ideas here (by using weak gcc references). You can now define earlyInitVariant() and lateInitVariant() if your board needs them. earlyInitVariant() runs at the beginning of setup() directly after waitUntilPowerLevelSafe(); while lateInitVariant() runs after the LoRa radio is initialized.
**Important:** earlyInitVariant() runs _before_ consoleInit(), so the logging subsystem isn't set up yet. Calling a `LOG_*` macro there **crashes the device** - it is not a silent no-op. Never use `LOG_*` in earlyInitVariant(); defer any logging to lateInitVariant() or later.
If you'd like a board specific variant to be run, add the variant.cpp file to an appropriately named
subdirectory and check for \_VARIANT_boardname in the cpp file (so that your code is only built for your board).
You'll need to define \_VARIANT_boardname in your corresponding variant.h file.
@@ -0,0 +1,83 @@
#include "configuration.h"
#ifdef T_WATCH_ULTRA
// Board-specific init lives here (rather than in variants/esp32s3/t-watch-ultra/variant.cpp)
// so that PlatformIO's library dependency finder can resolve headers such as
// input/TouchScreenImpl1.h (which transitively pulls in the ArduinoThread "Thread.h"),
// ExtensionIOXL9555.hpp and TouchDrvCSTXXX.hpp. Files compiled from outside src/ only get
// include paths for libraries they reference directly, so the transitive Thread.h include
// is not found there. See src/platform/extra_variants/README.md.
#include "TouchDrvCSTXXX.hpp"
#include "input/TouchScreenImpl1.h"
#include <ExtensionIOXL9555.hpp>
#include <Wire.h>
static ExtensionIOXL9555 io;
static TouchDrvCST92xx touchDrv;
void earlyInitVariant()
{
pinMode(LORA_CS, OUTPUT);
digitalWrite(LORA_CS, HIGH);
pinMode(DISP_CS, OUTPUT);
digitalWrite(DISP_CS, HIGH);
pinMode(SDCARD_CS, OUTPUT);
digitalWrite(SDCARD_CS, HIGH);
pinMode(NFC_CS, OUTPUT);
digitalWrite(NFC_CS, HIGH);
pinMode(I2C_SDA, INPUT_PULLUP);
pinMode(I2C_SCL, INPUT_PULLUP);
if (io.begin(Wire, XL9555_SLAVE_ADDRESS0)) {
io.pinMode(EXPANDS_DRV_EN, OUTPUT);
io.digitalWrite(EXPANDS_DRV_EN, HIGH);
delay(1);
io.pinMode(EXPANDS_DISP_EN, OUTPUT);
io.digitalWrite(EXPANDS_DISP_EN, HIGH);
delay(1);
io.pinMode(EXPANDS_TOUCH_RST, OUTPUT);
io.digitalWrite(EXPANDS_TOUCH_RST, LOW);
delay(20);
io.digitalWrite(EXPANDS_TOUCH_RST, HIGH);
delay(60);
io.pinMode(EXPANDS_LORA_RF_SW, OUTPUT);
io.digitalWrite(EXPANDS_LORA_RF_SW, HIGH); // set RF switch to built-in LoRa antenna
// io.pinMode(EXPANDS_SD_DET, INPUT);
}
// NOTE: deliberately no LOG_* on the io.begin() failure path. earlyInitVariant() runs
// before consoleInit(), where calling a LOG_* macro crashes the device (see
// extra_variants/README.md). On failure the EXPANDS_* pins stay on their defaults.
}
static bool readTouch(int16_t *x, int16_t *y)
{
int16_t x_array[1], y_array[1];
uint8_t touched = touchDrv.getPoint(x_array, y_array, 1);
if (touched > 0) {
*x = (x_array[0]);
*y = (y_array[0]);
// Check bounds
if (*x < 0 || *x >= TFT_WIDTH || *y < 0 || *y >= TFT_HEIGHT) {
return false;
}
return true; // Valid touch detected
}
return false; // No valid touch data
}
void lateInitVariant()
{
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
pinMode(SCREEN_TOUCH_INT, INPUT_PULLUP);
touchDrv.setPins(-1, SCREEN_TOUCH_INT);
if (touchDrv.begin(Wire, TOUCH_SLAVE_ADDRESS, -1, -1)) {
touchScreenImpl1 = new TouchScreenImpl1(TFT_WIDTH, TFT_HEIGHT, readTouch);
touchScreenImpl1->init();
} else {
LOG_ERROR("failed to initialize CST92xx");
}
}
}
#endif
+1 -1
View File
@@ -370,7 +370,7 @@ void doDeepSleep(uint32_t msecToWake, bool skipPreflight = false, bool skipSaveN
// t-beam v1.2 radio power channel
PMU->disablePowerOutput(XPOWERS_ALDO2); // lora radio power channel
} else if (HW_VENDOR == meshtastic_HardwareModel_LILYGO_TBEAM_S3_CORE ||
HW_VENDOR == meshtastic_HardwareModel_T_WATCH_S3) {
HW_VENDOR == meshtastic_HardwareModel_T_WATCH_S3 || HW_VENDOR == meshtastic_HardwareModel_T_WATCH_ULTRA) {
PMU->disablePowerOutput(XPOWERS_ALDO3); // lora radio power channel
}
} else if (model == XPOWERS_AXP192) {