mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-03 19:12:00 +10:00
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:
co-authored by
Jonathan Bennett
Thomas Göttgens
parent
389559bddb
commit
0b906b4d15
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"memory_type": "qio_qspi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"psram_type": "qio",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "t-watch-ultra"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth", "lora"],
|
||||
"debug": {
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "LilyGo T-Watch Ultra",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"require_upload_port": true,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.lilygo.cc/en-pl/products/t-watch-ultra",
|
||||
"vendor": "LilyGo"
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
// vibration motor
|
||||
#define PIN_VIBRATION 2
|
||||
#define HAS_DRV2605 1
|
||||
|
||||
// Have SPI interface SD card slot
|
||||
#define HAS_SDCARD
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#ifndef Pins_Arduino_h
|
||||
#define Pins_Arduino_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// #ifndef digitalPinToInterrupt
|
||||
// #define digitalPinToInterrupt(p) (((p) < 48) ? (p) : -1)
|
||||
// #endif
|
||||
|
||||
#define USB_VID 0x303a
|
||||
#define USB_PID 0x8227
|
||||
#define USB_MANUFACTURER "LILYGO"
|
||||
#define USB_PRODUCT "T-Watch-Ultra"
|
||||
|
||||
#define DISP_WIDTH 502
|
||||
#define DISP_HEIGHT 410
|
||||
|
||||
// QSPI interface display
|
||||
#define DISP_D0 (38)
|
||||
#define DISP_D1 (39)
|
||||
#define DISP_D2 (42)
|
||||
#define DISP_D3 (45)
|
||||
#define DISP_SCK (40)
|
||||
#define DISP_CS (41)
|
||||
#define DISP_RST (37)
|
||||
#define DISP_TE (6)
|
||||
|
||||
// Interrupt IO port
|
||||
#define TP_INT (12)
|
||||
#define RTC_INT (1)
|
||||
#define PMU_INT (7)
|
||||
#define NFC_INT (5)
|
||||
#define SENSOR_INT (8)
|
||||
#define NFC_CS (4)
|
||||
|
||||
// PDM microphone
|
||||
#define MIC_SCK (17)
|
||||
#define MIC_DAT (18)
|
||||
|
||||
// MAX98357A
|
||||
#define I2S_BCLK (9)
|
||||
#define I2S_WCLK (10)
|
||||
#define I2S_DOUT (11)
|
||||
|
||||
#define SD_CS (21)
|
||||
|
||||
// TX, RX pin connected to GPS
|
||||
static const uint8_t TX = 43;
|
||||
static const uint8_t RX = 44;
|
||||
|
||||
// BHI260,PCF85063,AXP2101,DRV2605L share I2C Bus
|
||||
static const uint8_t SDA = 3;
|
||||
static const uint8_t SCL = 2;
|
||||
|
||||
// Default sd cs pin
|
||||
static const uint8_t SS = SD_CS;
|
||||
static const uint8_t MOSI = 34;
|
||||
static const uint8_t MISO = 33;
|
||||
static const uint8_t SCK = 35;
|
||||
|
||||
#define GPS_TX (TX)
|
||||
#define GPS_RX (RX)
|
||||
#define GPS_PPS (13)
|
||||
|
||||
#define TP_SDA (SDA)
|
||||
#define TP_SCL (SCL)
|
||||
|
||||
// LoRa and SD card share SPI bus -> variant.h
|
||||
// #define LORA_SCK (SCK) // share spi bus
|
||||
// #define LORA_MISO (MISO) // share spi bus
|
||||
// #define LORA_MOSI (MOSI) // share spi bus
|
||||
// #define LORA_CS (36)
|
||||
// #define LORA_RST (47)
|
||||
// #define LORA_BUSY (48)
|
||||
// #define LORA_IRQ (14)
|
||||
|
||||
// External expansion chip IO definition
|
||||
#define EXPANDS_DRV_EN (6)
|
||||
#define EXPANDS_DISP_EN (7)
|
||||
#define EXPANDS_TOUCH_RST (8)
|
||||
#define EXPANDS_SD_DET (10)
|
||||
#define EXPANDS_LORA_RF_SW (11)
|
||||
|
||||
// Peripheral definition exists
|
||||
#define USING_XL9555_EXPANDS
|
||||
#define USING_PCM_AMPLIFIER
|
||||
#define USING_PDM_MICROPHONE
|
||||
#define USING_PMU_MANAGE
|
||||
#define USING_INPUT_DEV_TOUCHPAD
|
||||
#define USING_ST25R3916
|
||||
#define USING_BHI260_SENSOR
|
||||
#define HAS_SD_CARD_SOCKET
|
||||
|
||||
#endif /* Pins_Arduino_h */
|
||||
@@ -0,0 +1,96 @@
|
||||
; LilyGo T-Watch S3
|
||||
[env:t-watch-ultra]
|
||||
custom_meshtastic_hw_model = 114
|
||||
custom_meshtastic_hw_model_slug = T_WATCH_ULTRA
|
||||
custom_meshtastic_architecture = esp32-s3
|
||||
custom_meshtastic_actively_supported = true
|
||||
custom_meshtastic_support_level = 1
|
||||
custom_meshtastic_display_name = T-Watch Ultra
|
||||
custom_meshtastic_images = t-watch-ultra.svg
|
||||
custom_meshtastic_tags = LilyGo
|
||||
custom_meshtastic_requires_dfu = false
|
||||
custom_meshtastic_partition_scheme = 16MB
|
||||
custom_meshtastic_has_mui = true
|
||||
|
||||
extends = esp32s3_base
|
||||
board = t-watch-ultra
|
||||
board_level = release
|
||||
board_build.partitions = default_16MB.csv
|
||||
upload_protocol = esptool
|
||||
|
||||
custom_sdkconfig =
|
||||
${esp32s3_base.custom_sdkconfig}
|
||||
; Keep esp_littlefs buffers in internal RAM (off the shared-bus PSRAM).
|
||||
CONFIG_LITTLEFS_MALLOC_STRATEGY_INTERNAL=y
|
||||
CONFIG_SPI_FLASH_SHARE_SPI1_BUS=y
|
||||
|
||||
build_flags = ${esp32_base.build_flags} -Ivariants/esp32s3/t-watch-ultra
|
||||
; Route flash reads through the cache/mmap path (esp_partition_read_mmap_wrap.c)
|
||||
; to dodge the IDF 5.5 manual-read regression on this board's flash.
|
||||
-Wl,--wrap=esp_partition_read
|
||||
-D T_WATCH_ULTRA
|
||||
-D RADIOLIB_EXCLUDE_SX128X=1
|
||||
-D RADIOLIB_EXCLUDE_SX127X=1
|
||||
-D RADIOLIB_EXCLUDE_LR11X0=1
|
||||
-UMESHTASTIC_EXCLUDE_ACCELEROMETER
|
||||
-D HAS_SDCARD
|
||||
-D SDCARD_USE_SPI1
|
||||
-D SD_SPI_FREQUENCY=75000000
|
||||
-D SPI_MISO=33
|
||||
-D SPI_MOSI=34
|
||||
-D SPI_SCK=35
|
||||
-D SDCARD_CS=21
|
||||
; -DHAS_BMA423=1
|
||||
|
||||
build_src_filter =
|
||||
${esp32s3_base.build_src_filter}
|
||||
+<../variants/esp32s3/t-watch-ultra>
|
||||
|
||||
lib_deps = ${esp32s3_base.lib_deps}
|
||||
https://github.com/lovyan03/LovyanGFX/archive/tags/1.2.27.zip
|
||||
adafruit/Adafruit DRV2605 Library@^1.2.4
|
||||
# renovate: datasource=git-refs depName=ESP8266Audio packageName=https://github.com/meshtastic/ESP8266Audio gitBranch=meshtastic-2.0.0-dacfix
|
||||
https://github.com/earlephilhower/ESP8266Audio/archive/05f2fb0045cc294b4e0d1a1a9747b89c22c1fea4.zip
|
||||
# renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM
|
||||
earlephilhower/ESP8266SAM@1.1.0
|
||||
lewisxhe/SensorLib@0.3.1
|
||||
|
||||
[env:t-watch-ultra-tft]
|
||||
board_level = extra
|
||||
extends = env:t-watch-ultra
|
||||
build_flags =
|
||||
${env:t-watch-ultra.build_flags}
|
||||
-D CONFIG_DISABLE_HAL_LOCKS=1
|
||||
-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=410
|
||||
-D LGFX_SCREEN_HEIGHT=502
|
||||
-D LGFX_AMOLED_ROUNDER=1
|
||||
-D LGFX_BUFSIZE=308732
|
||||
-D DISPLAY_SIZE=410x502 ; portrait mode
|
||||
-D DISPLAY_SET_RESOLUTION
|
||||
-D LGFX_DRIVER=LGFX_TWATCH_ULTRA
|
||||
-D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_T_WATCH_ULTRA.h\"
|
||||
; -D LVGL_DRIVER=LVGL_T_WATCH_ULTRA
|
||||
-D VIEW_320x240
|
||||
-D USE_PACKET_API
|
||||
-D MAP_FULL_REDRAW
|
||||
-D CUSTOM_TOUCH_DRIVER
|
||||
|
||||
lib_deps =
|
||||
${env:t-watch-ultra.lib_deps}
|
||||
${device-ui_base.lib_deps}
|
||||
@@ -0,0 +1,101 @@
|
||||
|
||||
// CO5300 TFT AMOLED
|
||||
#define CO5300_CS 41
|
||||
#define CO5300_SCK 40
|
||||
#define CO5300_RESET 37
|
||||
#define CO5300_TE 6
|
||||
#define CO5300_IO0 38
|
||||
#define CO5300_IO1 39
|
||||
#define CO5300_IO2 42
|
||||
#define CO5300_IO3 45
|
||||
#define CO5300_SPI_HOST SPI2_HOST
|
||||
#define SPI_FREQUENCY 75000000
|
||||
#define SPI_READ_FREQUENCY 16000000 // irrelevant
|
||||
#define TFT_HEIGHT 502
|
||||
#define TFT_WIDTH 410
|
||||
#define TFT_OFFSET_X 22
|
||||
#define TFT_OFFSET_Y 0
|
||||
#define TFT_OFFSET_ROTATION 0
|
||||
#define SCREEN_TRANSITION_FRAMERATE 5 // fps
|
||||
#define USE_TFTDISPLAY 1
|
||||
#define HAS_SCREEN 1
|
||||
#define TFT_RESET_AFTER_SLEEP
|
||||
#define OLED_HUGE
|
||||
#define ROUNDED_SCREEN true
|
||||
#define BASEUI_HEADER_MARGIN 15
|
||||
#define BASEUI_HEADER_LR_MARGIN 55
|
||||
#define BASEUI_BELOW_HEADER_MARGIN 15
|
||||
#define BASEUI_BODY_LR_MARGIN 35
|
||||
|
||||
#define HAS_TOUCHSCREEN 1
|
||||
#define HAS_SPI_TFT 1
|
||||
#define ENABLE_TOUCH_INT 1
|
||||
#define VARIANT_TOUCHSCREEN 1
|
||||
#define SCREEN_TOUCH_INT 12
|
||||
#define TOUCH_I2C_PORT 0
|
||||
#define TOUCH_SLAVE_ADDRESS 0x1A
|
||||
#define WAKE_ON_TOUCH
|
||||
|
||||
#define BUTTON_PIN 0
|
||||
|
||||
#define USE_POWERSAVE
|
||||
#define SLEEP_TIME 120
|
||||
|
||||
// External expansion chip XL9555
|
||||
#define USE_XL9555
|
||||
|
||||
// PCF85063 RTC Module
|
||||
#define PCF85063_RTC 0x51
|
||||
#define HAS_RTC 1
|
||||
|
||||
// MAX98357A
|
||||
#define HAS_I2S
|
||||
#define DAC_I2S_BCK 9
|
||||
#define DAC_I2S_WS 10
|
||||
#define DAC_I2S_DOUT 11
|
||||
#define DAC_I2S_MCLK -1 // TODO
|
||||
|
||||
#define HAS_AXP2101
|
||||
#define PMU_IRQ 7
|
||||
#define PMU_POWER_BUTTON_IS_CANCEL
|
||||
#define HAS_DRV2605 1
|
||||
|
||||
#define HAS_BHI260AP
|
||||
#define BHI260AP_INT 8
|
||||
#undef MESHTASTIC_EXCLUDE_ACCELEROMETER
|
||||
#define SHOW_STEP_COUNTER
|
||||
|
||||
#define I2C_SDA 3
|
||||
#define I2C_SCL 2
|
||||
#define I2C_NO_RESCAN
|
||||
|
||||
#define HAS_GPS 1
|
||||
#define GPS_BAUDRATE 38400
|
||||
#define GPS_RX_PIN 44
|
||||
#define GPS_TX_PIN 43
|
||||
#define PIN_GPS_PPS 13
|
||||
|
||||
#define USE_SX1262
|
||||
// #define USE_SX1280
|
||||
#define HW_SPI1_DEVICE
|
||||
|
||||
#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
|
||||
|
||||
#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 1.8
|
||||
|
||||
#define USE_VIRTUAL_KEYBOARD 1
|
||||
#define DISPLAY_CLOCK_FRAME 1
|
||||
@@ -59,7 +59,7 @@ static const uint8_t A0 = PIN_A0;
|
||||
#define WIRE_INTERFACES_COUNT 1
|
||||
#define PIN_WIRE_SDA (0 + 26)
|
||||
#define PIN_WIRE_SCL (0 + 27)
|
||||
#define HAS_BHI260AP
|
||||
// #define HAS_BHI260AP ; lewisxhe/SensorLib too big for nrf52
|
||||
|
||||
#define TP_SER_IO (0 + 11)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user