mirror of
https://github.com/vicliu624/trail-mate.git
synced 2026-10-23 05:32:18 +00:00
Add T-Deck Pro board bring-up and sync pending UI fixes
This commit is contained in:
1 parent
f6a70d0860
commit
e2a586bc1d
19 files changed
+1560
-59
No files matched your search
@@ -194,7 +194,9 @@ set(trail_mate_idf_ui_shared_sources
|
||||
|
||||
set(trail_mate_idf_include_dirs
|
||||
"include"
|
||||
"${CMAKE_SOURCE_DIR}/platform/shared/include"
|
||||
"${CMAKE_SOURCE_DIR}/boards/tab5/include"
|
||||
"${CMAKE_SOURCE_DIR}/boards/t_display_p4/include"
|
||||
"${CMAKE_SOURCE_DIR}/modules/ui_shared/include"
|
||||
"${CMAKE_SOURCE_DIR}/modules/core_sys/include"
|
||||
"${CMAKE_SOURCE_DIR}/modules/core_chat/include"
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"partitions": "default_16MB.csv",
|
||||
"memory_type": "qio_qspi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "esp32s3"
|
||||
},
|
||||
"connectivity": ["wifi"],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "LilyGo T-Deck Pro (16M Flash 8M QSPI PSRAM)",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.lilygo.cc",
|
||||
"vendor": "LilyGo"
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace boards::tdeck_pro
|
||||
{
|
||||
|
||||
struct BoardProfile
|
||||
{
|
||||
struct ProductIdentity
|
||||
{
|
||||
const char* long_name;
|
||||
const char* short_name;
|
||||
const char* ble_name;
|
||||
};
|
||||
|
||||
struct I2cPins
|
||||
{
|
||||
int sda;
|
||||
int scl;
|
||||
};
|
||||
|
||||
struct SpiPins
|
||||
{
|
||||
int sck;
|
||||
int mosi;
|
||||
int miso;
|
||||
};
|
||||
|
||||
struct UartPins
|
||||
{
|
||||
int tx;
|
||||
int rx;
|
||||
uint32_t baud;
|
||||
};
|
||||
|
||||
struct EpdPins
|
||||
{
|
||||
int cs;
|
||||
int dc;
|
||||
int rst;
|
||||
int busy;
|
||||
};
|
||||
|
||||
struct TouchPins
|
||||
{
|
||||
int int_pin;
|
||||
int rst;
|
||||
uint8_t i2c_addr;
|
||||
};
|
||||
|
||||
struct KeyboardPins
|
||||
{
|
||||
int int_pin;
|
||||
int led;
|
||||
uint8_t i2c_addr;
|
||||
uint8_t rows;
|
||||
uint8_t cols;
|
||||
};
|
||||
|
||||
struct LoraPins
|
||||
{
|
||||
int cs;
|
||||
int busy;
|
||||
int rst;
|
||||
int irq;
|
||||
int enable;
|
||||
};
|
||||
|
||||
struct GpsPins
|
||||
{
|
||||
int enable;
|
||||
int pps;
|
||||
UartPins uart;
|
||||
};
|
||||
|
||||
struct MotionPins
|
||||
{
|
||||
int enable_1v8;
|
||||
int int_pin;
|
||||
uint8_t i2c_addr;
|
||||
};
|
||||
|
||||
struct SdPins
|
||||
{
|
||||
int cs;
|
||||
};
|
||||
|
||||
struct OptionalModulePins
|
||||
{
|
||||
bool has_a7682e;
|
||||
bool has_pcm512a;
|
||||
int a7682e_enable;
|
||||
int a7682e_pwrkey;
|
||||
int a7682e_rst;
|
||||
int a7682e_ri;
|
||||
int a7682e_dtr;
|
||||
UartPins a7682e_uart;
|
||||
int i2s_bclk;
|
||||
int i2s_lrc;
|
||||
int i2s_dout;
|
||||
};
|
||||
|
||||
ProductIdentity identity;
|
||||
I2cPins i2c;
|
||||
SpiPins spi;
|
||||
EpdPins epd;
|
||||
TouchPins touch;
|
||||
KeyboardPins keyboard;
|
||||
LoraPins lora;
|
||||
GpsPins gps;
|
||||
MotionPins motion;
|
||||
SdPins sd;
|
||||
OptionalModulePins optional;
|
||||
int motor_pin;
|
||||
int keyboard_default_brightness;
|
||||
int screen_width;
|
||||
int screen_height;
|
||||
};
|
||||
|
||||
inline BoardProfile makeA7682EProfile()
|
||||
{
|
||||
BoardProfile profile{};
|
||||
profile.identity.long_name = "Trail Mate T-Deck Pro A7682E";
|
||||
profile.identity.short_name = "TDP4G";
|
||||
profile.identity.ble_name = "TrailMate-TDeckPro-4G";
|
||||
|
||||
profile.i2c.sda = 13;
|
||||
profile.i2c.scl = 14;
|
||||
|
||||
profile.spi.sck = 36;
|
||||
profile.spi.mosi = 33;
|
||||
profile.spi.miso = 47;
|
||||
|
||||
profile.epd.cs = 34;
|
||||
profile.epd.dc = 35;
|
||||
profile.epd.rst = -1;
|
||||
profile.epd.busy = 37;
|
||||
|
||||
profile.touch.int_pin = 12;
|
||||
profile.touch.rst = 45;
|
||||
profile.touch.i2c_addr = 0x1A;
|
||||
|
||||
profile.keyboard.int_pin = 15;
|
||||
profile.keyboard.led = 42;
|
||||
profile.keyboard.i2c_addr = 0x34;
|
||||
profile.keyboard.rows = 4;
|
||||
profile.keyboard.cols = 10;
|
||||
|
||||
profile.lora.cs = 3;
|
||||
profile.lora.busy = 6;
|
||||
profile.lora.rst = 4;
|
||||
profile.lora.irq = 5;
|
||||
profile.lora.enable = 46;
|
||||
|
||||
profile.gps.enable = 39;
|
||||
profile.gps.pps = 1;
|
||||
profile.gps.uart.tx = 43;
|
||||
profile.gps.uart.rx = 44;
|
||||
profile.gps.uart.baud = 38400;
|
||||
|
||||
profile.motion.enable_1v8 = 38;
|
||||
profile.motion.int_pin = 21;
|
||||
profile.motion.i2c_addr = 0x28;
|
||||
|
||||
profile.sd.cs = 48;
|
||||
|
||||
profile.optional.has_a7682e = true;
|
||||
profile.optional.has_pcm512a = false;
|
||||
profile.optional.a7682e_enable = 41;
|
||||
profile.optional.a7682e_pwrkey = 40;
|
||||
profile.optional.a7682e_rst = 9;
|
||||
profile.optional.a7682e_ri = 7;
|
||||
profile.optional.a7682e_dtr = 8;
|
||||
profile.optional.a7682e_uart.tx = 11;
|
||||
profile.optional.a7682e_uart.rx = 10;
|
||||
profile.optional.a7682e_uart.baud = 115200;
|
||||
profile.optional.i2s_bclk = -1;
|
||||
profile.optional.i2s_lrc = -1;
|
||||
profile.optional.i2s_dout = -1;
|
||||
|
||||
profile.motor_pin = 2;
|
||||
profile.keyboard_default_brightness = 96;
|
||||
profile.screen_width = 240;
|
||||
profile.screen_height = 320;
|
||||
return profile;
|
||||
}
|
||||
|
||||
inline BoardProfile makePCM512AProfile()
|
||||
{
|
||||
BoardProfile profile = makeA7682EProfile();
|
||||
profile.identity.long_name = "Trail Mate T-Deck Pro PCM512A";
|
||||
profile.identity.short_name = "TDPA";
|
||||
profile.identity.ble_name = "TrailMate-TDeckPro-Audio";
|
||||
|
||||
profile.optional.has_a7682e = false;
|
||||
profile.optional.has_pcm512a = true;
|
||||
profile.optional.a7682e_enable = -1;
|
||||
profile.optional.a7682e_pwrkey = -1;
|
||||
profile.optional.a7682e_rst = -1;
|
||||
profile.optional.a7682e_ri = -1;
|
||||
profile.optional.a7682e_dtr = -1;
|
||||
profile.optional.a7682e_uart.tx = -1;
|
||||
profile.optional.a7682e_uart.rx = -1;
|
||||
profile.optional.a7682e_uart.baud = 0;
|
||||
profile.optional.i2s_bclk = 7;
|
||||
profile.optional.i2s_lrc = 9;
|
||||
profile.optional.i2s_dout = 8;
|
||||
profile.motor_pin = 40;
|
||||
return profile;
|
||||
}
|
||||
|
||||
#if defined(TRAIL_MATE_TDECK_PRO_A7682E)
|
||||
static const BoardProfile kBoardProfile = makeA7682EProfile();
|
||||
#elif defined(TRAIL_MATE_TDECK_PRO_PCM512A)
|
||||
static const BoardProfile kBoardProfile = makePCM512AProfile();
|
||||
#else
|
||||
#error "T-Deck Pro profile requires TRAIL_MATE_TDECK_PRO_A7682E or TRAIL_MATE_TDECK_PRO_PCM512A"
|
||||
#endif
|
||||
|
||||
} // namespace boards::tdeck_pro
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "boards/tdeck_pro/tdeck_pro_board.h"
|
||||
#include "platform/esp/boards/board_runtime.h"
|
||||
#include "ui/LV_Helper.h"
|
||||
|
||||
namespace platform::esp::boards::detail
|
||||
{
|
||||
|
||||
inline void initializeBoard(bool waking_from_sleep)
|
||||
{
|
||||
(void)waking_from_sleep;
|
||||
::boards::tdeck_pro::board.begin();
|
||||
}
|
||||
|
||||
inline void initializeDisplay()
|
||||
{
|
||||
beginLvglHelper(static_cast<LilyGo_Display&>(::boards::tdeck_pro::instance));
|
||||
}
|
||||
|
||||
inline bool tryResolveAppContextInitHandles(AppContextInitHandles* out_handles)
|
||||
{
|
||||
if (!out_handles)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
*out_handles = resolveAppContextInitHandles();
|
||||
return true;
|
||||
}
|
||||
|
||||
inline AppContextInitHandles resolveAppContextInitHandles()
|
||||
{
|
||||
return {&::boards::tdeck_pro::board, &::boards::tdeck_pro::instance, &::boards::tdeck_pro::instance, &::boards::tdeck_pro::instance};
|
||||
}
|
||||
|
||||
} // namespace platform::esp::boards::detail
|
||||
@@ -0,0 +1,166 @@
|
||||
#pragma once
|
||||
|
||||
#define XPOWERS_CHIP_BQ25896
|
||||
|
||||
#include <Adafruit_TCA8418.h>
|
||||
#include <GxEPD2_BW.h>
|
||||
#include <GaugeBQ27220.hpp>
|
||||
#include <RadioLib.h>
|
||||
#include <SensorBHI260AP.hpp>
|
||||
#include <TouchDrvCSTXXX.hpp>
|
||||
#include <XPowersLib.h>
|
||||
#include <SPI.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "board/BoardBase.h"
|
||||
#include "board/GpsBoard.h"
|
||||
#include "board/LoraBoard.h"
|
||||
#include "board/MotionBoard.h"
|
||||
#include "board/SdBoard.h"
|
||||
#include "board/TLoRaPagerTypes.h"
|
||||
#include "boards/tdeck_pro/board_profile.h"
|
||||
#include "display/DisplayInterface.h"
|
||||
#include "platform/esp/arduino_common/gps/GPS.h"
|
||||
|
||||
namespace boards::tdeck_pro
|
||||
{
|
||||
|
||||
class SX1262Access : public SX1262
|
||||
{
|
||||
public:
|
||||
using SX1262::SX1262;
|
||||
using SX126x::readRegister;
|
||||
using SX126x::setTxParams;
|
||||
using SX126x::writeRegister;
|
||||
};
|
||||
|
||||
class TDeckProBoard : public BoardBase,
|
||||
public LoraBoard,
|
||||
public GpsBoard,
|
||||
public MotionBoard,
|
||||
public SdBoard,
|
||||
public LilyGo_Display
|
||||
{
|
||||
public:
|
||||
static TDeckProBoard* getInstance();
|
||||
static constexpr const BoardProfile& profile()
|
||||
{
|
||||
return kBoardProfile;
|
||||
}
|
||||
|
||||
uint32_t begin(uint32_t disable_hw_init = 0) override;
|
||||
void wakeUp() override {}
|
||||
void handlePowerButton() override {}
|
||||
void softwareShutdown() override {}
|
||||
|
||||
void setBrightness(uint8_t level) override;
|
||||
uint8_t getBrightness() override { return brightness_; }
|
||||
|
||||
bool hasKeyboard() override { return keyboard_ready_; }
|
||||
void keyboardSetBrightness(uint8_t level) override;
|
||||
uint8_t keyboardGetBrightness() override { return keyboard_brightness_; }
|
||||
|
||||
bool isRTCReady() const override;
|
||||
bool isCharging() override;
|
||||
int getBatteryLevel() override;
|
||||
|
||||
bool isSDReady() const override { return sd_ready_; }
|
||||
bool isCardReady() override;
|
||||
bool isGPSReady() const override { return gps_ready_; }
|
||||
|
||||
void vibrator() override;
|
||||
void stopVibrator() override;
|
||||
void playMessageTone() override;
|
||||
void setMessageToneVolume(uint8_t volume_percent) override;
|
||||
uint8_t getMessageToneVolume() const override;
|
||||
|
||||
void setRotation(uint8_t rotation) override;
|
||||
uint8_t getRotation() override { return rotation_; }
|
||||
void pushColors(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t* color) override;
|
||||
uint16_t width() override;
|
||||
uint16_t height() override;
|
||||
bool hasTouch() override { return touch_ready_; }
|
||||
uint8_t getPoint(int16_t* x, int16_t* y, uint8_t get_point) override;
|
||||
int getKeyChar(char* c) override;
|
||||
|
||||
bool isRadioOnline() const override { return radio_ready_; }
|
||||
int transmitRadio(const uint8_t* data, size_t len) override;
|
||||
int startRadioReceive() override;
|
||||
uint32_t getRadioIrqFlags() override;
|
||||
int getRadioPacketLength(bool update) override;
|
||||
int readRadioData(uint8_t* buf, size_t len) override;
|
||||
void clearRadioIrqFlags(uint32_t flags) override;
|
||||
float getRadioRSSI() override;
|
||||
float getRadioSNR() override;
|
||||
void configureLoraRadio(float freq_mhz, float bw_khz, uint8_t sf, uint8_t cr_denom,
|
||||
int8_t tx_power, uint16_t preamble_len, uint8_t sync_word,
|
||||
uint8_t crc_len) override;
|
||||
|
||||
bool initGPS() override;
|
||||
void setGPSOnline(bool online) override { gps_ready_ = online; }
|
||||
GPS& getGPS() override { return gps_; }
|
||||
void powerControl(PowerCtrlChannel_t ch, bool enable) override;
|
||||
bool syncTimeFromGPS(uint32_t gps_task_interval_ms = 0) override;
|
||||
|
||||
SensorBHI260AP& getMotionSensor() override { return motion_; }
|
||||
bool isSensorReady() const override { return motion_ready_; }
|
||||
|
||||
bool hasCellularModem() const { return profile().optional.has_a7682e; }
|
||||
bool hasAudioDac() const { return profile().optional.has_pcm512a; }
|
||||
|
||||
private:
|
||||
TDeckProBoard();
|
||||
void initSharedPins();
|
||||
bool initPower();
|
||||
bool initDisplay();
|
||||
bool initTouch();
|
||||
bool initKeyboard();
|
||||
bool initMotion();
|
||||
bool initRadio();
|
||||
bool initStorage();
|
||||
bool installSD() override;
|
||||
void uninstallSD() override;
|
||||
void renderEpd();
|
||||
void setBit(int16_t x, int16_t y, bool black);
|
||||
bool keyEventToChar(uint8_t event, char* c, bool* pressed);
|
||||
|
||||
using EpdPanel = GxEPD2_BW<GxEPD2_310_GDEQ031T10, GxEPD2_310_GDEQ031T10::HEIGHT>;
|
||||
|
||||
uint8_t brightness_ = DEVICE_MAX_BRIGHTNESS_LEVEL;
|
||||
uint8_t keyboard_brightness_ = static_cast<uint8_t>(kBoardProfile.keyboard_default_brightness);
|
||||
uint8_t message_tone_volume_ = 45;
|
||||
uint8_t rotation_ = 0;
|
||||
bool power_ready_ = false;
|
||||
bool display_ready_ = false;
|
||||
bool touch_ready_ = false;
|
||||
bool keyboard_ready_ = false;
|
||||
bool radio_ready_ = false;
|
||||
bool sd_ready_ = false;
|
||||
bool gps_ready_ = false;
|
||||
bool motion_ready_ = false;
|
||||
bool rtc_ready_ = false;
|
||||
bool battery_gauge_ready_ = false;
|
||||
int last_battery_level_ = -1;
|
||||
|
||||
SPIClass& shared_spi_ = SPI;
|
||||
Module lora_module_{static_cast<uint32_t>(profile().lora.cs),
|
||||
static_cast<uint32_t>(profile().lora.irq),
|
||||
static_cast<uint32_t>(profile().lora.rst),
|
||||
static_cast<uint32_t>(profile().lora.busy),
|
||||
shared_spi_};
|
||||
SX1262Access radio_{&lora_module_};
|
||||
EpdPanel epd_{GxEPD2_310_GDEQ031T10(profile().epd.cs, profile().epd.dc, profile().epd.rst, profile().epd.busy)};
|
||||
TouchDrvCSTXXX touch_;
|
||||
Adafruit_TCA8418 keyboard_;
|
||||
GPS gps_;
|
||||
SensorBHI260AP motion_;
|
||||
PowersBQ25896 pmu_;
|
||||
GaugeBQ27220 gauge_;
|
||||
std::vector<uint8_t> mono_buffer_;
|
||||
};
|
||||
|
||||
extern TDeckProBoard& instance;
|
||||
extern BoardBase& board;
|
||||
|
||||
} // namespace boards::tdeck_pro
|
||||
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"name": "boards_tdeck_pro",
|
||||
"version": "0.1.0",
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
"platforms": [
|
||||
"espressif32"
|
||||
],
|
||||
"build": {
|
||||
"includeDir": "include",
|
||||
"srcDir": "src",
|
||||
"flags": [
|
||||
"-std=gnu++17",
|
||||
"-Iinclude",
|
||||
"-I../..",
|
||||
"-I../../platform/shared/include",
|
||||
"-I../../platform/esp/boards/include",
|
||||
"-I../../modules/ui_shared/include",
|
||||
"-I../../modules/core_sys/include",
|
||||
"-I../../modules/core_chat/include",
|
||||
"-I../../modules/core_gps/include",
|
||||
"-I../../modules/core_hostlink/include",
|
||||
"-I../../modules/core_team/include",
|
||||
"-I../../platform/esp/arduino_common/include"
|
||||
]
|
||||
},
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "lvgl"
|
||||
},
|
||||
{
|
||||
"name": "SensorLib"
|
||||
},
|
||||
{
|
||||
"name": "XPowersLib"
|
||||
},
|
||||
{
|
||||
"name": "RadioLib"
|
||||
},
|
||||
{
|
||||
"name": "TinyGPSPlus"
|
||||
},
|
||||
{
|
||||
"name": "GxEPD2"
|
||||
},
|
||||
{
|
||||
"name": "Adafruit TCA8418"
|
||||
},
|
||||
{
|
||||
"name": "Adafruit BusIO"
|
||||
},
|
||||
{
|
||||
"name": "ESP8266Audio"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,851 @@
|
||||
#if defined(ARDUINO_T_DECK_PRO)
|
||||
|
||||
#include "boards/tdeck_pro/tdeck_pro_board.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <AudioFileSourcePROGMEM.h>
|
||||
#include <AudioGeneratorRTTTL.h>
|
||||
#include <AudioOutputI2S.h>
|
||||
#include <SD.h>
|
||||
#include <SPI.h>
|
||||
#include <Wire.h>
|
||||
#include <ctime>
|
||||
#include <limits>
|
||||
#include <sys/time.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
|
||||
namespace boards::tdeck_pro
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr const char* kTag = "TDeckProBoard";
|
||||
constexpr time_t kMinValidEpochSeconds = 1577836800; // 2020-01-01 UTC
|
||||
constexpr uint8_t kKeyboardRows = 4;
|
||||
constexpr uint8_t kKeyboardCols = 10;
|
||||
constexpr uint32_t kEpdSpiHz = 2000000;
|
||||
constexpr uint8_t kFlushLogLimit = 8;
|
||||
SemaphoreHandle_t g_shared_spi_mutex = nullptr;
|
||||
|
||||
void sharedSpiReleaseAllCs()
|
||||
{
|
||||
digitalWrite(TDeckProBoard::profile().lora.cs, HIGH);
|
||||
digitalWrite(TDeckProBoard::profile().sd.cs, HIGH);
|
||||
digitalWrite(TDeckProBoard::profile().epd.cs, HIGH);
|
||||
}
|
||||
|
||||
void sharedSpiBusInit()
|
||||
{
|
||||
if (g_shared_spi_mutex == nullptr)
|
||||
{
|
||||
g_shared_spi_mutex = xSemaphoreCreateRecursiveMutex();
|
||||
if (g_shared_spi_mutex == nullptr)
|
||||
{
|
||||
Serial.printf("[%s] shared SPI mutex create failed\n", kTag);
|
||||
return;
|
||||
}
|
||||
}
|
||||
sharedSpiReleaseAllCs();
|
||||
}
|
||||
|
||||
void sharedSpiLock()
|
||||
{
|
||||
if (g_shared_spi_mutex == nullptr)
|
||||
{
|
||||
sharedSpiBusInit();
|
||||
}
|
||||
if (g_shared_spi_mutex != nullptr)
|
||||
{
|
||||
xSemaphoreTakeRecursive(g_shared_spi_mutex, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
void sharedSpiUnlock()
|
||||
{
|
||||
if (g_shared_spi_mutex != nullptr)
|
||||
{
|
||||
sharedSpiReleaseAllCs();
|
||||
xSemaphoreGiveRecursive(g_shared_spi_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void sharedSpiPrepareDevice(int cs_pin)
|
||||
{
|
||||
sharedSpiReleaseAllCs();
|
||||
if (cs_pin >= 0)
|
||||
{
|
||||
digitalWrite(cs_pin, HIGH);
|
||||
}
|
||||
}
|
||||
|
||||
int batteryPercentFromMv(int mv)
|
||||
{
|
||||
if (mv <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
int pct = static_cast<int>(((mv - 3300) / 900.0f) * 100.0f);
|
||||
if (pct < 0)
|
||||
{
|
||||
pct = 0;
|
||||
}
|
||||
if (pct > 100)
|
||||
{
|
||||
pct = 100;
|
||||
}
|
||||
return pct;
|
||||
}
|
||||
|
||||
bool isLeapYear(int year)
|
||||
{
|
||||
return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
|
||||
}
|
||||
|
||||
uint8_t daysInMonth(int year, uint8_t month)
|
||||
{
|
||||
static constexpr uint8_t kDays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
if (month < 1 || month > 12)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (month == 2 && isLeapYear(year))
|
||||
{
|
||||
return 29;
|
||||
}
|
||||
return kDays[month - 1];
|
||||
}
|
||||
|
||||
bool gpsDatetimeValid(int year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second)
|
||||
{
|
||||
if (year < 2020 || year > 2100)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (month < 1 || month > 12)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const uint8_t max_day = daysInMonth(year, month);
|
||||
if (day < 1 || day > max_day)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return hour < 24 && minute < 60 && second < 60;
|
||||
}
|
||||
|
||||
int64_t daysFromCivil(int year, unsigned month, unsigned day)
|
||||
{
|
||||
year -= month <= 2;
|
||||
const int era = (year >= 0 ? year : year - 399) / 400;
|
||||
const unsigned yoe = static_cast<unsigned>(year - era * 400);
|
||||
const unsigned doy = (153 * (month + (month > 2 ? -3 : 9)) + 2) / 5 + day - 1;
|
||||
const unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
|
||||
return static_cast<int64_t>(era) * 146097 + static_cast<int64_t>(doe) - 719468;
|
||||
}
|
||||
|
||||
time_t gpsDatetimeToEpochUtc(int year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second)
|
||||
{
|
||||
const int64_t days = daysFromCivil(year, month, day);
|
||||
const int64_t sec_of_day = static_cast<int64_t>(hour) * 3600 +
|
||||
static_cast<int64_t>(minute) * 60 +
|
||||
static_cast<int64_t>(second);
|
||||
const int64_t epoch64 = days * 86400 + sec_of_day;
|
||||
if (epoch64 < 0 || epoch64 > static_cast<int64_t>(std::numeric_limits<time_t>::max()))
|
||||
{
|
||||
return static_cast<time_t>(-1);
|
||||
}
|
||||
return static_cast<time_t>(epoch64);
|
||||
}
|
||||
|
||||
char translateKey(uint8_t idx)
|
||||
{
|
||||
static constexpr char kMap[kKeyboardRows][kKeyboardCols] = {
|
||||
{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'},
|
||||
{'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'},
|
||||
{'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '\n'},
|
||||
{'z', 'x', 'c', 'v', 'b', 'n', 'm', ' ', '\b', '.'},
|
||||
};
|
||||
const uint8_t row = idx / 10;
|
||||
const uint8_t col = idx % 10;
|
||||
if (row >= kKeyboardRows || col >= kKeyboardCols)
|
||||
{
|
||||
return '\0';
|
||||
}
|
||||
return kMap[row][col];
|
||||
}
|
||||
|
||||
void applyTxPower(SX1262Access& radio, int8_t tx_power)
|
||||
{
|
||||
constexpr int8_t kTxPowerMinDbm = -9;
|
||||
int8_t clipped = tx_power;
|
||||
if (clipped < kTxPowerMinDbm)
|
||||
{
|
||||
clipped = kTxPowerMinDbm;
|
||||
}
|
||||
radio.setOutputPower(clipped);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TDeckProBoard::TDeckProBoard()
|
||||
: LilyGo_Display(SPI_DRIVER, true)
|
||||
{
|
||||
mono_buffer_.resize(static_cast<size_t>(profile().screen_width * profile().screen_height) / 8U, 0xFF);
|
||||
}
|
||||
|
||||
TDeckProBoard* TDeckProBoard::getInstance()
|
||||
{
|
||||
static TDeckProBoard singleton;
|
||||
return &singleton;
|
||||
}
|
||||
|
||||
void TDeckProBoard::initSharedPins()
|
||||
{
|
||||
pinMode(profile().lora.enable, OUTPUT);
|
||||
digitalWrite(profile().lora.enable, HIGH);
|
||||
|
||||
pinMode(profile().gps.enable, OUTPUT);
|
||||
digitalWrite(profile().gps.enable, HIGH);
|
||||
|
||||
pinMode(profile().motion.enable_1v8, OUTPUT);
|
||||
digitalWrite(profile().motion.enable_1v8, HIGH);
|
||||
|
||||
if (profile().optional.has_a7682e && profile().optional.a7682e_enable >= 0)
|
||||
{
|
||||
pinMode(profile().optional.a7682e_enable, OUTPUT);
|
||||
digitalWrite(profile().optional.a7682e_enable, HIGH);
|
||||
}
|
||||
if (profile().optional.has_a7682e && profile().optional.a7682e_pwrkey >= 0)
|
||||
{
|
||||
pinMode(profile().optional.a7682e_pwrkey, OUTPUT);
|
||||
digitalWrite(profile().optional.a7682e_pwrkey, HIGH);
|
||||
}
|
||||
if (profile().motor_pin >= 0)
|
||||
{
|
||||
pinMode(profile().motor_pin, OUTPUT);
|
||||
digitalWrite(profile().motor_pin, LOW);
|
||||
}
|
||||
|
||||
pinMode(profile().sd.cs, OUTPUT);
|
||||
digitalWrite(profile().sd.cs, HIGH);
|
||||
pinMode(profile().epd.cs, OUTPUT);
|
||||
digitalWrite(profile().epd.cs, HIGH);
|
||||
pinMode(profile().lora.cs, OUTPUT);
|
||||
digitalWrite(profile().lora.cs, HIGH);
|
||||
sharedSpiBusInit();
|
||||
}
|
||||
|
||||
bool TDeckProBoard::initPower()
|
||||
{
|
||||
Wire.begin(profile().i2c.sda, profile().i2c.scl);
|
||||
delay(10);
|
||||
|
||||
power_ready_ = pmu_.begin(Wire, 0x6B, profile().i2c.sda, profile().i2c.scl);
|
||||
battery_gauge_ready_ = gauge_.begin(Wire, profile().i2c.sda, profile().i2c.scl);
|
||||
if (power_ready_)
|
||||
{
|
||||
pmu_.enableMeasure();
|
||||
pmu_.disableOTG();
|
||||
}
|
||||
Serial.printf("[%s] power init charger=%d gauge=%d\n", kTag, power_ready_ ? 1 : 0, battery_gauge_ready_ ? 1 : 0);
|
||||
return power_ready_ || battery_gauge_ready_;
|
||||
}
|
||||
|
||||
bool TDeckProBoard::initDisplay()
|
||||
{
|
||||
SPI.begin(profile().spi.sck, profile().spi.miso, profile().spi.mosi, profile().epd.cs);
|
||||
sharedSpiLock();
|
||||
sharedSpiPrepareDevice(profile().epd.cs);
|
||||
epd_.epd2.selectSPI(SPI, SPISettings(kEpdSpiHz, MSBFIRST, SPI_MODE0));
|
||||
epd_.init(0, true, 2, false);
|
||||
epd_.setRotation(rotation_);
|
||||
epd_.setFullWindow();
|
||||
epd_.firstPage();
|
||||
do
|
||||
{
|
||||
epd_.fillScreen(GxEPD_WHITE);
|
||||
} while (epd_.nextPage());
|
||||
epd_.powerOff();
|
||||
sharedSpiUnlock();
|
||||
display_ready_ = true;
|
||||
Serial.printf("[%s] epd init ok %ux%u spi=%luHz\n", kTag, width(), height(), (unsigned long)kEpdSpiHz);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TDeckProBoard::initTouch()
|
||||
{
|
||||
touch_.setPins(profile().touch.rst, profile().touch.int_pin);
|
||||
touch_ready_ = touch_.begin(Wire, profile().touch.i2c_addr, profile().i2c.sda, profile().i2c.scl);
|
||||
if (touch_ready_)
|
||||
{
|
||||
touch_.setMaxCoordinates(profile().screen_width, profile().screen_height);
|
||||
}
|
||||
Serial.printf("[%s] touch init %s\n", kTag, touch_ready_ ? "ok" : "fail");
|
||||
return touch_ready_;
|
||||
}
|
||||
|
||||
bool TDeckProBoard::initKeyboard()
|
||||
{
|
||||
keyboard_ready_ = keyboard_.begin(profile().keyboard.i2c_addr, &Wire);
|
||||
if (!keyboard_ready_)
|
||||
{
|
||||
Serial.printf("[%s] keyboard init fail\n", kTag);
|
||||
return false;
|
||||
}
|
||||
keyboard_.matrix(profile().keyboard.rows, profile().keyboard.cols);
|
||||
keyboard_.flush();
|
||||
keyboardSetBrightness(keyboard_brightness_);
|
||||
Serial.printf("[%s] keyboard init ok\n", kTag);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TDeckProBoard::initMotion()
|
||||
{
|
||||
motion_ready_ = motion_.begin(Wire, profile().motion.i2c_addr, profile().i2c.sda, profile().i2c.scl);
|
||||
Serial.printf("[%s] motion init %s\n", kTag, motion_ready_ ? "ok" : "fail");
|
||||
return motion_ready_;
|
||||
}
|
||||
|
||||
bool TDeckProBoard::initRadio()
|
||||
{
|
||||
SPI.begin(profile().spi.sck, profile().spi.miso, profile().spi.mosi, profile().lora.cs);
|
||||
sharedSpiLock();
|
||||
sharedSpiPrepareDevice(profile().lora.cs);
|
||||
radio_.reset();
|
||||
radio_ready_ = (radio_.begin() == RADIOLIB_ERR_NONE);
|
||||
sharedSpiUnlock();
|
||||
Serial.printf("[%s] radio init %s\n", kTag, radio_ready_ ? "ok" : "fail");
|
||||
return radio_ready_;
|
||||
}
|
||||
|
||||
bool TDeckProBoard::initStorage()
|
||||
{
|
||||
sd_ready_ = installSD();
|
||||
Serial.printf("[%s] sd init %s\n", kTag, sd_ready_ ? "ok" : "fail");
|
||||
return sd_ready_;
|
||||
}
|
||||
|
||||
bool TDeckProBoard::installSD()
|
||||
{
|
||||
pinMode(profile().sd.cs, OUTPUT);
|
||||
digitalWrite(profile().sd.cs, HIGH);
|
||||
sharedSpiLock();
|
||||
sharedSpiPrepareDevice(profile().sd.cs);
|
||||
const bool ok = SD.begin(profile().sd.cs, SPI);
|
||||
sharedSpiUnlock();
|
||||
return ok;
|
||||
}
|
||||
|
||||
void TDeckProBoard::uninstallSD()
|
||||
{
|
||||
SD.end();
|
||||
}
|
||||
|
||||
uint32_t TDeckProBoard::begin(uint32_t disable_hw_init)
|
||||
{
|
||||
Serial.begin(115200);
|
||||
delay(30);
|
||||
Serial.printf("[%s] begin variant=%s\n", kTag,
|
||||
#if defined(TRAIL_MATE_TDECK_PRO_A7682E)
|
||||
"a7682e"
|
||||
#else
|
||||
"pcm512a"
|
||||
#endif
|
||||
);
|
||||
|
||||
initSharedPins();
|
||||
(void)initPower();
|
||||
(void)initDisplay();
|
||||
|
||||
if ((disable_hw_init & NO_HW_TOUCH) == 0)
|
||||
{
|
||||
(void)initTouch();
|
||||
}
|
||||
(void)initKeyboard();
|
||||
(void)initMotion();
|
||||
if ((disable_hw_init & NO_HW_SD) == 0)
|
||||
{
|
||||
(void)initStorage();
|
||||
}
|
||||
if ((disable_hw_init & NO_HW_GPS) == 0)
|
||||
{
|
||||
(void)initGPS();
|
||||
}
|
||||
(void)initRadio();
|
||||
|
||||
rtc_ready_ = time(nullptr) >= kMinValidEpochSeconds;
|
||||
|
||||
uint32_t probe = 0;
|
||||
if (power_ready_) probe |= HW_PMU_ONLINE;
|
||||
if (battery_gauge_ready_) probe |= HW_GAUGE_ONLINE;
|
||||
if (touch_ready_) probe |= HW_TOUCH_ONLINE;
|
||||
if (keyboard_ready_) probe |= HW_KEYBOARD_ONLINE;
|
||||
if (motion_ready_) probe |= HW_BHI260AP_ONLINE;
|
||||
if (sd_ready_) probe |= HW_SD_ONLINE;
|
||||
if (gps_ready_) probe |= HW_GPS_ONLINE;
|
||||
if (radio_ready_) probe |= HW_RADIO_ONLINE;
|
||||
return probe;
|
||||
}
|
||||
|
||||
void TDeckProBoard::setBrightness(uint8_t level)
|
||||
{
|
||||
brightness_ = level;
|
||||
}
|
||||
|
||||
void TDeckProBoard::keyboardSetBrightness(uint8_t level)
|
||||
{
|
||||
keyboard_brightness_ = level;
|
||||
if (profile().keyboard.led >= 0)
|
||||
{
|
||||
analogWrite(profile().keyboard.led, level);
|
||||
}
|
||||
}
|
||||
|
||||
bool TDeckProBoard::isRTCReady() const
|
||||
{
|
||||
return rtc_ready_ || (time(nullptr) >= kMinValidEpochSeconds);
|
||||
}
|
||||
|
||||
bool TDeckProBoard::isCharging()
|
||||
{
|
||||
return power_ready_ ? pmu_.isCharging() : false;
|
||||
}
|
||||
|
||||
int TDeckProBoard::getBatteryLevel()
|
||||
{
|
||||
if (battery_gauge_ready_)
|
||||
{
|
||||
int level = static_cast<int>(gauge_.getStateOfCharge());
|
||||
if (level >= 0 && level <= 100)
|
||||
{
|
||||
last_battery_level_ = level;
|
||||
return level;
|
||||
}
|
||||
}
|
||||
if (power_ready_)
|
||||
{
|
||||
int level = batteryPercentFromMv(static_cast<int>(pmu_.getBattVoltage()));
|
||||
if (level >= 0 && level <= 100)
|
||||
{
|
||||
last_battery_level_ = level;
|
||||
return level;
|
||||
}
|
||||
}
|
||||
return last_battery_level_;
|
||||
}
|
||||
|
||||
bool TDeckProBoard::isCardReady()
|
||||
{
|
||||
return sd_ready_ && SD.cardType() != CARD_NONE;
|
||||
}
|
||||
|
||||
void TDeckProBoard::vibrator()
|
||||
{
|
||||
if (profile().motor_pin >= 0)
|
||||
{
|
||||
digitalWrite(profile().motor_pin, HIGH);
|
||||
}
|
||||
}
|
||||
|
||||
void TDeckProBoard::stopVibrator()
|
||||
{
|
||||
if (profile().motor_pin >= 0)
|
||||
{
|
||||
digitalWrite(profile().motor_pin, LOW);
|
||||
}
|
||||
}
|
||||
|
||||
void TDeckProBoard::playMessageTone()
|
||||
{
|
||||
if (!profile().optional.has_pcm512a || message_tone_volume_ == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static const char kMessageToneRtttl[] = "Msg:d=4,o=6,b=200:32e,32g,32b,16c7";
|
||||
AudioOutputI2S audio_out(1, AudioOutputI2S::EXTERNAL_I2S);
|
||||
audio_out.SetPinout(profile().optional.i2s_bclk,
|
||||
profile().optional.i2s_lrc,
|
||||
profile().optional.i2s_dout);
|
||||
float gain = static_cast<float>(message_tone_volume_) / 250.0f;
|
||||
if (gain > 0.40f)
|
||||
{
|
||||
gain = 0.40f;
|
||||
}
|
||||
audio_out.SetGain(gain);
|
||||
|
||||
AudioFileSourcePROGMEM song(kMessageToneRtttl, sizeof(kMessageToneRtttl) - 1);
|
||||
AudioGeneratorRTTTL generator;
|
||||
if (generator.begin(&song, &audio_out))
|
||||
{
|
||||
const uint32_t deadline = millis() + 1600;
|
||||
while (generator.isRunning() && millis() < deadline)
|
||||
{
|
||||
if (!generator.loop())
|
||||
{
|
||||
break;
|
||||
}
|
||||
delay(1);
|
||||
}
|
||||
generator.stop();
|
||||
}
|
||||
audio_out.stop();
|
||||
}
|
||||
|
||||
void TDeckProBoard::setMessageToneVolume(uint8_t volume_percent)
|
||||
{
|
||||
if (volume_percent > 100)
|
||||
{
|
||||
volume_percent = 100;
|
||||
}
|
||||
message_tone_volume_ = volume_percent;
|
||||
}
|
||||
|
||||
uint8_t TDeckProBoard::getMessageToneVolume() const
|
||||
{
|
||||
return message_tone_volume_;
|
||||
}
|
||||
|
||||
void TDeckProBoard::setRotation(uint8_t rotation)
|
||||
{
|
||||
rotation_ = rotation & 0x3;
|
||||
epd_.setRotation(rotation_);
|
||||
}
|
||||
|
||||
uint16_t TDeckProBoard::width()
|
||||
{
|
||||
return rotation_ % 2 == 0 ? static_cast<uint16_t>(profile().screen_width)
|
||||
: static_cast<uint16_t>(profile().screen_height);
|
||||
}
|
||||
|
||||
uint16_t TDeckProBoard::height()
|
||||
{
|
||||
return rotation_ % 2 == 0 ? static_cast<uint16_t>(profile().screen_height)
|
||||
: static_cast<uint16_t>(profile().screen_width);
|
||||
}
|
||||
|
||||
void TDeckProBoard::setBit(int16_t x, int16_t y, bool black)
|
||||
{
|
||||
if (x < 0 || y < 0 || x >= profile().screen_width || y >= profile().screen_height)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const size_t idx = static_cast<size_t>(y) * static_cast<size_t>(profile().screen_width) + static_cast<size_t>(x);
|
||||
const size_t byte_idx = idx / 8U;
|
||||
const uint8_t bit_mask = static_cast<uint8_t>(0x80U >> (idx % 8U));
|
||||
if (black)
|
||||
{
|
||||
mono_buffer_[byte_idx] &= static_cast<uint8_t>(~bit_mask);
|
||||
}
|
||||
else
|
||||
{
|
||||
mono_buffer_[byte_idx] |= bit_mask;
|
||||
}
|
||||
}
|
||||
|
||||
void TDeckProBoard::renderEpd()
|
||||
{
|
||||
if (!display_ready_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sharedSpiLock();
|
||||
sharedSpiPrepareDevice(profile().epd.cs);
|
||||
epd_.setRotation(rotation_);
|
||||
epd_.setFullWindow();
|
||||
epd_.firstPage();
|
||||
do
|
||||
{
|
||||
epd_.drawInvertedBitmap(0, 0, mono_buffer_.data(), profile().screen_width, profile().screen_height, GxEPD_BLACK);
|
||||
} while (epd_.nextPage());
|
||||
epd_.powerOff();
|
||||
sharedSpiUnlock();
|
||||
}
|
||||
|
||||
void TDeckProBoard::pushColors(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t* color)
|
||||
{
|
||||
if (!color)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static uint8_t s_flush_log_count = 0;
|
||||
uint32_t dark_pixels = 0;
|
||||
|
||||
for (uint16_t row = 0; row < y2; ++row)
|
||||
{
|
||||
for (uint16_t col = 0; col < x2; ++col)
|
||||
{
|
||||
const uint16_t pixel = color[static_cast<size_t>(row) * x2 + col];
|
||||
const uint8_t r = static_cast<uint8_t>((pixel >> 11) & 0x1F);
|
||||
const uint8_t g = static_cast<uint8_t>((pixel >> 5) & 0x3F);
|
||||
const uint8_t b = static_cast<uint8_t>(pixel & 0x1F);
|
||||
const uint16_t luminance = static_cast<uint16_t>(r * 299U + g * 587U + b * 114U);
|
||||
const bool black = luminance < 16384U;
|
||||
if (black)
|
||||
{
|
||||
dark_pixels++;
|
||||
}
|
||||
setBit(static_cast<int16_t>(x1 + col), static_cast<int16_t>(y1 + row), black);
|
||||
}
|
||||
}
|
||||
if (s_flush_log_count < kFlushLogLimit)
|
||||
{
|
||||
Serial.printf("[%s] flush #%u area=(%u,%u %ux%u) dark=%lu/%lu\n",
|
||||
kTag,
|
||||
static_cast<unsigned>(s_flush_log_count + 1),
|
||||
static_cast<unsigned>(x1),
|
||||
static_cast<unsigned>(y1),
|
||||
static_cast<unsigned>(x2),
|
||||
static_cast<unsigned>(y2),
|
||||
static_cast<unsigned long>(dark_pixels),
|
||||
static_cast<unsigned long>(static_cast<uint32_t>(x2) * static_cast<uint32_t>(y2)));
|
||||
s_flush_log_count++;
|
||||
}
|
||||
renderEpd();
|
||||
}
|
||||
|
||||
uint8_t TDeckProBoard::getPoint(int16_t* x, int16_t* y, uint8_t get_point)
|
||||
{
|
||||
if (!touch_ready_)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int16_t tx = 0;
|
||||
int16_t ty = 0;
|
||||
const uint8_t touched = touch_.getPoint(&tx, &ty, get_point);
|
||||
if (!touched || !x || !y)
|
||||
{
|
||||
return touched;
|
||||
}
|
||||
|
||||
switch (rotation_ & 0x3)
|
||||
{
|
||||
case 1:
|
||||
*x = ty;
|
||||
*y = static_cast<int16_t>(profile().screen_width - 1 - tx);
|
||||
break;
|
||||
case 2:
|
||||
*x = static_cast<int16_t>(profile().screen_width - 1 - tx);
|
||||
*y = static_cast<int16_t>(profile().screen_height - 1 - ty);
|
||||
break;
|
||||
case 3:
|
||||
*x = static_cast<int16_t>(profile().screen_height - 1 - ty);
|
||||
*y = tx;
|
||||
break;
|
||||
default:
|
||||
*x = tx;
|
||||
*y = ty;
|
||||
break;
|
||||
}
|
||||
return touched;
|
||||
}
|
||||
|
||||
bool TDeckProBoard::keyEventToChar(uint8_t event, char* c, bool* pressed)
|
||||
{
|
||||
if (!c || !pressed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
*pressed = (event & 0x80U) != 0;
|
||||
uint8_t key = static_cast<uint8_t>(event & 0x7FU);
|
||||
if (key == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
key -= 1;
|
||||
*c = translateKey(key);
|
||||
return *c != '\0';
|
||||
}
|
||||
|
||||
int TDeckProBoard::getKeyChar(char* c)
|
||||
{
|
||||
if (!keyboard_ready_ || !c || keyboard_.available() == 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
bool pressed = false;
|
||||
if (!keyEventToChar(keyboard_.getEvent(), c, &pressed))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
return pressed ? KEYBOARD_PRESSED : KEYBOARD_RELEASED;
|
||||
}
|
||||
|
||||
int TDeckProBoard::transmitRadio(const uint8_t* data, size_t len)
|
||||
{
|
||||
sharedSpiLock();
|
||||
sharedSpiPrepareDevice(profile().lora.cs);
|
||||
const int rc = radio_.transmit(data, len);
|
||||
sharedSpiUnlock();
|
||||
return rc;
|
||||
}
|
||||
|
||||
int TDeckProBoard::startRadioReceive()
|
||||
{
|
||||
sharedSpiLock();
|
||||
sharedSpiPrepareDevice(profile().lora.cs);
|
||||
const int rc = radio_.startReceive();
|
||||
sharedSpiUnlock();
|
||||
return rc;
|
||||
}
|
||||
|
||||
uint32_t TDeckProBoard::getRadioIrqFlags()
|
||||
{
|
||||
sharedSpiLock();
|
||||
sharedSpiPrepareDevice(profile().lora.cs);
|
||||
const uint32_t flags = radio_.getIrqFlags();
|
||||
sharedSpiUnlock();
|
||||
return flags;
|
||||
}
|
||||
|
||||
int TDeckProBoard::getRadioPacketLength(bool update)
|
||||
{
|
||||
sharedSpiLock();
|
||||
sharedSpiPrepareDevice(profile().lora.cs);
|
||||
const int len = static_cast<int>(radio_.getPacketLength(update));
|
||||
sharedSpiUnlock();
|
||||
return len;
|
||||
}
|
||||
|
||||
int TDeckProBoard::readRadioData(uint8_t* buf, size_t len)
|
||||
{
|
||||
sharedSpiLock();
|
||||
sharedSpiPrepareDevice(profile().lora.cs);
|
||||
const int rc = radio_.readData(buf, len);
|
||||
sharedSpiUnlock();
|
||||
return rc;
|
||||
}
|
||||
|
||||
void TDeckProBoard::clearRadioIrqFlags(uint32_t flags)
|
||||
{
|
||||
sharedSpiLock();
|
||||
sharedSpiPrepareDevice(profile().lora.cs);
|
||||
radio_.clearIrqFlags(flags);
|
||||
sharedSpiUnlock();
|
||||
}
|
||||
|
||||
float TDeckProBoard::getRadioRSSI()
|
||||
{
|
||||
sharedSpiLock();
|
||||
sharedSpiPrepareDevice(profile().lora.cs);
|
||||
const float rssi = radio_.getRSSI();
|
||||
sharedSpiUnlock();
|
||||
return rssi;
|
||||
}
|
||||
|
||||
float TDeckProBoard::getRadioSNR()
|
||||
{
|
||||
sharedSpiLock();
|
||||
sharedSpiPrepareDevice(profile().lora.cs);
|
||||
const float snr = radio_.getSNR();
|
||||
sharedSpiUnlock();
|
||||
return snr;
|
||||
}
|
||||
|
||||
void TDeckProBoard::configureLoraRadio(float freq_mhz, float bw_khz, uint8_t sf, uint8_t cr_denom,
|
||||
int8_t tx_power, uint16_t preamble_len, uint8_t sync_word,
|
||||
uint8_t crc_len)
|
||||
{
|
||||
sharedSpiLock();
|
||||
sharedSpiPrepareDevice(profile().lora.cs);
|
||||
radio_.setFrequency(freq_mhz);
|
||||
radio_.setBandwidth(bw_khz);
|
||||
radio_.setSpreadingFactor(sf);
|
||||
radio_.setCodingRate(cr_denom);
|
||||
applyTxPower(radio_, tx_power);
|
||||
radio_.setPreambleLength(preamble_len);
|
||||
radio_.setSyncWord(sync_word);
|
||||
radio_.setCRC(crc_len);
|
||||
sharedSpiUnlock();
|
||||
}
|
||||
|
||||
bool TDeckProBoard::initGPS()
|
||||
{
|
||||
Serial2.begin(profile().gps.uart.baud, SERIAL_8N1, profile().gps.uart.rx, profile().gps.uart.tx);
|
||||
delay(50);
|
||||
gps_ready_ = gps_.init(&Serial2);
|
||||
Serial.printf("[%s] gps init %s\n", kTag, gps_ready_ ? "ok" : "fail");
|
||||
return gps_ready_;
|
||||
}
|
||||
|
||||
void TDeckProBoard::powerControl(PowerCtrlChannel_t ch, bool enable)
|
||||
{
|
||||
switch (ch)
|
||||
{
|
||||
case POWER_GPS:
|
||||
if (profile().gps.enable >= 0)
|
||||
{
|
||||
digitalWrite(profile().gps.enable, enable ? HIGH : LOW);
|
||||
}
|
||||
break;
|
||||
case POWER_SENSOR:
|
||||
if (profile().motion.enable_1v8 >= 0)
|
||||
{
|
||||
digitalWrite(profile().motion.enable_1v8, enable ? HIGH : LOW);
|
||||
}
|
||||
break;
|
||||
case POWER_RADIO:
|
||||
if (profile().lora.enable >= 0)
|
||||
{
|
||||
digitalWrite(profile().lora.enable, enable ? HIGH : LOW);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool TDeckProBoard::syncTimeFromGPS(uint32_t gps_task_interval_ms)
|
||||
{
|
||||
(void)gps_task_interval_ms;
|
||||
if (!gps_.date.isValid() || !gps_.time.isValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int year = gps_.date.year();
|
||||
const uint8_t month = gps_.date.month();
|
||||
const uint8_t day = gps_.date.day();
|
||||
const uint8_t hour = gps_.time.hour();
|
||||
const uint8_t minute = gps_.time.minute();
|
||||
const uint8_t second = gps_.time.second();
|
||||
if (!gpsDatetimeValid(year, month, day, hour, minute, second))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const time_t epoch = gpsDatetimeToEpochUtc(year, month, day, hour, minute, second);
|
||||
if (epoch < kMinValidEpochSeconds)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
timeval tv{};
|
||||
tv.tv_sec = epoch;
|
||||
tv.tv_usec = 0;
|
||||
if (settimeofday(&tv, nullptr) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
rtc_ready_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
TDeckProBoard& getInstanceRef()
|
||||
{
|
||||
return *TDeckProBoard::getInstance();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TDeckProBoard& instance = getInstanceRef();
|
||||
BoardBase& board = getInstanceRef();
|
||||
|
||||
} // namespace boards::tdeck_pro
|
||||
|
||||
BoardBase& board = ::boards::tdeck_pro::instance;
|
||||
|
||||
#endif // defined(ARDUINO_T_DECK_PRO)
|
||||
@@ -179,13 +179,16 @@ void refresh_recent_widget()
|
||||
std::snprintf(footer, sizeof(footer), "%u threads active", static_cast<unsigned>(total));
|
||||
set_label_text_if_changed(recent.footer_label, footer);
|
||||
|
||||
// Keep the footer accent subtle; continuously animating it forces a redraw
|
||||
// every dashboard tick even when no conversation state changed.
|
||||
const lv_coord_t track_w = lv_obj_get_width(recent.scan_track);
|
||||
const lv_coord_t runner_w = lv_obj_get_width(recent.scan_runner);
|
||||
const lv_coord_t travel = track_w - runner_w;
|
||||
const lv_coord_t x = travel > 0
|
||||
? static_cast<lv_coord_t>(((dashboard_state().tick % 2U) == 0U) ? (travel / 3) : ((travel * 2) / 3))
|
||||
: 0;
|
||||
lv_obj_set_x(recent.scan_runner, x);
|
||||
const lv_coord_t x = travel > 0 ? travel / 2 : 0;
|
||||
if (lv_obj_get_x(recent.scan_runner) != x)
|
||||
{
|
||||
lv_obj_set_x(recent.scan_runner, x);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui::menu::dashboard
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace ui::menu::dashboard
|
||||
{
|
||||
@@ -98,7 +99,8 @@ DashboardCardChrome create_card_chrome(lv_obj_t* parent,
|
||||
chrome.body = lv_obj_create(chrome.card);
|
||||
lv_obj_set_size(chrome.body, LV_PCT(100), card_h - kHeaderH - kFooterH);
|
||||
lv_obj_align_to(chrome.body, chrome.header, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
|
||||
lv_obj_set_style_bg_opa(chrome.body, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_bg_color(chrome.body, color_panel_bg(), 0);
|
||||
lv_obj_set_style_bg_opa(chrome.body, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_width(chrome.body, 0, 0);
|
||||
lv_obj_set_style_pad_left(chrome.body, 10, 0);
|
||||
lv_obj_set_style_pad_right(chrome.body, 10, 0);
|
||||
@@ -110,7 +112,7 @@ DashboardCardChrome create_card_chrome(lv_obj_t* parent,
|
||||
lv_obj_set_size(chrome.footer, LV_PCT(100), kFooterH);
|
||||
lv_obj_align(chrome.footer, LV_ALIGN_BOTTOM_MID, 0, 0);
|
||||
lv_obj_set_style_bg_color(chrome.footer, color_soft_amber(), 0);
|
||||
lv_obj_set_style_bg_opa(chrome.footer, LV_OPA_50, 0);
|
||||
lv_obj_set_style_bg_opa(chrome.footer, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_width(chrome.footer, 0, 0);
|
||||
lv_obj_set_style_radius(chrome.footer, 18, 0);
|
||||
lv_obj_set_style_pad_left(chrome.footer, 10, 0);
|
||||
@@ -128,10 +130,23 @@ void set_status_chip(DashboardCardChrome& chrome, const char* text, lv_color_t b
|
||||
{
|
||||
return;
|
||||
}
|
||||
lv_obj_set_style_bg_color(chrome.status_chip, bg, 0);
|
||||
lv_obj_set_style_bg_opa(chrome.status_chip, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_text_color(chrome.status_label, fg, 0);
|
||||
lv_label_set_text(chrome.status_label, text);
|
||||
if (lv_color_to_int(lv_obj_get_style_bg_color(chrome.status_chip, LV_PART_MAIN)) != lv_color_to_int(bg))
|
||||
{
|
||||
lv_obj_set_style_bg_color(chrome.status_chip, bg, 0);
|
||||
}
|
||||
if (lv_obj_get_style_bg_opa(chrome.status_chip, LV_PART_MAIN) != LV_OPA_COVER)
|
||||
{
|
||||
lv_obj_set_style_bg_opa(chrome.status_chip, LV_OPA_COVER, 0);
|
||||
}
|
||||
if (lv_color_to_int(lv_obj_get_style_text_color(chrome.status_label, LV_PART_MAIN)) != lv_color_to_int(fg))
|
||||
{
|
||||
lv_obj_set_style_text_color(chrome.status_label, fg, 0);
|
||||
}
|
||||
const char* current = lv_label_get_text(chrome.status_label);
|
||||
if (text != nullptr && (current == nullptr || std::strcmp(current, text) != 0))
|
||||
{
|
||||
lv_label_set_text(chrome.status_label, text);
|
||||
}
|
||||
}
|
||||
|
||||
void style_stat_tile(lv_obj_t* tile, lv_color_t bg)
|
||||
|
||||
@@ -10,13 +10,17 @@
|
||||
#include "ui/menu/dashboard/dashboard_state.h"
|
||||
#include "ui/menu/dashboard/dashboard_widgets.h"
|
||||
#include "ui/menu/menu_profile.h"
|
||||
#include "ui/ui_theme.h"
|
||||
|
||||
namespace ui::menu::dashboard
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
constexpr uint32_t kDashboardTimerMs = 220;
|
||||
// Tab5's 720x1280 menu can starve the LVGL task if we keep animating the
|
||||
// dashboard aggressively while the menu grid is also visible. A slower cadence
|
||||
// keeps the dashboard useful without continuously forcing large redraws.
|
||||
constexpr uint32_t kDashboardTimerMs = 1000;
|
||||
|
||||
bool is_tab5_profile()
|
||||
{
|
||||
@@ -34,7 +38,6 @@ void refresh_dashboard(lv_timer_t* timer)
|
||||
|
||||
++dashboard.tick;
|
||||
|
||||
// Keep motion responsive, but avoid repainting the whole dashboard too aggressively.
|
||||
refresh_compass_widget();
|
||||
|
||||
if ((dashboard.tick % 2U) == 1U)
|
||||
@@ -68,7 +71,8 @@ void init(lv_obj_t* menu_panel, lv_obj_t* grid_panel, const ui::menu_layout::Ini
|
||||
|
||||
dashboard.dock_right = right_gap >= 240;
|
||||
dashboard.panel = lv_obj_create(menu_panel);
|
||||
lv_obj_set_style_bg_opa(dashboard.panel, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_bg_color(dashboard.panel, ui::theme::page_bg(), 0);
|
||||
lv_obj_set_style_bg_opa(dashboard.panel, LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_width(dashboard.panel, 0, 0);
|
||||
lv_obj_set_style_pad_all(dashboard.panel, 0, 0);
|
||||
lv_obj_set_style_pad_row(dashboard.panel, 12, 0);
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace ui::menu_profile
|
||||
namespace
|
||||
{
|
||||
|
||||
#if !defined(TRAIL_MATE_ESP_BOARD_TAB5) && !defined(ARDUINO_T_LORA_PAGER) && !defined(ARDUINO_T_DECK)
|
||||
#if !defined(TRAIL_MATE_ESP_BOARD_TAB5) && !defined(ARDUINO_T_LORA_PAGER) && !defined(ARDUINO_T_DECK) && !defined(ARDUINO_T_DECK_PRO)
|
||||
lv_coord_t display_width()
|
||||
{
|
||||
lv_coord_t width = lv_display_get_physical_horizontal_resolution(nullptr);
|
||||
@@ -206,7 +206,7 @@ const MenuLayoutProfile& current()
|
||||
return make_tab5_profile();
|
||||
#elif defined(ARDUINO_T_LORA_PAGER)
|
||||
return make_pager_profile();
|
||||
#elif defined(ARDUINO_T_DECK)
|
||||
#elif defined(ARDUINO_T_DECK) || defined(ARDUINO_T_DECK_PRO)
|
||||
return make_tdeck_profile();
|
||||
#else
|
||||
return make_default_profile(display_width(), display_height());
|
||||
|
||||
@@ -163,7 +163,7 @@ const PageLayoutProfile& current()
|
||||
return make_tab5_profile();
|
||||
#elif defined(ARDUINO_T_LORA_PAGER)
|
||||
return make_pager_profile();
|
||||
#elif defined(ARDUINO_T_DECK)
|
||||
#elif defined(ARDUINO_T_DECK) || defined(ARDUINO_T_DECK_PRO)
|
||||
return make_tdeck_profile();
|
||||
#else
|
||||
lv_coord_t width = lv_display_get_physical_horizontal_resolution(nullptr);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "team/protocol/team_location_marker.h"
|
||||
#include "team/usecase/team_controller.h"
|
||||
#include "ui/page/page_profile.h"
|
||||
#include "ui/assets/fonts/fonts.h"
|
||||
#include "ui/screens/team/team_ui_store.h"
|
||||
#include "ui/ui_common.h"
|
||||
#include "ui/widgets/system_notification.h"
|
||||
@@ -1516,7 +1517,11 @@ void UiController::openKeyVerificationInfoModal(chat::NodeId node_id, uint32_t n
|
||||
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
char num_buf[24] = {};
|
||||
std::snprintf(num_buf, sizeof(num_buf), "%03u %03u", number / 1000U, number % 1000U);
|
||||
std::snprintf(num_buf,
|
||||
sizeof(num_buf),
|
||||
"%03u %03u",
|
||||
static_cast<unsigned>(number / 1000U),
|
||||
static_cast<unsigned>(number % 1000U));
|
||||
key_verify_desc_ = lv_label_create(key_verify_panel_);
|
||||
std::string desc = resolve_contact_name(node_id) + "\nShare this number:\n";
|
||||
desc += num_buf;
|
||||
|
||||
@@ -39,6 +39,7 @@ static lv_color16_t* buf1 = nullptr;
|
||||
|
||||
static void disp_flush(lv_display_t* disp_drv, const lv_area_t* area, uint8_t* color_p)
|
||||
{
|
||||
static uint8_t s_tdeck_pro_flush_log_count = 0;
|
||||
#if LV_TEST_FLUSH_LOG
|
||||
static uint32_t s_flush_count = 0;
|
||||
static uint32_t s_flush_last_ms = 0;
|
||||
@@ -56,6 +57,33 @@ static void disp_flush(lv_display_t* disp_drv, const lv_area_t* area, uint8_t* c
|
||||
lv_draw_sw_rgb565_swap(color_p, len);
|
||||
#endif
|
||||
|
||||
#if defined(ARDUINO_T_DECK_PRO)
|
||||
if (s_tdeck_pro_flush_log_count < 8)
|
||||
{
|
||||
uint32_t blackish = 0;
|
||||
if (color_p != nullptr)
|
||||
{
|
||||
const uint16_t* pix = reinterpret_cast<const uint16_t*>(color_p);
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
{
|
||||
if (pix[i] == 0x0000)
|
||||
{
|
||||
blackish++;
|
||||
}
|
||||
}
|
||||
}
|
||||
Serial.printf("[LVGL][TDeckPro] flush #%u area=(%d,%d)-(%d,%d) len=%lu rgb565_black=%lu\n",
|
||||
static_cast<unsigned>(s_tdeck_pro_flush_log_count + 1),
|
||||
static_cast<int>(area->x1),
|
||||
static_cast<int>(area->y1),
|
||||
static_cast<int>(area->x2),
|
||||
static_cast<int>(area->y2),
|
||||
static_cast<unsigned long>(len),
|
||||
static_cast<unsigned long>(blackish));
|
||||
s_tdeck_pro_flush_log_count++;
|
||||
}
|
||||
#endif
|
||||
|
||||
plane->pushColors(area->x1, area->y1, w, h, (uint16_t*)color_p);
|
||||
|
||||
lv_display_flush_ready(disp_drv);
|
||||
@@ -118,7 +146,7 @@ static void touchpad_read(lv_indev_t* drv, lv_indev_data_t* data)
|
||||
if (touched)
|
||||
{
|
||||
input::MorseEngine::notifyTouch();
|
||||
#if defined(ARDUINO_T_DECK)
|
||||
#if defined(ARDUINO_T_DECK) || defined(ARDUINO_T_DECK_PRO)
|
||||
// T-Deck: touch can wake/show the screen saver, but only SPACE enters the main menu.
|
||||
if (isScreenSaverActive())
|
||||
{
|
||||
@@ -172,7 +200,7 @@ static void lv_encoder_read(lv_indev_t* drv, lv_indev_data_t* data)
|
||||
{
|
||||
auto* plane = (LilyGo_Display*)lv_indev_get_user_data(drv);
|
||||
RotaryMsg_t msg = plane->getRotary();
|
||||
#if defined(ARDUINO_T_DECK)
|
||||
#if defined(ARDUINO_T_DECK) || defined(ARDUINO_T_DECK_PRO)
|
||||
data->enc_diff = 0;
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
#endif
|
||||
@@ -184,7 +212,7 @@ static void lv_encoder_read(lv_indev_t* drv, lv_indev_data_t* data)
|
||||
{
|
||||
wakeScreenSaver();
|
||||
}
|
||||
#if defined(ARDUINO_T_DECK)
|
||||
#if defined(ARDUINO_T_DECK) || defined(ARDUINO_T_DECK_PRO)
|
||||
// T-Deck path already reset above.
|
||||
#else
|
||||
data->enc_diff = 0;
|
||||
@@ -196,7 +224,7 @@ static void lv_encoder_read(lv_indev_t* drv, lv_indev_data_t* data)
|
||||
// If GPS is loading tiles, ignore input
|
||||
if (isGPSLoadingTiles())
|
||||
{
|
||||
#if !defined(ARDUINO_T_DECK)
|
||||
#if !defined(ARDUINO_T_DECK) && !defined(ARDUINO_T_DECK_PRO)
|
||||
data->enc_diff = 0;
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
#endif
|
||||
@@ -224,7 +252,7 @@ static void lv_encoder_read(lv_indev_t* drv, lv_indev_data_t* data)
|
||||
return;
|
||||
}
|
||||
|
||||
#if !defined(ARDUINO_T_DECK)
|
||||
#if !defined(ARDUINO_T_DECK) && !defined(ARDUINO_T_DECK_PRO)
|
||||
data->enc_diff = 0;
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
#endif
|
||||
@@ -246,7 +274,7 @@ static void lv_encoder_read(lv_indev_t* drv, lv_indev_data_t* data)
|
||||
data->enc_diff = -1;
|
||||
break;
|
||||
default:
|
||||
#if !defined(ARDUINO_T_DECK)
|
||||
#if !defined(ARDUINO_T_DECK) && !defined(ARDUINO_T_DECK_PRO)
|
||||
data->state = LV_INDEV_STATE_RELEASED;
|
||||
#endif
|
||||
break;
|
||||
@@ -271,30 +299,18 @@ static void keypad_read(lv_indev_t* drv, lv_indev_data_t* data)
|
||||
// which could cause spurious wake閳姀nterFromScreenSaver() without user input.
|
||||
if (isScreenSleeping() || isScreenSaverActive())
|
||||
{
|
||||
if (state == KEYBOARD_PRESSED || state == KEYBOARD_RELEASED)
|
||||
if (state == KEYBOARD_PRESSED)
|
||||
{
|
||||
#if defined(ARDUINO_T_DECK)
|
||||
// T-Deck policy: any key can wake into the saver shell, and SPACE can continue into the main menu.
|
||||
// Accept both press/release for SPACE so a quick tap from sleep can still land in the menu.
|
||||
// Keyboard wake policy: any key can wake into the saver shell, but
|
||||
// only SPACE is allowed to continue into the main menu.
|
||||
if (isScreenSaverActive() && c == ' ')
|
||||
{
|
||||
enterFromScreenSaver();
|
||||
}
|
||||
else if (state == KEYBOARD_PRESSED)
|
||||
else
|
||||
{
|
||||
wakeScreenSaver();
|
||||
}
|
||||
#else
|
||||
// Other keyboard devices keep the original behavior:
|
||||
// first key wakes the screen saver, and any key exits it.
|
||||
if (state == KEYBOARD_PRESSED)
|
||||
{
|
||||
if (isScreenSaverActive())
|
||||
enterFromScreenSaver();
|
||||
else
|
||||
wakeScreenSaver();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
data->state = LV_INDEV_STATE_REL; // Don't pass key to UI
|
||||
return;
|
||||
@@ -306,7 +322,7 @@ static void keypad_read(lv_indev_t* drv, lv_indev_data_t* data)
|
||||
walkie::on_key_event(c, state);
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_T_DECK)
|
||||
#if defined(ARDUINO_T_DECK) || defined(ARDUINO_T_DECK_PRO)
|
||||
if (state == KEYBOARD_PRESSED && c == ' ' && ui_get_active_app() == nullptr)
|
||||
{
|
||||
updateUserActivity();
|
||||
|
||||
@@ -211,7 +211,7 @@ void init_screen_saver()
|
||||
s_screen_saver_hint_label = lv_label_create(s_screen_saver_layer);
|
||||
lv_obj_set_style_text_color(s_screen_saver_hint_label, lv_color_hex(0x8A6A3A), 0);
|
||||
lv_obj_set_style_text_font(s_screen_saver_hint_label, &lv_font_montserrat_14, 0);
|
||||
#if defined(ARDUINO_T_DECK)
|
||||
#if defined(ARDUINO_T_DECK) || defined(ARDUINO_T_DECK_PRO)
|
||||
lv_label_set_text(s_screen_saver_hint_label, "Press SPACE to enter main menu");
|
||||
#else
|
||||
lv_label_set_text(s_screen_saver_hint_label, "Press SPACE to enter");
|
||||
@@ -412,12 +412,12 @@ void wakeScreenSaver()
|
||||
if (xSemaphoreTake(s_activity_mutex, pdMS_TO_TICKS(10)) == pdTRUE)
|
||||
{
|
||||
was_sleeping = s_screen_sleeping;
|
||||
// Treat the wake gesture as real activity so the background sleep
|
||||
// task doesn't immediately force the panel back off before the
|
||||
// 3-second saver window expires.
|
||||
s_last_user_activity_time = millis();
|
||||
s_screen_saver_active = true;
|
||||
s_screen_sleeping = false;
|
||||
// Keep the runtime in the logical sleep state while the transient
|
||||
// saver is visible. This matches the 0.1.13 behavior: waking only
|
||||
// shows the saver shell, and if the user does nothing for 3s we go
|
||||
// right back to sleep instead of being treated as fully awake.
|
||||
s_screen_sleeping = true;
|
||||
xSemaphoreGive(s_activity_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -803,7 +803,11 @@ void render_line_from_buffer(uint8_t line_rgb[320][4], e_mode mode, uint16_t lin
|
||||
|
||||
void sstv_task(void*)
|
||||
{
|
||||
#if defined(TRAIL_MATE_ESP_BOARD_TAB5)
|
||||
TLoRaPagerBoard* board = TLoRaPagerBoard::getInstance();
|
||||
#else
|
||||
boards::tlora_pager::TLoRaPagerBoard* board = boards::tlora_pager::TLoRaPagerBoard::getInstance();
|
||||
#endif
|
||||
int prev_volume = -1;
|
||||
bool prev_out_mute = false;
|
||||
bool restore_amp = false;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "boards/tab5/platform_esp_board_runtime.h"
|
||||
#elif defined(TRAIL_MATE_ESP_BOARD_T_DISPLAY_P4)
|
||||
#include "boards/t_display_p4/platform_esp_board_runtime.h"
|
||||
#elif defined(ARDUINO_T_DECK_PRO)
|
||||
#include "boards/tdeck_pro/platform_esp_board_runtime.h"
|
||||
#elif defined(ARDUINO_T_DECK)
|
||||
#include "boards/tdeck/platform_esp_board_runtime.h"
|
||||
#elif defined(ARDUINO_T_WATCH_S3)
|
||||
|
||||
@@ -496,17 +496,15 @@ void append_bytes_and_process(const uint8_t* buffer, int length, char* line_buff
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_ext5v_enabled()
|
||||
bool configure_uart_hardware()
|
||||
{
|
||||
#if defined(TRAIL_MATE_ESP_BOARD_TAB5)
|
||||
(void)::boards::tab5::Tab5Board::instance().acquireExt5vRail("gps_runtime");
|
||||
#endif
|
||||
}
|
||||
|
||||
void configure_uart_hardware()
|
||||
{
|
||||
#if defined(TRAIL_MATE_ESP_BOARD_TAB5)
|
||||
(void)::boards::tab5::Tab5Board::instance().configureGpsUart();
|
||||
if (!::boards::tab5::Tab5Board::instance().configureGpsUart())
|
||||
{
|
||||
ESP_LOGE(kTag, "Tab5 GNSS UART setup failed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
const auto gps_uart = gps_uart_pins();
|
||||
uart_config_t config{};
|
||||
@@ -521,14 +519,14 @@ void configure_uart_hardware()
|
||||
ESP_ERROR_CHECK(uart_set_pin(static_cast<uart_port_t>(gps_uart.port), gps_uart.tx, gps_uart.rx, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
|
||||
ESP_ERROR_CHECK(uart_driver_install(static_cast<uart_port_t>(gps_uart.port), 4096, 0, 0, nullptr, 0));
|
||||
ESP_LOGI(kTag, "GNSS UART configured: port=%d tx=%d rx=%d baud=%d", gps_uart.port, gps_uart.tx, gps_uart.rx, config.baud_rate);
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void teardown_uart_hardware()
|
||||
{
|
||||
#if defined(TRAIL_MATE_ESP_BOARD_TAB5)
|
||||
::boards::tab5::Tab5Board::instance().teardownGpsUart();
|
||||
::boards::tab5::Tab5Board::instance().releaseExt5vRail("gps_runtime");
|
||||
// Tab5 keeps GNSS power and UART under board ownership for the whole runtime.
|
||||
#else
|
||||
const auto gps_uart = gps_uart_pins();
|
||||
if (gps_uart.port >= 0)
|
||||
@@ -540,9 +538,18 @@ void teardown_uart_hardware()
|
||||
|
||||
void worker_task(void*)
|
||||
{
|
||||
ensure_ext5v_enabled();
|
||||
vTaskDelay(pdMS_TO_TICKS(kProbeWarmupMs));
|
||||
configure_uart_hardware();
|
||||
if (!configure_uart_hardware())
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_mutex);
|
||||
s_runtime.powered = false;
|
||||
s_runtime.stop_requested = false;
|
||||
s_runtime.probe_requested = false;
|
||||
s_runtime.probe_deadline_ms = 0;
|
||||
s_runtime.worker_handle = nullptr;
|
||||
vTaskDelete(nullptr);
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_mutex);
|
||||
s_runtime.powered = true;
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
[env:tdeck_pro_a7682e]
|
||||
extends = arduino_base
|
||||
board = T-Deck-Pro
|
||||
upload_speed = 921600
|
||||
build_flags =
|
||||
${arduino_base.build_flags}
|
||||
-DBOARD_HAS_PSRAM=1
|
||||
-DARDUINO_T_DECK_PRO
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DARDUINO_LILYGO_LORA_SX1262
|
||||
-DSCREEN_WIDTH=240
|
||||
-DSCREEN_HEIGHT=320
|
||||
-DSENSOR_INT=21
|
||||
-DGPS_TX=43
|
||||
-DGPS_RX=44
|
||||
-DLORA_CS=3
|
||||
-DLORA_BUSY=6
|
||||
-DLORA_RST=4
|
||||
-DLORA_IRQ=5
|
||||
-DSD_CS=48
|
||||
-DDISP_CS=34
|
||||
-DDISP_DC=35
|
||||
-DDISP_RST=-1
|
||||
-DDISP_BUSY=37
|
||||
-DBOARD_TOUCH_INT=12
|
||||
-DUSING_INPUT_DEV_TOUCHPAD
|
||||
-DUSING_INPUT_DEV_KEYBOARD
|
||||
-DTRAIL_MATE_TDECK_PRO=1
|
||||
-DTRAIL_MATE_TDECK_PRO_A7682E=1
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
zinggjm/GxEPD2 @ 1.5.9
|
||||
adafruit/Adafruit TCA8418 @ ^1.0.2
|
||||
adafruit/Adafruit BusIO @ ^1.17.0
|
||||
mikalhart/TinyGPSPlus @ 1.0.3
|
||||
|
||||
[env:tdeck_pro_pcm512a]
|
||||
extends = arduino_base
|
||||
board = T-Deck-Pro
|
||||
upload_speed = 921600
|
||||
build_flags =
|
||||
${arduino_base.build_flags}
|
||||
-DBOARD_HAS_PSRAM=1
|
||||
-DARDUINO_T_DECK_PRO
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DARDUINO_LILYGO_LORA_SX1262
|
||||
-DSCREEN_WIDTH=240
|
||||
-DSCREEN_HEIGHT=320
|
||||
-DSENSOR_INT=21
|
||||
-DGPS_TX=43
|
||||
-DGPS_RX=44
|
||||
-DLORA_CS=3
|
||||
-DLORA_BUSY=6
|
||||
-DLORA_RST=4
|
||||
-DLORA_IRQ=5
|
||||
-DSD_CS=48
|
||||
-DDISP_CS=34
|
||||
-DDISP_DC=35
|
||||
-DDISP_RST=-1
|
||||
-DDISP_BUSY=37
|
||||
-DBOARD_TOUCH_INT=12
|
||||
-DDAC_I2S_BCK=7
|
||||
-DDAC_I2S_WS=9
|
||||
-DDAC_I2S_DOUT=8
|
||||
-DUSING_INPUT_DEV_TOUCHPAD
|
||||
-DUSING_INPUT_DEV_KEYBOARD
|
||||
-DTRAIL_MATE_TDECK_PRO=1
|
||||
-DTRAIL_MATE_TDECK_PRO_PCM512A=1
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
zinggjm/GxEPD2 @ 1.5.9
|
||||
adafruit/Adafruit TCA8418 @ ^1.0.2
|
||||
adafruit/Adafruit BusIO @ ^1.17.0
|
||||
mikalhart/TinyGPSPlus @ 1.0.3
|
||||
Reference in new issue
Block a user