Compare commits

..

9 Commits

Author SHA1 Message Date
Ben Meadors
b77cafbab7 Merge branch 'master' into static-buffer-gps 2025-09-09 06:32:55 -05:00
Ben Meadors
803e96800e ATAK module should be disabled for non-TAK roles (#7928) 2025-09-08 17:21:55 -05:00
renovate[bot]
6c69780615 chore(deps): update meshtastic/device-ui digest to 3677476 (#7925)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-08 16:52:21 -05:00
Tom Fifield
d5bb566276 Only log good times. (It's not always a good time then) (#7904)
Further to https://github.com/meshtastic/firmware/pull/7897 ,
there was another log line that was triggering indiscriminantly on
GPS_INTERVAL_THRESHOLD .

Rather than logging a bad time 4000 times, let's just log one good time
when it is set.
2025-09-08 05:59:37 -05:00
Ben Meadors
4ed7043aea Remove string 2025-09-05 20:01:22 -05:00
Ben Meadors
7a48bd5e76 Revert "\Update src/gps/GPS.cpp"
This reverts commit 54d64e19f7.
2025-09-05 19:59:41 -05:00
Ben Meadors
a5bb0ee284 Merge branch 'master' into static-buffer-gps 2025-09-05 07:21:07 -05:00
Ben Meadors
54d64e19f7 \Update src/gps/GPS.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-05 07:20:29 -05:00
Ben Meadors
d095b6b92e Use char buffer for probeResponse 2025-09-05 07:15:12 -05:00
82 changed files with 922 additions and 3702 deletions

View File

@@ -76,7 +76,7 @@ bool loopCanSleep()
// Called just prior to starting Meshtastic. Allows for setting config values before startup.
void lateInitVariant()
{
portduino_config.logoutputlevel = level_error;
settingsMap[logoutputlevel] = level_error;
channelFile.channels[0] = meshtastic_Channel{
.has_settings = true,
.settings =
@@ -132,7 +132,7 @@ int portduino_main(int argc, char **argv); // Renamed "main" function from Mesht
// Start Meshtastic in a thread and wait till it has reached the ON state.
int LLVMFuzzerInitialize(int *argc, char ***argv)
{
portduino_config.maxtophone = 5;
settingsMap[maxtophone] = 5;
meshtasticThread = std::thread([program = *argv[0]]() {
char nodeIdStr[12];

View File

@@ -48,10 +48,6 @@ jobs:
python-version: 3.x
cache: pip
- run: pip install -U platformio
- name: Uncomment build epoch
shell: bash
run: |
sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
- name: Generate matrix
id: jsonStep
run: |

View File

@@ -1,52 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"]
],
"usb_product": "GAT562 Mesh Tracker Pro",
"mcu": "nrf52840",
"variant": "gat562_mesh_tracker_pro",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino", "freertos"],
"name": "GAT562 Mesh Tracker Pro",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "http://www.gat-iot.com/",
"vendor": "GAT"
}

View File

@@ -118,7 +118,7 @@ lib_deps =
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/233d18ef42e9d189f90fdfe621f0cd7edff2d221.zip
https://github.com/meshtastic/device-ui/archive/3677476c8a823ee85056b5fb1d146a3e193f8276.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]

View File

@@ -8,7 +8,6 @@
"replacements:all",
"workarounds:all"
],
"baseBranchPatterns": ["master"],
"forkProcessing": "enabled",
"ignoreDeps": [
"protobufs"

View File

@@ -146,7 +146,7 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess
{
int result;
#ifdef ARCH_PORTDUINO
bool utf = !portduino_config.ascii_logs;
bool utf = !settingsMap[ascii_logs];
#else
bool utf = true;
#endif

View File

@@ -38,46 +38,4 @@ const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaC
return useShortName ? "Custom" : "Invalid";
break;
}
}
const char *DisplayFormatters::getDeviceRole(meshtastic_Config_DeviceConfig_Role role)
{
switch (role) {
case meshtastic_Config_DeviceConfig_Role_CLIENT:
return "Client";
break;
case meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE:
return "Client Mute";
break;
case meshtastic_Config_DeviceConfig_Role_CLIENT_HIDDEN:
return "Client Hidden";
break;
case meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND:
return "Lost and Found";
break;
case meshtastic_Config_DeviceConfig_Role_TRACKER:
return "Tracker";
break;
case meshtastic_Config_DeviceConfig_Role_SENSOR:
return "Sensor";
break;
case meshtastic_Config_DeviceConfig_Role_TAK:
return "TAK";
break;
case meshtastic_Config_DeviceConfig_Role_TAK_TRACKER:
return "TAK Tracker";
break;
case meshtastic_Config_DeviceConfig_Role_ROUTER:
return "Router";
break;
case meshtastic_Config_DeviceConfig_Role_ROUTER_LATE:
return "Router Late";
break;
case meshtastic_Config_DeviceConfig_Role_REPEATER:
return "Repeater";
break;
default:
return "Unknown";
break;
}
}

View File

@@ -6,7 +6,4 @@ class DisplayFormatters
public:
static const char *getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
bool usePreset);
public:
static const char *getDeviceRole(meshtastic_Config_DeviceConfig_Role role);
};

View File

@@ -22,9 +22,6 @@ class GPSStatus : public Status
meshtastic_Position p = meshtastic_Position_init_default;
/// Time of last valid GPS fix (millis since boot)
uint32_t lastFixMillis = 0;
public:
GPSStatus() { statusType = STATUS_TYPE_GPS; }
@@ -86,9 +83,6 @@ class GPSStatus : public Status
uint32_t getNumSatellites() const { return p.sats_in_view; }
/// Return millis() when the last GPS fix occurred (0 = never)
uint32_t getLastFixMillis() const { return lastFixMillis; }
bool matches(const GPSStatus *newStatus) const
{
#ifdef GPS_DEBUG
@@ -120,9 +114,6 @@ class GPSStatus : public Status
if (isDirty) {
if (hasLock) {
// Record time of last valid GPS fix
lastFixMillis = millis();
// In debug logs, identify position by @timestamp:stage (stage 3 = notify)
LOG_DEBUG("New GPS pos@%x:3 lat=%f lon=%f alt=%d pdop=%.2f track=%.2f speed=%.2f sats=%d", p.timestamp,
p.latitude_i * 1e-7, p.longitude_i * 1e-7, p.altitude, p.PDOP * 1e-2, p.ground_track * 1e-5,

View File

@@ -57,7 +57,7 @@ size_t RedirectablePrint::vprintf(const char *logLevel, const char *format, va_l
#endif
#ifdef ARCH_PORTDUINO
bool color = !portduino_config.ascii_logs;
bool color = !settingsMap[ascii_logs];
#else
bool color = true;
#endif
@@ -99,7 +99,7 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
size_t r = 0;
#ifdef ARCH_PORTDUINO
bool color = !portduino_config.ascii_logs;
bool color = !settingsMap[ascii_logs];
#else
bool color = true;
#endif
@@ -288,7 +288,7 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...)
#if ARCH_PORTDUINO
// level trace is special, two possible ways to handle it.
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
if (portduino_config.traceFilename != "") {
if (settingsStrings[traceFilename] != "") {
va_list arg;
va_start(arg, format);
try {
@@ -297,18 +297,18 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...)
}
va_end(arg);
}
if (portduino_config.logoutputlevel < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
if (settingsMap[logoutputlevel] < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
delete[] newFormat;
return;
}
}
if (portduino_config.logoutputlevel < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) {
if (settingsMap[logoutputlevel] < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) {
delete[] newFormat;
return;
} else if (portduino_config.logoutputlevel < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) {
} else if (settingsMap[logoutputlevel] < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) {
delete[] newFormat;
return;
} else if (portduino_config.logoutputlevel < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) {
} else if (settingsMap[logoutputlevel] < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) {
delete[] newFormat;
return;
}

View File

@@ -28,14 +28,11 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
case INPUT_BROKER_USER_PRESS:
case INPUT_BROKER_ALT_PRESS:
case INPUT_BROKER_SELECT:
case INPUT_BROKER_SELECT_LONG:
playBeep(); // Confirmation feedback
break;
case INPUT_BROKER_UP:
case INPUT_BROKER_UP_LONG:
case INPUT_BROKER_DOWN:
case INPUT_BROKER_DOWN_LONG:
case INPUT_BROKER_LEFT:
case INPUT_BROKER_RIGHT:
playChirp(); // Navigation feedback

View File

@@ -431,7 +431,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define MESHTASTIC_EXCLUDE_SERIAL 1
#define MESHTASTIC_EXCLUDE_POWERSTRESS 1
#define MESHTASTIC_EXCLUDE_ADMIN 1
#define MESHTASTIC_EXCLUDE_AMBIENTLIGHTING 1
#endif
// // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled)

View File

@@ -1,5 +1,4 @@
#include <cstring> // Include for strstr
#include <string>
#include <vector>
#include "configuration.h"
@@ -808,14 +807,6 @@ bool GPS::setup()
} else {
LOG_INFO("GNSS module configuration saved!");
}
} else if (gnssModel == GNSS_MODEL_CM121) {
// only ask for RMC and GGA
// enable GGA
_serial_gps->write("$CFGMSG,0,0,1,1*1B\r\n");
delay(250);
// enable RMC
_serial_gps->write("$CFGMSG,0,4,1,1*1F\r\n");
delay(250);
}
didSerialInit = true;
}
@@ -1248,15 +1239,9 @@ GnssModel_t GPS::probe(int serialSpeed)
_serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n");
_serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n");
delay(20);
// Close NMEA sequences on CM121
_serial_gps->write("$CFGMSG,0,1,0,1*1B\r\n");
_serial_gps->write("$CFGMSG,0,2,0,1*18\r\n");
_serial_gps->write("$CFGMSG,0,3,0,1*19\r\n");
delay(20);
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121
std::vector<ChipInfo> unicore = {
{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}};
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A
std::vector<ChipInfo> unicore = {{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}};
PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500);
std::vector<ChipInfo> atgm = {
@@ -1384,34 +1369,42 @@ GnssModel_t GPS::probe(int serialSpeed)
GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipInfo> &responseMap)
{
String response = "";
char response[256] = {0}; // Fixed buffer instead of String
uint16_t responseLen = 0;
unsigned long start = millis();
while (millis() - start < timeout) {
if (_serial_gps->available()) {
response += (char)_serial_gps->read();
char c = _serial_gps->read();
if (response.endsWith(",") || response.endsWith("\r\n")) {
// Add char to buffer if there's space
if (responseLen < sizeof(response) - 1) {
response[responseLen++] = c;
response[responseLen] = '\0';
}
if (c == ',' || (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) {
#ifdef GPS_DEBUG
LOG_DEBUG(response.c_str());
LOG_DEBUG(response);
#endif
// check if we can see our chips
for (const auto &chipInfo : responseMap) {
if (strstr(response.c_str(), chipInfo.detectionString.c_str()) != nullptr) {
if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
LOG_INFO("%s detected", chipInfo.chipName.c_str());
return chipInfo.driver;
}
}
}
if (response.endsWith("\r\n")) {
response.trim();
response = ""; // Reset the response string for the next potential message
if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') {
// Reset the response buffer for the next potential message
responseLen = 0;
response[0] = '\0';
}
}
}
#ifdef GPS_DEBUG
LOG_DEBUG(response.c_str());
LOG_DEBUG(response);
#endif
return GNSS_MODEL_UNKNOWN; // Return empty string on timeout
return GNSS_MODEL_UNKNOWN; // Return unknown on timeout
}
GPS *GPS::createGps()
@@ -1436,7 +1429,7 @@ GPS *GPS::createGps()
_en_gpio = PIN_GPS_EN;
#endif
#ifdef ARCH_PORTDUINO
if (!portduino_config.has_gps)
if (!settingsMap[has_gps])
return nullptr;
#endif
if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all

View File

@@ -31,8 +31,7 @@ typedef enum {
GNSS_MODEL_MTK_PA1616S,
GNSS_MODEL_AG3335,
GNSS_MODEL_AG3352,
GNSS_MODEL_LS20031,
GNSS_MODEL_CM121
GNSS_MODEL_LS20031
} GnssModel_t;
typedef enum {

View File

@@ -130,15 +130,11 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
uint32_t printableEpoch = tv->tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
#ifdef BUILD_EPOCH
if (tv->tv_sec < BUILD_EPOCH) {
#ifdef GPS_DEBUG
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
#endif
return RTCSetResultInvalidTime;
} else if (tv->tv_sec > (BUILD_EPOCH + FORTY_YEARS)) {
#ifdef GPS_DEBUG
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH,
BUILD_EPOCH + FORTY_YEARS);
#endif
return RTCSetResultInvalidTime;
}
#endif
@@ -256,15 +252,11 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
#ifdef BUILD_EPOCH
if (tv.tv_sec < BUILD_EPOCH) {
#ifdef GPS_DEBUG
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
#endif
return RTCSetResultInvalidTime;
} else if (tv.tv_sec > (BUILD_EPOCH + FORTY_YEARS)) {
#ifdef GPS_DEBUG
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch, BUILD_EPOCH,
BUILD_EPOCH + FORTY_YEARS);
#endif
return RTCSetResultInvalidTime;
}
#endif

View File

@@ -216,44 +216,6 @@ void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t
ui->update();
}
void Screen::showTextInput(const char *header, const char *initialText, uint32_t durationMs,
std::function<void(const std::string &)> textCallback)
{
LOG_INFO("showTextInput called with header='%s', durationMs=%d", header ? header : "NULL", durationMs);
if (NotificationRenderer::virtualKeyboard) {
delete NotificationRenderer::virtualKeyboard;
NotificationRenderer::virtualKeyboard = nullptr;
}
NotificationRenderer::textInputCallback = nullptr;
NotificationRenderer::virtualKeyboard = new VirtualKeyboard();
if (header) {
NotificationRenderer::virtualKeyboard->setHeader(header);
}
if (initialText) {
NotificationRenderer::virtualKeyboard->setInputText(initialText);
}
// Set up callback with safer cleanup mechanism
NotificationRenderer::textInputCallback = textCallback;
NotificationRenderer::virtualKeyboard->setCallback([textCallback](const std::string &text) { textCallback(text); });
// Store the message and set the expiration timestamp (use same pattern as other notifications)
strncpy(NotificationRenderer::alertBannerMessage, header ? header : "Text Input", 255);
NotificationRenderer::alertBannerMessage[255] = '\0';
NotificationRenderer::alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs;
NotificationRenderer::pauseBanner = false;
NotificationRenderer::current_notification_type = notificationTypeEnum::text_input;
// Set the overlay using the same pattern as other notification types
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
ui->setTargetFPS(60);
ui->update();
}
static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
uint8_t module_frame;
@@ -370,7 +332,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif ARCH_PORTDUINO
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
if (portduino_config.displayPanel != no_screen) {
if (settingsMap[displayPanel] != no_screen) {
LOG_DEBUG("Make TFTDisplay!");
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
@@ -618,7 +580,7 @@ void Screen::setup()
#if ARCH_PORTDUINO
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
if (portduino_config.touchscreenModule) {
if (settingsMap[touchscreenModule]) {
touchScreenImpl1 =
new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast<TFTDisplay *>(dispdev)->getTouch);
touchScreenImpl1->init();
@@ -751,19 +713,13 @@ int32_t Screen::runOnce()
handleSetOn(false);
break;
case Cmd::ON_PRESS:
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
handleOnPress();
}
handleOnPress();
break;
case Cmd::SHOW_PREV_FRAME:
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
handleShowPrevFrame();
}
handleShowPrevFrame();
break;
case Cmd::SHOW_NEXT_FRAME:
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
handleShowNextFrame();
}
handleShowNextFrame();
break;
case Cmd::START_ALERT_FRAME: {
showingBootScreen = false; // this should avoid the edge case where an alert triggers before the boot screen goes away
@@ -785,9 +741,7 @@ int32_t Screen::runOnce()
NotificationRenderer::pauseBanner = false;
case Cmd::STOP_BOOT_SCREEN:
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
setFrames();
}
setFrames();
break;
case Cmd::NOOP:
break;
@@ -823,7 +777,6 @@ int32_t Screen::runOnce()
if (showingNormalScreen) {
// standard screen loop handling here
if (config.display.auto_screen_carousel_secs > 0 &&
NotificationRenderer::current_notification_type != notificationTypeEnum::text_input &&
!Throttle::isWithinTimespanMs(lastScreenTransition, config.display.auto_screen_carousel_secs * 1000)) {
// If an E-Ink display struggles with fast refresh, force carousel to use full refresh instead
@@ -914,11 +867,6 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver)
// Called when a frame should be added / removed, or custom frames should be cleared
void Screen::setFrames(FrameFocus focus)
{
// Block setFrames calls when virtual keyboard is active to prevent overlay interference
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
return;
}
uint8_t originalPosition = ui->getUiState()->currentFrame;
uint8_t previousFrameCount = framesetInfo.frameCount;
FramesetInfo fsi; // Location of specific frames, for applying focus parameter
@@ -941,91 +889,71 @@ void Screen::setFrames(FrameFocus focus)
}
#if defined(DISPLAY_CLOCK_FRAME)
if (!hiddenFrames.clock) {
fsi.positions.clock = numframes;
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
: graphics::ClockRenderer::drawDigitalClockFrame;
indicatorIcons.push_back(digital_icon_clock);
}
fsi.positions.clock = numframes;
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
: graphics::ClockRenderer::drawDigitalClockFrame;
indicatorIcons.push_back(digital_icon_clock);
#endif
// Declare this early so its available in FOCUS_PRESERVE block
bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message);
if (!hiddenFrames.home) {
fsi.positions.home = numframes;
normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
indicatorIcons.push_back(icon_home);
}
fsi.positions.home = numframes;
normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
indicatorIcons.push_back(icon_home);
fsi.positions.textMessage = numframes;
normalFrames[numframes++] = graphics::MessageRenderer::drawTextMessageFrame;
indicatorIcons.push_back(icon_mail);
#ifndef USE_EINK
if (!hiddenFrames.nodelist) {
fsi.positions.nodelist = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen;
indicatorIcons.push_back(icon_nodes);
}
fsi.positions.nodelist = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen;
indicatorIcons.push_back(icon_nodes);
#endif
// Show detailed node views only on E-Ink builds
#ifdef USE_EINK
if (!hiddenFrames.nodelist_lastheard) {
fsi.positions.nodelist_lastheard = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen;
indicatorIcons.push_back(icon_nodes);
}
if (!hiddenFrames.nodelist_hopsignal) {
fsi.positions.nodelist_hopsignal = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen;
indicatorIcons.push_back(icon_signal);
}
if (!hiddenFrames.nodelist_distance) {
fsi.positions.nodelist_distance = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen;
indicatorIcons.push_back(icon_distance);
}
fsi.positions.nodelist_lastheard = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawLastHeardScreen;
indicatorIcons.push_back(icon_nodes);
fsi.positions.nodelist_hopsignal = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawHopSignalScreen;
indicatorIcons.push_back(icon_signal);
fsi.positions.nodelist_distance = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawDistanceScreen;
indicatorIcons.push_back(icon_distance);
#endif
#if HAS_GPS
if (!hiddenFrames.nodelist_bearings) {
fsi.positions.nodelist_bearings = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
indicatorIcons.push_back(icon_list);
}
if (!hiddenFrames.gps) {
fsi.positions.gps = numframes;
normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
indicatorIcons.push_back(icon_compass);
}
fsi.positions.nodelist_bearings = numframes;
normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
indicatorIcons.push_back(icon_list);
fsi.positions.gps = numframes;
normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
indicatorIcons.push_back(icon_compass);
#endif
if (RadioLibInterface::instance && !hiddenFrames.lora) {
if (RadioLibInterface::instance) {
fsi.positions.lora = numframes;
normalFrames[numframes++] = graphics::DebugRenderer::drawLoRaFocused;
indicatorIcons.push_back(icon_radio);
}
if (!hiddenFrames.system) {
fsi.positions.system = numframes;
normalFrames[numframes++] = graphics::DebugRenderer::drawSystemScreen;
indicatorIcons.push_back(icon_system);
if (!dismissedFrames.memory) {
fsi.positions.memory = numframes;
normalFrames[numframes++] = graphics::DebugRenderer::drawMemoryUsage;
indicatorIcons.push_back(icon_memory);
}
#if !defined(DISPLAY_CLOCK_FRAME)
if (!hiddenFrames.clock) {
fsi.positions.clock = numframes;
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
: graphics::ClockRenderer::drawDigitalClockFrame;
indicatorIcons.push_back(digital_icon_clock);
}
fsi.positions.clock = numframes;
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
: graphics::ClockRenderer::drawDigitalClockFrame;
indicatorIcons.push_back(digital_icon_clock);
#endif
if (!hiddenFrames.chirpy) {
fsi.positions.chirpy = numframes;
normalFrames[numframes++] = graphics::DebugRenderer::drawChirpy;
indicatorIcons.push_back(small_chirpy);
}
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
if (!hiddenFrames.wifi && isWifiAvailable()) {
if (!dismissedFrames.wifi && isWifiAvailable()) {
fsi.positions.wifi = numframes;
normalFrames[numframes++] = graphics::DebugRenderer::drawDebugInfoWiFiTrampoline;
indicatorIcons.push_back(icon_wifi);
@@ -1067,29 +995,27 @@ void Screen::setFrames(FrameFocus focus)
if (numMeshNodes > 0)
numMeshNodes--;
if (!hiddenFrames.show_favorites) {
// Temporary array to hold favorite node frames
std::vector<FrameCallback> favoriteFrames;
// Temporary array to hold favorite node frames
std::vector<FrameCallback> favoriteFrames;
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
}
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
}
}
// Insert favorite frames *after* collecting them all
if (!favoriteFrames.empty()) {
fsi.positions.firstFavorite = numframes;
for (const auto &f : favoriteFrames) {
normalFrames[numframes++] = f;
indicatorIcons.push_back(icon_node);
}
fsi.positions.lastFavorite = numframes - 1;
} else {
fsi.positions.firstFavorite = 255;
fsi.positions.lastFavorite = 255;
// Insert favorite frames *after* collecting them all
if (!favoriteFrames.empty()) {
fsi.positions.firstFavorite = numframes;
for (const auto &f : favoriteFrames) {
normalFrames[numframes++] = f;
indicatorIcons.push_back(icon_node);
}
fsi.positions.lastFavorite = numframes - 1;
} else {
fsi.positions.firstFavorite = 255;
fsi.positions.lastFavorite = 255;
}
fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE
@@ -1128,7 +1054,7 @@ void Screen::setFrames(FrameFocus focus)
ui->switchToFrame(fsi.positions.clock);
break;
case FOCUS_SYSTEM:
ui->switchToFrame(fsi.positions.system);
ui->switchToFrame(fsi.positions.memory);
break;
case FOCUS_PRESERVE:
@@ -1156,101 +1082,30 @@ void Screen::setFrameImmediateDraw(FrameCallback *drawFrames)
setFastFramerate();
}
void Screen::toggleFrameVisibility(const std::string &frameName)
{
#ifndef USE_EINK
if (frameName == "nodelist") {
hiddenFrames.nodelist = !hiddenFrames.nodelist;
}
#endif
#ifdef USE_EINK
if (frameName == "nodelist_lastheard") {
hiddenFrames.nodelist_lastheard = !hiddenFrames.nodelist_lastheard;
}
if (frameName == "nodelist_hopsignal") {
hiddenFrames.nodelist_hopsignal = !hiddenFrames.nodelist_hopsignal;
}
if (frameName == "nodelist_distance") {
hiddenFrames.nodelist_distance = !hiddenFrames.nodelist_distance;
}
#endif
#if HAS_GPS
if (frameName == "nodelist_bearings") {
hiddenFrames.nodelist_bearings = !hiddenFrames.nodelist_bearings;
}
if (frameName == "gps") {
hiddenFrames.gps = !hiddenFrames.gps;
}
#endif
if (frameName == "lora") {
hiddenFrames.lora = !hiddenFrames.lora;
}
if (frameName == "clock") {
hiddenFrames.clock = !hiddenFrames.clock;
}
if (frameName == "show_favorites") {
hiddenFrames.show_favorites = !hiddenFrames.show_favorites;
}
if (frameName == "chirpy") {
hiddenFrames.chirpy = !hiddenFrames.chirpy;
}
}
bool Screen::isFrameHidden(const std::string &frameName) const
{
#ifndef USE_EINK
if (frameName == "nodelist")
return hiddenFrames.nodelist;
#endif
#ifdef USE_EINK
if (frameName == "nodelist_lastheard")
return hiddenFrames.nodelist_lastheard;
if (frameName == "nodelist_hopsignal")
return hiddenFrames.nodelist_hopsignal;
if (frameName == "nodelist_distance")
return hiddenFrames.nodelist_distance;
#endif
#if HAS_GPS
if (frameName == "nodelist_bearings")
return hiddenFrames.nodelist_bearings;
if (frameName == "gps")
return hiddenFrames.gps;
#endif
if (frameName == "lora")
return hiddenFrames.lora;
if (frameName == "clock")
return hiddenFrames.clock;
if (frameName == "show_favorites")
return hiddenFrames.show_favorites;
if (frameName == "chirpy")
return hiddenFrames.chirpy;
return false;
}
// Dismisses the currently displayed screen frame, if possible
// Relevant for text message, waypoint, others in future?
// Triggered with a CardKB keycombo
void Screen::hideCurrentFrame()
void Screen::dismissCurrentFrame()
{
uint8_t currentFrame = ui->getUiState()->currentFrame;
bool dismissed = false;
if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) {
LOG_INFO("Hide Text Message");
LOG_INFO("Dismiss Text Message");
devicestate.has_rx_text_message = false;
memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
} else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) {
LOG_DEBUG("Hide Waypoint");
LOG_DEBUG("Dismiss Waypoint");
devicestate.has_rx_waypoint = false;
hiddenFrames.waypoint = true;
dismissedFrames.waypoint = true;
dismissed = true;
} else if (currentFrame == framesetInfo.positions.wifi) {
LOG_DEBUG("Hide WiFi Screen");
hiddenFrames.wifi = true;
LOG_DEBUG("Dismiss WiFi Screen");
dismissedFrames.wifi = true;
dismissed = true;
} else if (currentFrame == framesetInfo.positions.lora) {
LOG_INFO("Hide LoRa");
hiddenFrames.lora = true;
} else if (currentFrame == framesetInfo.positions.memory) {
LOG_INFO("Dismiss Memory");
dismissedFrames.memory = true;
dismissed = true;
}
@@ -1402,7 +1257,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
// Outgoing message (likely sent from phone)
devicestate.has_rx_text_message = false;
memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
hiddenFrames.textMessage = true;
dismissedFrames.textMessage = true;
hasUnreadMessage = false; // Clear unread state when user replies
setFrames(FOCUS_PRESERVE); // Stay on same frame, silently update frame list
@@ -1458,11 +1313,6 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
// Triggered by MeshModules
int Screen::handleUIFrameEvent(const UIFrameEvent *event)
{
// Block UI frame events when virtual keyboard is active
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
return 0;
}
if (showingNormalScreen) {
// Regenerate the frameset, potentially honoring a module's internal requestFocus() call
if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET)
@@ -1485,16 +1335,6 @@ int Screen::handleInputEvent(const InputEvent *event)
if (!screenOn)
return 0;
// Handle text input notifications specially - pass input to virtual keyboard
if (NotificationRenderer::current_notification_type == notificationTypeEnum::text_input) {
NotificationRenderer::inEvent = *event;
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
setFastFramerate(); // Draw ASAP
ui->update();
return 0;
}
#ifdef USE_EINK // the screen is the last input handler, so if an event makes it here, we can assume it will prompt a screen draw.
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
@@ -1532,7 +1372,7 @@ int Screen::handleInputEvent(const InputEvent *event)
} else if (event->inputEvent == INPUT_BROKER_SELECT) {
if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
menuHandler::homeBaseMenu();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.system) {
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.memory) {
menuHandler::systemBaseMenu();
#if HAS_GPS
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.gps && gps) {
@@ -1541,7 +1381,7 @@ int Screen::handleInputEvent(const InputEvent *event)
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.clock) {
menuHandler::clockMenu();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
menuHandler::loraMenu();
menuHandler::LoraRegionPicker();
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
if (devicestate.rx_text_message.from) {
menuHandler::messageResponseMenu();

View File

@@ -12,7 +12,7 @@
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
namespace graphics
{
enum notificationTypeEnum { none, text_banner, selection_picker, node_picker, number_picker, text_input };
enum notificationTypeEnum { none, text_banner, selection_picker, node_picker, number_picker };
struct BannerOverlayOptions {
const char *message;
@@ -313,8 +313,6 @@ class Screen : public concurrency::OSThread
void showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback);
void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback);
void showTextInput(const char *header, const char *initialText, uint32_t durationMs,
std::function<void(const std::string &)> textCallback);
void requestMenu(graphics::menuHandler::screenMenus menuToShow)
{
@@ -593,11 +591,7 @@ class Screen : public concurrency::OSThread
void setSSLFrames();
// Dismiss the currently focussed frame, if possible (e.g. text message, waypoint)
void hideCurrentFrame();
// Menu-driven Show / Hide Toggle
void toggleFrameVisibility(const std::string &frameName);
bool isFrameHidden(const std::string &frameName) const;
void dismissCurrentFrame();
#ifdef USE_EINK
/// Draw an image to remain on E-Ink display after screen off
@@ -659,7 +653,7 @@ class Screen : public concurrency::OSThread
uint8_t settings = 255;
uint8_t wifi = 255;
uint8_t deviceFocused = 255;
uint8_t system = 255;
uint8_t memory = 255;
uint8_t gps = 255;
uint8_t home = 255;
uint8_t textMessage = 255;
@@ -669,7 +663,6 @@ class Screen : public concurrency::OSThread
uint8_t nodelist_distance = 255;
uint8_t nodelist_bearings = 255;
uint8_t clock = 255;
uint8_t chirpy = 255;
uint8_t firstFavorite = 255;
uint8_t lastFavorite = 255;
uint8_t lora = 255;
@@ -678,29 +671,12 @@ class Screen : public concurrency::OSThread
uint8_t frameCount = 0;
} framesetInfo;
struct hiddenFrames {
struct DismissedFrames {
bool textMessage = false;
bool waypoint = false;
bool wifi = false;
bool system = false;
bool home = false;
bool clock = false;
#ifndef USE_EINK
bool nodelist = false;
#endif
#ifdef USE_EINK
bool nodelist_lastheard = false;
bool nodelist_hopsignal = false;
bool nodelist_distance = false;
#endif
#if HAS_GPS
bool nodelist_bearings = false;
bool gps = false;
#endif
bool lora = false;
bool show_favorites = false;
bool chirpy = true;
} hiddenFrames;
bool memory = false;
} dismissedFrames;
/// Try to start drawing ASAP
void setFastFramerate();

View File

@@ -1,7 +1,6 @@
#include "graphics/SharedUIDisplay.h"
#include "RTC.h"
#include "graphics/ScreenFonts.h"
#include "graphics/draw/UIRenderer.h"
#include "main.h"
#include "meshtastic/config.pb.h"
#include "power.h"
@@ -17,10 +16,6 @@ void determineResolution(int16_t screenheight, int16_t screenwidth)
isHighResolution = true;
}
if (screenwidth > 128 && screenheight <= 64) {
isHighResolution = false;
}
// Special case for Heltec Wireless Tracker v1.1
if (screenwidth == 160 && screenheight == 80) {
isHighResolution = false;
@@ -58,7 +53,7 @@ void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w,
// *************************
// * Common Header Drawing *
// *************************
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert, bool show_date)
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool battery_only)
{
constexpr int HEADER_OFFSET_Y = 1;
y += HEADER_OFFSET_Y;
@@ -74,7 +69,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
const int screenW = display->getWidth();
const int screenH = display->getHeight();
if (!force_no_invert) {
if (!battery_only) {
// === Inverted Header Background ===
if (isInverted) {
display->setColor(BLACK);
@@ -192,28 +187,13 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int timeStrWidth = display->getStringWidth("12:34"); // Default alignment
int timeX = screenW - xOffset - timeStrWidth + 4;
if (rtc_sec > 0) {
if (rtc_sec > 0 && !battery_only) {
// === Build Time String ===
long hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY;
int hour = hms / SEC_PER_HOUR;
int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
snprintf(timeStr, sizeof(timeStr), "%d:%02d", hour, minute);
// === Build Date String ===
char datetimeStr[25];
UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, false);
char dateLine[40];
if (isHighResolution) {
snprintf(dateLine, sizeof(dateLine), "%s", datetimeStr);
} else {
if (hasUnreadMessage) {
snprintf(dateLine, sizeof(dateLine), "%s", &datetimeStr[5]);
} else {
snprintf(dateLine, sizeof(dateLine), "%s", &datetimeStr[2]);
}
}
if (config.display.use_12h_clock) {
bool isPM = hour >= 12;
hour %= 12;
@@ -222,11 +202,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
snprintf(timeStr, sizeof(timeStr), "%d:%02d%s", hour, minute, isPM ? "p" : "a");
}
if (show_date) {
timeStrWidth = display->getStringWidth(dateLine);
} else {
timeStrWidth = display->getStringWidth(timeStr);
}
timeStrWidth = display->getStringWidth(timeStr);
timeX = screenW - xOffset - timeStrWidth + 3;
// === Show Mail or Mute Icon to the Left of Time ===
@@ -253,7 +229,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int iconW = 16, iconH = 12;
int iconX = iconRightEdge - iconW;
int iconY = textY + (FONT_HEIGHT_SMALL - iconH) / 2 - 1;
if (isInverted && !force_no_invert) {
if (isInverted) {
display->setColor(WHITE);
display->fillRect(iconX - 1, iconY - 1, iconW + 3, iconH + 2);
display->setColor(BLACK);
@@ -268,7 +244,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
} else {
int iconX = iconRightEdge - (mail_width - 2);
int iconY = textY + (FONT_HEIGHT_SMALL - mail_height) / 2;
if (isInverted && !force_no_invert) {
if (isInverted) {
display->setColor(WHITE);
display->fillRect(iconX - 1, iconY - 1, mail_width + 2, mail_height + 2);
display->setColor(BLACK);
@@ -311,17 +287,10 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
}
}
if (show_date) {
// === Draw Date ===
display->drawString(timeX, textY, dateLine);
if (isBold)
display->drawString(timeX - 1, textY, dateLine);
} else {
// === Draw Time ===
display->drawString(timeX, textY, timeStr);
if (isBold)
display->drawString(timeX - 1, textY, timeStr);
}
// === Draw Time ===
display->drawString(timeX, textY, timeStr);
if (isBold)
display->drawString(timeX - 1, textY, timeStr);
} else {
// === No Time Available: Mail/Mute Icon Moves to Far Right ===

View File

@@ -49,8 +49,7 @@ void determineResolution(int16_t screenheight, int16_t screenwidth);
void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w, int16_t h, int16_t r);
// Shared battery/time/mail header
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool force_no_invert = false,
bool show_date = false);
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool battery_only = false);
const int *getTextPositions(OLEDDisplay *display);

View File

@@ -767,24 +767,24 @@ class LGFX : public lgfx::LGFX_Device
LGFX(void)
{
if (portduino_config.displayPanel == st7789)
if (settingsMap[displayPanel] == st7789)
_panel_instance = new lgfx::Panel_ST7789;
else if (portduino_config.displayPanel == st7735)
else if (settingsMap[displayPanel] == st7735)
_panel_instance = new lgfx::Panel_ST7735;
else if (portduino_config.displayPanel == st7735s)
else if (settingsMap[displayPanel] == st7735s)
_panel_instance = new lgfx::Panel_ST7735S;
else if (portduino_config.displayPanel == st7796)
else if (settingsMap[displayPanel] == st7796)
_panel_instance = new lgfx::Panel_ST7796;
else if (portduino_config.displayPanel == ili9341)
else if (settingsMap[displayPanel] == ili9341)
_panel_instance = new lgfx::Panel_ILI9341;
else if (portduino_config.displayPanel == ili9342)
else if (settingsMap[displayPanel] == ili9342)
_panel_instance = new lgfx::Panel_ILI9342;
else if (portduino_config.displayPanel == ili9488)
else if (settingsMap[displayPanel] == ili9488)
_panel_instance = new lgfx::Panel_ILI9488;
else if (portduino_config.displayPanel == hx8357d)
else if (settingsMap[displayPanel] == hx8357d)
_panel_instance = new lgfx::Panel_HX8357D;
#if defined(LGFX_SDL)
else if (portduino_config.displayPanel == x11) {
else if (settingsMap[displayPanel] == x11) {
_panel_instance = new lgfx::Panel_sdl;
}
#endif
@@ -795,61 +795,61 @@ class LGFX : public lgfx::LGFX_Device
auto buscfg = _bus_instance.config();
buscfg.spi_mode = 0;
buscfg.spi_host = portduino_config.display_spi_dev_int;
buscfg.spi_host = settingsMap[displayspidev];
buscfg.pin_dc = portduino_config.displayDC.pin; // Set SPI DC pin number (-1 = disable)
buscfg.pin_dc = settingsMap[displayDC]; // Set SPI DC pin number (-1 = disable)
_bus_instance.config(buscfg); // applies the set value to the bus.
_panel_instance->setBus(&_bus_instance); // set the bus on the panel.
auto cfg = _panel_instance->config(); // Gets a structure for display panel settings.
LOG_DEBUG("Width: %d, Height: %d", portduino_config.displayWidth, portduino_config.displayHeight);
cfg.pin_cs = portduino_config.displayCS.pin; // Pin number where CS is connected (-1 = disable)
cfg.pin_rst = portduino_config.displayReset.pin;
if (portduino_config.displayRotate) {
cfg.panel_width = portduino_config.displayHeight; // actual displayable width
cfg.panel_height = portduino_config.displayWidth; // actual displayable height
LOG_DEBUG("Width: %d, Height: %d", settingsMap[displayWidth], settingsMap[displayHeight]);
cfg.pin_cs = settingsMap[displayCS]; // Pin number where CS is connected (-1 = disable)
cfg.pin_rst = settingsMap[displayReset];
if (settingsMap[displayRotate]) {
cfg.panel_width = settingsMap[displayHeight]; // actual displayable width
cfg.panel_height = settingsMap[displayWidth]; // actual displayable height
} else {
cfg.panel_width = portduino_config.displayWidth; // actual displayable width
cfg.panel_height = portduino_config.displayHeight; // actual displayable height
cfg.panel_width = settingsMap[displayWidth]; // actual displayable width
cfg.panel_height = settingsMap[displayHeight]; // actual displayable height
}
cfg.offset_x = portduino_config.displayOffsetX; // Panel offset amount in X direction
cfg.offset_y = portduino_config.displayOffsetY; // Panel offset amount in Y direction
cfg.offset_rotation = portduino_config.displayOffsetRotate; // Rotation direction value offset 0~7 (4~7 is mirrored)
cfg.invert = portduino_config.displayInvert; // Set to true if the light/darkness of the panel is reversed
cfg.offset_x = settingsMap[displayOffsetX]; // Panel offset amount in X direction
cfg.offset_y = settingsMap[displayOffsetY]; // Panel offset amount in Y direction
cfg.offset_rotation = settingsMap[displayOffsetRotate]; // Rotation direction value offset 0~7 (4~7 is mirrored)
cfg.invert = settingsMap[displayInvert]; // Set to true if the light/darkness of the panel is reversed
_panel_instance->config(cfg);
// Configure settings for touch control.
if (portduino_config.touchscreenModule) {
if (portduino_config.touchscreenModule == xpt2046) {
if (settingsMap[touchscreenModule]) {
if (settingsMap[touchscreenModule] == xpt2046) {
_touch_instance = new lgfx::Touch_XPT2046;
} else if (portduino_config.touchscreenModule == stmpe610) {
} else if (settingsMap[touchscreenModule] == stmpe610) {
_touch_instance = new lgfx::Touch_STMPE610;
} else if (portduino_config.touchscreenModule == ft5x06) {
} else if (settingsMap[touchscreenModule] == ft5x06) {
_touch_instance = new lgfx::Touch_FT5x06;
}
auto touch_cfg = _touch_instance->config();
touch_cfg.pin_cs = portduino_config.touchscreenCS.pin;
touch_cfg.pin_cs = settingsMap[touchscreenCS];
touch_cfg.x_min = 0;
touch_cfg.x_max = portduino_config.displayHeight - 1;
touch_cfg.x_max = settingsMap[displayHeight] - 1;
touch_cfg.y_min = 0;
touch_cfg.y_max = portduino_config.displayWidth - 1;
touch_cfg.pin_int = portduino_config.touchscreenIRQ.pin;
touch_cfg.y_max = settingsMap[displayWidth] - 1;
touch_cfg.pin_int = settingsMap[touchscreenIRQ];
touch_cfg.bus_shared = true;
touch_cfg.offset_rotation = portduino_config.touchscreenRotate;
if (portduino_config.touchscreenI2CAddr != -1) {
touch_cfg.i2c_addr = portduino_config.touchscreenI2CAddr;
touch_cfg.offset_rotation = settingsMap[touchscreenRotate];
if (settingsMap[touchscreenI2CAddr] != -1) {
touch_cfg.i2c_addr = settingsMap[touchscreenI2CAddr];
} else {
touch_cfg.spi_host = portduino_config.touchscreen_spi_dev_int;
touch_cfg.spi_host = settingsMap[touchscreenspidev];
}
_touch_instance->config(touch_cfg);
_panel_instance->setTouch(_touch_instance);
}
#if defined(LGFX_SDL)
if (portduino_config.displayPanel == x11) {
if (settingsMap[displayPanel] == x11) {
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)_panel_instance;
sdl_panel_->setup();
sdl_panel_->addKeyCodeMapping(SDLK_RETURN, SDL_SCANCODE_KP_ENTER);
@@ -1115,10 +1115,10 @@ TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY g
backlightEnable = p;
#if ARCH_PORTDUINO
if (portduino_config.displayRotate) {
setGeometry(GEOMETRY_RAWMODE, portduino_config.displayWidth, portduino_config.displayWidth);
if (settingsMap[displayRotate]) {
setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayWidth], settingsMap[configNames::displayHeight]);
} else {
setGeometry(GEOMETRY_RAWMODE, portduino_config.displayHeight, portduino_config.displayHeight);
setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayHeight], settingsMap[configNames::displayWidth]);
}
#elif defined(SCREEN_ROTATE)
@@ -1240,7 +1240,7 @@ void TFTDisplay::sdlLoop()
#if defined(LGFX_SDL)
static int lastPressed = 0;
static int shuttingDown = false;
if (portduino_config.displayPanel == x11) {
if (settingsMap[displayPanel] == x11) {
lgfx::Panel_sdl *sdl_panel_ = (lgfx::Panel_sdl *)tft->_panel_instance;
if (sdl_panel_->loop() && !shuttingDown) {
LOG_WARN("Window Closed!");
@@ -1288,8 +1288,8 @@ void TFTDisplay::sendCommand(uint8_t com)
backlightEnable->set(true);
#if ARCH_PORTDUINO
display(true);
if (portduino_config.displayBacklight.pin > 0)
digitalWrite(portduino_config.displayBacklight.pin, TFT_BACKLIGHT_ON);
if (settingsMap[displayBacklight] > 0)
digitalWrite(settingsMap[displayBacklight], TFT_BACKLIGHT_ON);
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
tft->wakeup();
tft->powerSaveOff();
@@ -1312,8 +1312,8 @@ void TFTDisplay::sendCommand(uint8_t com)
backlightEnable->set(false);
#if ARCH_PORTDUINO
tft->clear();
if (portduino_config.displayBacklight.pin > 0)
digitalWrite(portduino_config.displayBacklight.pin, !TFT_BACKLIGHT_ON);
if (settingsMap[displayBacklight] > 0)
digitalWrite(settingsMap[displayBacklight], !TFT_BACKLIGHT_ON);
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
tft->sleep();
tft->powerSaveOn();

View File

@@ -1,738 +0,0 @@
#include "VirtualKeyboard.h"
#include "configuration.h"
#include "graphics/Screen.h"
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
#include "main.h"
#include <Arduino.h>
#include <vector>
namespace graphics
{
VirtualKeyboard::VirtualKeyboard() : cursorRow(0), cursorCol(0), lastActivityTime(millis())
{
initializeKeyboard();
// Set cursor to H(2, 5)
cursorRow = 2;
cursorCol = 5;
}
VirtualKeyboard::~VirtualKeyboard() {}
void VirtualKeyboard::initializeKeyboard()
{
// New 4 row, 11 column keyboard layout:
static const char LAYOUT[KEYBOARD_ROWS][KEYBOARD_COLS] = {{'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '\b'},
{'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '\n'},
{'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', ' '},
{'z', 'x', 'c', 'v', 'b', 'n', 'm', '.', ',', '?', '\x1b'}};
// Derive layout dimensions and assert they match the configured keyboard grid
constexpr int LAYOUT_ROWS = (int)(sizeof(LAYOUT) / sizeof(LAYOUT[0]));
constexpr int LAYOUT_COLS = (int)(sizeof(LAYOUT[0]) / sizeof(LAYOUT[0][0]));
static_assert(LAYOUT_ROWS == KEYBOARD_ROWS, "LAYOUT rows must equal KEYBOARD_ROWS");
static_assert(LAYOUT_COLS == KEYBOARD_COLS, "LAYOUT cols must equal KEYBOARD_COLS");
// Initialize all keys to empty first
for (int row = 0; row < LAYOUT_ROWS; row++) {
for (int col = 0; col < LAYOUT_COLS; col++) {
keyboard[row][col] = {0, VK_CHAR, 0, 0, 0, 0};
}
}
// Fill keyboard from the 2D layout
for (int row = 0; row < LAYOUT_ROWS; row++) {
for (int col = 0; col < LAYOUT_COLS; col++) {
char ch = LAYOUT[row][col];
// No empty slots in the simplified layout
VirtualKeyType type = VK_CHAR;
if (ch == '\b') {
type = VK_BACKSPACE;
} else if (ch == '\n') {
type = VK_ENTER;
} else if (ch == '\x1b') { // ESC
type = VK_ESC;
} else if (ch == ' ') {
type = VK_SPACE;
}
// Make action keys wider to fit text while keeping the last column aligned
uint8_t width = (type == VK_BACKSPACE || type == VK_ENTER || type == VK_SPACE) ? (KEY_WIDTH * 3) : KEY_WIDTH;
keyboard[row][col] = {ch, type, (uint8_t)(col * KEY_WIDTH), (uint8_t)(row * KEY_HEIGHT), width, KEY_HEIGHT};
}
}
}
void VirtualKeyboard::draw(OLEDDisplay *display, int16_t offsetX, int16_t offsetY)
{
// Repeat ticking is driven by NotificationRenderer once per frame
// Base styles
display->setColor(WHITE);
display->setFont(FONT_SMALL);
// Screen geometry
const int screenW = display->getWidth();
const int screenH = display->getHeight();
// Decide wide-screen mode: if there is comfortable width, allow taller keys and reserve fixed width for last column labels
// Heuristic: if screen width >= 200px (e.g., 240x135), treat as wide
const bool isWide = screenW >= 200;
// Determine last-column label max width
display->setFont(FONT_SMALL);
const int wENTER = display->getStringWidth("ENTER");
int lastColLabelW = wENTER; // ENTER is usually the widest
// Smaller padding on very small screens to avoid excessive whitespace
const int lastColPad = (screenW <= 128 ? 2 : 6);
const int reservedLastColW = lastColLabelW + lastColPad; // reserved width for last column keys
// Always reserve width for the rightmost text column to avoid overlap on small screens
int cellW = 0;
int leftoverW = 0;
{
const int leftCols = KEYBOARD_COLS - 1; // 10 input characters
int usableW = screenW - reservedLastColW;
if (usableW < leftCols) {
// Guard: ensure at least 1px per left cell if labels are extremely wide (unlikely)
usableW = leftCols;
}
cellW = usableW / leftCols;
leftoverW = usableW - cellW * leftCols; // distribute extra pixels over left columns (left to right)
}
// Dynamic key geometry
int cellH = KEY_HEIGHT;
int keyboardStartY = 0;
if (screenH <= 64) {
const int headerHeight = headerText.empty() ? 0 : (FONT_HEIGHT_SMALL - 2);
const int gapBelowHeader = 0;
const int singleLineBoxHeight = FONT_HEIGHT_SMALL;
const int gapAboveKeyboard = 0;
keyboardStartY = offsetY + headerHeight + gapBelowHeader + singleLineBoxHeight + gapAboveKeyboard;
if (keyboardStartY < 0)
keyboardStartY = 0;
if (keyboardStartY > screenH)
keyboardStartY = screenH;
int keyboardHeight = screenH - keyboardStartY;
cellH = std::max(1, keyboardHeight / KEYBOARD_ROWS);
} else if (isWide) {
// For wide screens (e.g., T114 240x135), prefer square keys: height equals left-column key width.
cellH = std::max((int)KEY_HEIGHT, cellW);
// Guarantee at least 2 lines of input are visible by reducing cell height minimally if needed.
// Replicate the spacing used in drawInputArea(): headerGap=1, box-to-header gap=1, gap above keyboard=1
display->setFont(FONT_SMALL);
const int headerHeight = headerText.empty() ? 0 : (FONT_HEIGHT_SMALL + 1);
const int headerToBoxGap = 1;
const int gapAboveKb = 1;
const int minBoxHeightForTwoLines = 2 * FONT_HEIGHT_SMALL + 2; // inner 1px top/bottom
int maxKeyboardHeight = screenH - (offsetY + headerHeight + headerToBoxGap + minBoxHeightForTwoLines + gapAboveKb);
int maxCellHAllowed = maxKeyboardHeight / KEYBOARD_ROWS;
if (maxCellHAllowed < (int)KEY_HEIGHT)
maxCellHAllowed = KEY_HEIGHT;
if (maxCellHAllowed > 0 && cellH > maxCellHAllowed) {
cellH = maxCellHAllowed;
}
// Keyboard placement from bottom for wide screens
int keyboardHeight = KEYBOARD_ROWS * cellH;
keyboardStartY = screenH - keyboardHeight;
if (keyboardStartY < 0)
keyboardStartY = 0;
} else {
// Default (non-wide, non-64px) behavior: use key height heuristic and place at bottom
cellH = KEY_HEIGHT;
int keyboardHeight = KEYBOARD_ROWS * cellH;
keyboardStartY = screenH - keyboardHeight;
if (keyboardStartY < 0)
keyboardStartY = 0;
}
// Draw input area above keyboard
drawInputArea(display, offsetX, offsetY, keyboardStartY);
// Precompute per-column x and width with leftover distributed over left columns for even spacing
int colX[KEYBOARD_COLS];
int colW[KEYBOARD_COLS];
int runningX = offsetX;
for (int col = 0; col < KEYBOARD_COLS - 1; ++col) {
int wcol = cellW + (col < leftoverW ? 1 : 0);
colX[col] = runningX;
colW[col] = wcol;
runningX += wcol;
}
// Last column
colX[KEYBOARD_COLS - 1] = runningX;
colW[KEYBOARD_COLS - 1] = reservedLastColW;
// Draw keyboard grid
for (int row = 0; row < KEYBOARD_ROWS; row++) {
for (int col = 0; col < KEYBOARD_COLS; col++) {
const VirtualKey &k = keyboard[row][col];
if (k.character != 0 || k.type != VK_CHAR) {
const bool isLastCol = (col == KEYBOARD_COLS - 1);
int x = colX[col];
int w = colW[col];
int y = offsetY + keyboardStartY + row * cellH;
int h = cellH;
bool selected = (row == cursorRow && col == cursorCol);
drawKey(display, k, selected, x, y, (uint8_t)w, (uint8_t)h, isLastCol);
}
}
}
}
void VirtualKeyboard::drawInputArea(OLEDDisplay *display, int16_t offsetX, int16_t offsetY, int16_t keyboardStartY)
{
display->setColor(WHITE);
const int screenWidth = display->getWidth();
const int screenHeight = display->getHeight();
// Use the standard small font metrics for input box sizing (restore original size)
const int inputLineH = FONT_HEIGHT_SMALL;
// Header uses the standard small (which may be larger on big screens)
display->setFont(FONT_SMALL);
int headerHeight = 0;
if (!headerText.empty()) {
// Draw header and reserve exact font height (plus a tighter gap) to maximize input area
display->drawString(offsetX + 2, offsetY, headerText.c_str());
if (screenHeight <= 64) {
headerHeight = FONT_HEIGHT_SMALL - 2; // 11px
} else {
headerHeight = FONT_HEIGHT_SMALL; // no extra padding baked in
}
}
const int boxX = offsetX;
const int boxWidth = screenWidth;
int boxY;
int boxHeight;
if (screenHeight <= 64) {
const int gapBelowHeader = 0;
const int fixedBoxHeight = inputLineH;
const int gapAboveKeyboard = 0;
boxY = offsetY + headerHeight + gapBelowHeader;
boxHeight = fixedBoxHeight;
if (boxY + boxHeight + gapAboveKeyboard > keyboardStartY) {
int over = boxY + boxHeight + gapAboveKeyboard - keyboardStartY;
boxHeight = std::max(1, fixedBoxHeight - over);
}
} else {
const int gapBelowHeader = 1;
int gapAboveKeyboard = 1;
int tmpBoxY = offsetY + headerHeight + gapBelowHeader;
const int minBoxHeight = inputLineH + 2;
int availableH = keyboardStartY - tmpBoxY - gapAboveKeyboard;
if (availableH < minBoxHeight)
availableH = minBoxHeight;
boxY = tmpBoxY;
boxHeight = availableH;
}
// Draw box border
display->drawRect(boxX, boxY, boxWidth, boxHeight);
display->setFont(FONT_SMALL);
// Text rendering: multi-line if space allows (>= 2 lines), else single-line with leading ellipsis
const int textX = boxX + 2;
const int maxTextWidth = boxWidth - 4;
const int maxLines = (boxHeight - 2) / inputLineH;
if (maxLines >= 2) {
// Inner bounds for caret clamping
const int innerLeft = boxX + 1;
const int innerRight = boxX + boxWidth - 2;
const int innerTop = boxY + 1;
const int innerBottom = boxY + boxHeight - 2;
// Wrap text greedily into lines that fit maxTextWidth
std::vector<std::string> lines;
{
std::string remaining = inputText;
while (!remaining.empty()) {
int bestLen = 0;
for (int len = 1; len <= (int)remaining.size(); ++len) {
int w = display->getStringWidth(remaining.substr(0, len).c_str());
if (w <= maxTextWidth)
bestLen = len;
else
break;
}
if (bestLen == 0) {
// At least show one character to make progress
bestLen = 1;
}
lines.emplace_back(remaining.substr(0, bestLen));
remaining.erase(0, bestLen);
}
}
const bool scrolledUp = ((int)lines.size() > maxLines);
int caretX = textX;
int caretY = innerTop;
// Leave a small top gap to render '...' without replacing the first line
const int topInset = 2;
const int lineStep = std::max(1, inputLineH - 1); // slightly tighter than font height
int lineY = innerTop + topInset;
if (scrolledUp) {
// Draw three small dots centered horizontally, vertically at the midpoint of the gap
// between the inner top and the first line's top baseline. This avoids using a tall glyph.
const int firstLineTop = lineY; // baseline top for the first visible line
const int gapMidY = innerTop + (firstLineTop - innerTop) / 2 + 1; // shift down 1px as requested
const int centerX = boxX + boxWidth / 2;
const int dotSpacing = 3; // px between dots
const int dotSize = 1; // small square dot
display->fillRect(centerX - dotSpacing, gapMidY, dotSize, dotSize);
display->fillRect(centerX, gapMidY, dotSize, dotSize);
display->fillRect(centerX + dotSpacing, gapMidY, dotSize, dotSize);
}
// How many lines fit with our top inset and tighter step
const int linesCapacity = std::max(1, (innerBottom - lineY + 1) / lineStep);
const int linesToShow = std::min((int)lines.size(), linesCapacity);
const int startIndex = scrolledUp ? ((int)lines.size() - linesToShow) : 0;
for (int i = 0; i < linesToShow; ++i) {
const std::string &chunk = lines[startIndex + i];
display->drawString(textX, lineY, chunk.c_str());
caretX = textX + display->getStringWidth(chunk.c_str());
caretY = lineY;
lineY += lineStep;
}
// Draw caret at end of the last visible line
int caretPadY = 2;
if (boxHeight >= inputLineH + 4)
caretPadY = 3;
int cursorTop = caretY + caretPadY;
// Use lineStep so caret height matches the row spacing
int cursorH = lineStep - caretPadY * 2;
if (cursorH < 1)
cursorH = 1;
// Clamp vertical bounds to stay inside the inner rect
if (cursorTop < innerTop)
cursorTop = innerTop;
if (cursorTop + cursorH - 1 > innerBottom)
cursorH = innerBottom - cursorTop + 1;
if (cursorH < 1)
cursorH = 1;
// Only draw if cursor is inside inner bounds
if (caretX >= innerLeft && caretX <= innerRight) {
display->drawVerticalLine(caretX, cursorTop, cursorH);
}
} else {
std::string displayText = inputText;
int textW = display->getStringWidth(displayText.c_str());
std::string scrolled = displayText;
if (textW > maxTextWidth) {
// Trim from the left until it fits
while (textW > maxTextWidth && !scrolled.empty()) {
scrolled.erase(0, 1);
textW = display->getStringWidth(scrolled.c_str());
}
// Add leading ellipsis and ensure it still fits
if (scrolled != displayText) {
scrolled = "..." + scrolled;
textW = display->getStringWidth(scrolled.c_str());
// If adding ellipsis causes overflow, trim more after the ellipsis
while (textW > maxTextWidth && scrolled.size() > 3) {
scrolled.erase(3, 1); // remove chars after the ellipsis
textW = display->getStringWidth(scrolled.c_str());
}
}
} else {
// Keep textW in sync with what we draw
textW = display->getStringWidth(scrolled.c_str());
}
int textY;
if (screenHeight <= 64) {
textY = boxY + (boxHeight - inputLineH) / 2;
} else {
const int innerLeft = boxX + 1;
const int innerRight = boxX + boxWidth - 2;
const int innerTop = boxY + 1;
const int innerBottom = boxY + boxHeight - 2;
// Center text vertically within inner box for single-line, then clamp so it never overlaps borders
int innerH = innerBottom - innerTop + 1;
textY = innerTop + std::max(0, (innerH - inputLineH) / 2);
// Clamp fully inside the inner rect
if (textY < innerTop)
textY = innerTop;
int maxTop = innerBottom - inputLineH + 1;
if (textY > maxTop)
textY = maxTop;
}
if (!scrolled.empty()) {
display->drawString(textX, textY, scrolled.c_str());
}
int cursorX = textX + textW;
if (screenHeight > 64) {
const int innerRight = boxX + boxWidth - 2;
if (cursorX > innerRight)
cursorX = innerRight;
}
int cursorTop, cursorH;
if (screenHeight <= 64) {
cursorH = 10;
cursorTop = boxY + (boxHeight - cursorH) / 2;
} else {
const int innerLeft = boxX + 1;
const int innerRight = boxX + boxWidth - 2;
const int innerTop = boxY + 1;
const int innerBottom = boxY + boxHeight - 2;
cursorTop = boxY + 2;
cursorH = boxHeight - 4;
if (cursorH < 1)
cursorH = 1;
if (cursorTop < innerTop)
cursorTop = innerTop;
if (cursorTop + cursorH - 1 > innerBottom)
cursorH = innerBottom - cursorTop + 1;
if (cursorH < 1)
cursorH = 1;
if (cursorX < innerLeft || cursorX > innerRight)
return;
}
display->drawVerticalLine(cursorX, cursorTop, cursorH);
}
}
void VirtualKeyboard::drawKey(OLEDDisplay *display, const VirtualKey &key, bool selected, int16_t x, int16_t y, uint8_t width,
uint8_t height, bool isLastCol)
{
// Draw key content
display->setFont(FONT_SMALL);
const int fontH = FONT_HEIGHT_SMALL;
// Build label and metrics first
std::string keyText;
if (key.type == VK_BACKSPACE || key.type == VK_ENTER || key.type == VK_SPACE || key.type == VK_ESC) {
// Keep literal text labels for the action keys on the rightmost column
keyText = (key.type == VK_BACKSPACE) ? "BACK"
: (key.type == VK_ENTER) ? "ENTER"
: (key.type == VK_SPACE) ? "SPACE"
: (key.type == VK_ESC) ? "ESC"
: "";
} else {
char c = getCharForKey(key, false);
if (c >= 'a' && c <= 'z') {
c = c - 'a' + 'A';
}
keyText = (key.character == ' ' || key.character == '_') ? "_" : std::string(1, c);
}
int textWidth = display->getStringWidth(keyText.c_str());
// Label alignment
// - Rightmost action column: right-align text with a small right padding (~2px) so it hugs screen edge neatly.
// - Other keys: center horizontally; use ceil-style rounding to avoid appearing left-biased on odd widths.
int textX;
if (isLastCol) {
const int rightPad = 1;
textX = x + width - textWidth - rightPad;
if (textX < x)
textX = x; // guard
} else {
if (display->getHeight() <= 64 && (key.character >= '0' && key.character <= '9')) {
textX = x + (width - textWidth + 1) / 2;
} else {
textX = x + (width - textWidth) / 2;
}
}
int contentTop = y;
int contentH = height;
if (selected) {
display->setColor(WHITE);
bool isAction = (key.type == VK_BACKSPACE || key.type == VK_ENTER || key.type == VK_SPACE || key.type == VK_ESC);
if (display->getHeight() <= 64 && !isAction) {
display->fillRect(x, y, width, height);
} else if (isAction) {
const int padX = 1;
const int padY = 2;
int hlW = textWidth + padX * 2;
int hlX = textX - padX;
if (hlX < x) {
hlW -= (x - hlX);
hlX = x;
}
int maxW = (x + width) - hlX;
if (hlW > maxW)
hlW = maxW;
if (hlW < 1)
hlW = 1;
int hlH = std::min(fontH + padY * 2, (int)height);
int hlY = y + (height - hlH) / 2;
display->fillRect(hlX, hlY, hlW, hlH);
contentTop = hlY;
contentH = hlH;
} else {
display->fillRect(x, y, width, height);
}
display->setColor(BLACK);
} else {
display->setColor(WHITE);
}
int centeredTextY;
if (display->getHeight() <= 64) {
centeredTextY = y + (height - fontH) / 2;
} else {
centeredTextY = contentTop + (contentH - fontH) / 2;
}
if (display->getHeight() > 64) {
if (centeredTextY < contentTop)
centeredTextY = contentTop;
if (centeredTextY + fontH > contentTop + contentH)
centeredTextY = std::max(contentTop, contentTop + contentH - fontH);
}
if (display->getHeight() <= 64 && keyText.size() == 1) {
char ch = keyText[0];
if (ch == '.' || ch == ',' || ch == ';') {
centeredTextY -= 1;
}
}
display->drawString(textX, centeredTextY, keyText.c_str());
}
char VirtualKeyboard::getCharForKey(const VirtualKey &key, bool isLongPress)
{
if (key.type != VK_CHAR) {
return key.character;
}
char c = key.character;
// Long-press: only keep letter lowercase->uppercase conversion; remove other symbol mappings
if (isLongPress && c >= 'a' && c <= 'z') {
c = (char)(c - 'a' + 'A');
}
return c;
}
void VirtualKeyboard::moveCursorDelta(int dRow, int dCol)
{
resetTimeout();
// wrap around rows and cols in the 4x11 grid
int r = (int)cursorRow + dRow;
int c = (int)cursorCol + dCol;
if (r < 0)
r = KEYBOARD_ROWS - 1;
else if (r >= KEYBOARD_ROWS)
r = 0;
if (c < 0)
c = KEYBOARD_COLS - 1;
else if (c >= KEYBOARD_COLS)
c = 0;
cursorRow = (uint8_t)r;
cursorCol = (uint8_t)c;
}
void VirtualKeyboard::moveCursorUp()
{
moveCursorDelta(-1, 0);
}
void VirtualKeyboard::moveCursorDown()
{
moveCursorDelta(1, 0);
}
void VirtualKeyboard::moveCursorLeft()
{
resetTimeout();
if (cursorCol > 0) {
cursorCol--;
} else {
if (cursorRow > 0) {
cursorRow--;
cursorCol = KEYBOARD_COLS - 1;
} else {
cursorRow = KEYBOARD_ROWS - 1;
cursorCol = KEYBOARD_COLS - 1;
}
}
}
void VirtualKeyboard::moveCursorRight()
{
resetTimeout();
if (cursorCol < KEYBOARD_COLS - 1) {
cursorCol++;
} else {
if (cursorRow < KEYBOARD_ROWS - 1) {
cursorRow++;
cursorCol = 0;
} else {
cursorRow = 0;
cursorCol = 0;
}
}
}
void VirtualKeyboard::handlePress()
{
resetTimeout(); // Reset timeout on any input activity
const VirtualKey &key = keyboard[cursorRow][cursorCol];
// Don't handle press if the key is empty (but allow special keys)
if (key.character == 0 && key.type == VK_CHAR) {
return;
}
// For character keys, insert lowercase character
if (key.type == VK_CHAR) {
insertCharacter(getCharForKey(key, false)); // false = lowercase/normal char
return;
}
// Handle non-character keys immediately
switch (key.type) {
case VK_BACKSPACE:
deleteCharacter();
break;
case VK_ENTER:
submitText();
break;
case VK_SPACE:
insertCharacter(' ');
break;
case VK_ESC:
if (onTextEntered) {
std::function<void(const std::string &)> callback = onTextEntered;
onTextEntered = nullptr;
inputText = "";
callback("");
}
return;
default:
break;
}
}
void VirtualKeyboard::handleLongPress()
{
resetTimeout(); // Reset timeout on any input activity
const VirtualKey &key = keyboard[cursorRow][cursorCol];
// Don't handle press if the key is empty (but allow special keys)
if (key.character == 0 && key.type == VK_CHAR) {
return;
}
// For character keys, insert uppercase/alternate character
if (key.type == VK_CHAR) {
insertCharacter(getCharForKey(key, true)); // true = uppercase/alternate char
return;
}
switch (key.type) {
case VK_BACKSPACE:
// One-shot: delete up to 5 characters on long press
for (int i = 0; i < 5; ++i) {
if (inputText.empty())
break;
deleteCharacter();
}
break;
case VK_ENTER:
submitText();
break;
case VK_SPACE:
insertCharacter(' ');
break;
case VK_ESC:
if (onTextEntered) {
onTextEntered("");
}
break;
default:
break;
}
}
void VirtualKeyboard::insertCharacter(char c)
{
if (inputText.length() < 160) { // Reasonable text length limit
inputText += c;
}
}
void VirtualKeyboard::deleteCharacter()
{
if (!inputText.empty()) {
inputText.pop_back();
}
}
void VirtualKeyboard::submitText()
{
LOG_INFO("Virtual keyboard: submitting text '%s'", inputText.c_str());
// Only submit if text is not empty
if (!inputText.empty() && onTextEntered) {
// Store callback and text to submit before clearing callback
std::function<void(const std::string &)> callback = onTextEntered;
std::string textToSubmit = inputText;
onTextEntered = nullptr;
// Don't clear inputText here - let the calling module handle cleanup
// inputText = ""; // Removed: keep text visible until module cleans up
callback(textToSubmit);
} else if (inputText.empty()) {
// For empty text, just ignore the submission - don't clear callback
// This keeps the virtual keyboard responsive for further input
LOG_INFO("Virtual keyboard: empty text submitted, ignoring - keyboard remains active");
} else {
// No callback available
if (screen) {
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
}
}
void VirtualKeyboard::setInputText(const std::string &text)
{
inputText = text;
}
std::string VirtualKeyboard::getInputText() const
{
return inputText;
}
void VirtualKeyboard::setHeader(const std::string &header)
{
headerText = header;
}
void VirtualKeyboard::setCallback(std::function<void(const std::string &)> callback)
{
onTextEntered = callback;
}
void VirtualKeyboard::resetTimeout()
{
lastActivityTime = millis();
}
bool VirtualKeyboard::isTimedOut() const
{
return (millis() - lastActivityTime) > TIMEOUT_MS;
}
} // namespace graphics

View File

@@ -1,80 +0,0 @@
#pragma once
#include "configuration.h"
#include <OLEDDisplay.h>
#include <functional>
#include <string>
namespace graphics
{
enum VirtualKeyType { VK_CHAR, VK_BACKSPACE, VK_ENTER, VK_SHIFT, VK_ESC, VK_SPACE };
struct VirtualKey {
char character;
VirtualKeyType type;
uint8_t x;
uint8_t y;
uint8_t width;
uint8_t height;
};
class VirtualKeyboard
{
public:
VirtualKeyboard();
~VirtualKeyboard();
void draw(OLEDDisplay *display, int16_t offsetX, int16_t offsetY);
void setInputText(const std::string &text);
std::string getInputText() const;
void setHeader(const std::string &header);
void setCallback(std::function<void(const std::string &)> callback);
// Navigation methods for encoder input
void moveCursorUp();
void moveCursorDown();
void moveCursorLeft();
void moveCursorRight();
void handlePress();
void handleLongPress();
// Timeout management
void resetTimeout();
bool isTimedOut() const;
private:
static const uint8_t KEYBOARD_ROWS = 4;
static const uint8_t KEYBOARD_COLS = 11;
static const uint8_t KEY_WIDTH = 9;
static const uint8_t KEY_HEIGHT = 9; // Compressed to fit 4 rows on 64px displays
static const uint8_t KEYBOARD_START_Y = 26; // Start just below input box bottom
VirtualKey keyboard[KEYBOARD_ROWS][KEYBOARD_COLS];
std::string inputText;
std::string headerText;
std::function<void(const std::string &)> onTextEntered;
uint8_t cursorRow;
uint8_t cursorCol;
// Timeout management for auto-exit
uint32_t lastActivityTime;
static const uint32_t TIMEOUT_MS = 60000; // 1 minute timeout
void initializeKeyboard();
void drawKey(OLEDDisplay *display, const VirtualKey &key, bool selected, int16_t x, int16_t y, uint8_t w, uint8_t h,
bool isLastCol);
void drawInputArea(OLEDDisplay *display, int16_t offsetX, int16_t offsetY, int16_t keyboardStartY);
// Unified cursor movement helper
void moveCursorDelta(int dRow, int dCol);
char getCharForKey(const VirtualKey &key, bool isLongPress = false);
void insertCharacter(char c);
void deleteCharacter();
void submitText();
};
} // namespace graphics

View File

@@ -2,6 +2,7 @@
#if HAS_SCREEN
#include "ClockRenderer.h"
#include "NodeDB.h"
#include "UIRenderer.h"
#include "configuration.h"
#include "gps/GeoCoord.h"
#include "gps/RTC.h"
@@ -189,7 +190,7 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
// === Set Title, Blank for Clock
const char *titleStr = "";
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
graphics::drawCommonHeader(display, x, y, titleStr, true);
#ifdef T_WATCH_S3
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
@@ -293,7 +294,6 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2,
isPM ? "pm" : "am");
}
#ifndef USE_EINK
xOffset = (isHighResolution) ? 18 : 10;
display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset,
@@ -313,7 +313,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// === Set Title, Blank for Clock
const char *titleStr = "";
// === Header ===
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
graphics::drawCommonHeader(display, x, y, titleStr, true);
#ifdef T_WATCH_S3
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {

View File

@@ -391,27 +391,18 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
int nameX = (SCREEN_WIDTH - textWidth);
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
// === Second Row: Role ===
auto role = DisplayFormatters::getDeviceRole(config.device.role);
char device_role[25];
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);
// === Third Row: Radio Preset ===
// === Second Row: Radio Preset ===
auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, false, config.lora.use_preset);
char regionradiopreset[25];
const char *region = myRegion ? myRegion->name : NULL;
if (region != nullptr) {
snprintf(regionradiopreset, sizeof(regionradiopreset), "Reg: %s/%s", region, mode);
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode);
}
textWidth = display->getStringWidth(regionradiopreset);
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], regionradiopreset);
// === Fourth Row: Frequency / ChanNum ===
// === Third Row: Frequency / ChanNum ===
char frequencyslot[35];
char freqStr[16];
float freq = RadioLibInterface::instance->getFreq();
@@ -429,7 +420,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
// === Fifth Row: Channel Utilization ===
// === Fourth Row: Channel Utilization ===
const char *chUtil = "ChUtil:";
char chUtilPercentage[10];
snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent());
@@ -446,7 +437,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
int total_line_content_width = (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++], chUtil);
// Force 56% or higher to show a full 100% bar, text would still show related percent.
if (chutil_percent >= 61) {
@@ -483,14 +474,14 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
display->fillRect(starting_position + chUtil_x, chUtil_y, fillRight, chutil_bar_height);
}
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)[4],
chUtilPercentage);
}
// ****************************
// * System Screen *
// ****************************
void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->clear();
display->setFont(FONT_SMALL);
@@ -634,33 +625,6 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
}
}
// ****************************
// * Chirpy Screen *
// ****************************
void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->clear();
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
int line = 1;
int iconX = SCREEN_WIDTH - chirpy_width - (chirpy_width / 3);
int iconY = (SCREEN_HEIGHT - chirpy_height) / 2;
int textX_offset = 10;
if (isHighResolution) {
iconX = SCREEN_WIDTH - chirpy_width_hirez - (chirpy_width_hirez / 3);
iconY = (SCREEN_HEIGHT - chirpy_height_hirez) / 2;
textX_offset = textX_offset * 4;
display->drawXbm(iconX, iconY, chirpy_width_hirez, chirpy_height_hirez, chirpy_hirez);
} else {
display->drawXbm(iconX, iconY, chirpy_width, chirpy_height, chirpy);
}
int textX = (display->getWidth() / 2) - textX_offset - (display->getStringWidth("Hello") / 2);
display->drawString(textX, getTextPositions(display)[line++], "Hello");
textX = (display->getWidth() / 2) - textX_offset - (display->getStringWidth("World!") / 2);
display->drawString(textX, getTextPositions(display)[line++], "World!");
}
} // namespace DebugRenderer
} // namespace graphics
#endif

View File

@@ -31,11 +31,8 @@ void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state
// LoRa information display
void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
// System screen display
void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
// Chirpy screen display
void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
// Memory screen display
void drawMemoryUsage(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
} // namespace DebugRenderer
} // namespace graphics

View File

@@ -10,10 +10,7 @@
#include "graphics/Screen.h"
#include "graphics/SharedUIDisplay.h"
#include "graphics/draw/UIRenderer.h"
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/UpDownInterruptImpl1.h"
#include "main.h"
#include "mesh/MeshTypes.h"
#include "modules/AdminModule.h"
#include "modules/CannedMessageModule.h"
#include "modules/KeyVerificationModule.h"
@@ -29,26 +26,6 @@ menuHandler::screenMenus menuHandler::menuQueue = menu_none;
bool test_enabled = false;
uint8_t test_count = 0;
void menuHandler::loraMenu()
{
static const char *optionsArray[] = {"Back", "Region Picker", "Device Role"};
enum optionsNumbers { Back = 0, lora_picker = 1, device_role_picker = 2 };
BannerOverlayOptions bannerOptions;
bannerOptions.message = "LoRa Actions";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
// No action
} else if (selected == lora_picker) {
menuHandler::menuQueue = menuHandler::lora_picker;
} else if (selected == device_role_picker) {
menuHandler::menuQueue = menuHandler::device_role_picker;
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::OnboardMessage()
{
static const char *optionsArray[] = {"OK", "Got it!"};
@@ -142,40 +119,6 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::DeviceRolePicker()
{
static const char *optionsArray[] = {"Back", "Client", "Client Mute", "Lost and Found", "Tracker"};
enum optionsNumbers {
Back = 0,
devicerole_client = 1,
devicerole_clientmute = 2,
devicerole_lostandfound = 3,
devicerole_tracker = 4
};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Device Role";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 5;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
menuHandler::menuQueue = menuHandler::lora_Menu;
screen->runNow();
return;
} else if (selected == devicerole_client) {
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
} else if (selected == devicerole_clientmute) {
config.device.role = meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE;
} else if (selected == devicerole_lostandfound) {
config.device.role = meshtastic_Config_DeviceConfig_Role_LOST_AND_FOUND;
} else if (selected == devicerole_tracker) {
config.device.role = meshtastic_Config_DeviceConfig_Role_TRACKER;
}
service->reloadConfig(SEGMENT_CONFIG);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::TwelveHourPicker()
{
static const char *optionsArray[] = {"Back", "12-hour", "24-hour"};
@@ -362,7 +305,7 @@ void menuHandler::messageResponseMenu()
bannerOptions.optionsCount = options;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Dismiss) {
screen->hideCurrentFrame();
screen->dismissCurrentFrame();
} else if (selected == Preset) {
if (devicestate.rx_text_message.to == NODENUM_BROADCAST) {
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST, devicestate.rx_text_message.channel);
@@ -403,11 +346,8 @@ void menuHandler::homeBaseMenu()
optionsArray[options] = "Sleep Screen";
optionsEnumArray[options++] = Sleep;
#endif
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
optionsArray[options] = "Send Position";
} else {
optionsArray[options] = "Send Node Info";
}
optionsArray[options] = "Send Position";
optionsEnumArray[options++] = Position;
optionsArray[options] = "New Preset Msg";
optionsEnumArray[options++] = Preset;
@@ -487,7 +427,7 @@ void menuHandler::textMessageBaseMenu()
void menuHandler::systemBaseMenu()
{
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, FrameToggles, Test, enumEnd };
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd };
static const char *optionsArray[enumEnd] = {"Back"};
static int optionsEnumArray[enumEnd] = {Back};
int options = 1;
@@ -500,9 +440,6 @@ void menuHandler::systemBaseMenu()
optionsEnumArray[options++] = ScreenOptions;
#endif
optionsArray[options] = "Frame Visiblity Toggle";
optionsEnumArray[options++] = FrameToggles;
optionsArray[options] = "Bluetooth Toggle";
optionsEnumArray[options++] = Bluetooth;
@@ -529,9 +466,6 @@ void menuHandler::systemBaseMenu()
} else if (selected == PowerMenu) {
menuHandler::menuQueue = menuHandler::power_menu;
screen->runNow();
} else if (selected == FrameToggles) {
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == Test) {
menuHandler::menuQueue = menuHandler::test_menu;
screen->runNow();
@@ -598,7 +532,6 @@ void menuHandler::positionBaseMenu()
optionsArray[options] = "Compass Calibrate";
optionsEnumArray[options++] = CompassCalibrate;
}
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Position Action";
bannerOptions.optionsArrayPtr = optionsArray;
@@ -1004,33 +937,16 @@ void menuHandler::traceRouteMenu()
void menuHandler::testMenu()
{
enum optionsNumbers { Back, NumberPicker, ShowChirpy };
static const char *optionsArray[4] = {"Back"};
static int optionsEnumArray[4] = {Back};
int options = 1;
optionsArray[options] = "Number Picker";
optionsEnumArray[options++] = NumberPicker;
optionsArray[options] = screen->isFrameHidden("chirpy") ? "Show Chirpy" : "Hide Chirpy";
optionsEnumArray[options++] = ShowChirpy;
static const char *optionsArray[] = {"Back", "Number Picker"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Hidden Test Menu";
std::string message = "Test to Run?\n";
bannerOptions.message = message.c_str();
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == NumberPicker) {
if (selected == 1) {
menuQueue = number_test;
screen->runNow();
} else if (selected == ShowChirpy) {
screen->toggleFrameVisibility("chirpy");
screen->setFrames(Screen::FOCUS_SYSTEM);
} else {
menuQueue = system_base_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
@@ -1227,116 +1143,6 @@ void menuHandler::keyVerificationFinalPrompt()
}
}
void menuHandler::FrameToggles_menu()
{
enum optionsNumbers {
Finish,
nodelist,
nodelist_lastheard,
nodelist_hopsignal,
nodelist_distance,
nodelist_bearings,
gps,
lora,
clock,
show_favorites,
enumEnd
};
static const char *optionsArray[enumEnd] = {"Finish"};
static int optionsEnumArray[enumEnd] = {Finish};
int options = 1;
// Track last selected index (not enum value!)
static int lastSelectedIndex = 0;
#ifndef USE_EINK
optionsArray[options] = screen->isFrameHidden("nodelist") ? "Show Node List" : "Hide Node List";
optionsEnumArray[options++] = nodelist;
#endif
#ifdef USE_EINK
optionsArray[options] = screen->isFrameHidden("nodelist_lastheard") ? "Show NL - Last Heard" : "Hide NL - Last Heard";
optionsEnumArray[options++] = nodelist_lastheard;
optionsArray[options] = screen->isFrameHidden("nodelist_hopsignal") ? "Show NL - Hops/Signal" : "Hide NL - Hops/Signal";
optionsEnumArray[options++] = nodelist_hopsignal;
optionsArray[options] = screen->isFrameHidden("nodelist_distance") ? "Show NL - Distance" : "Hide NL - Distance";
optionsEnumArray[options++] = nodelist_distance;
#endif
#if HAS_GPS
optionsArray[options] = screen->isFrameHidden("nodelist_bearings") ? "Show Bearings" : "Hide Bearings";
optionsEnumArray[options++] = nodelist_bearings;
optionsArray[options] = screen->isFrameHidden("gps") ? "Show Position" : "Hide Position";
optionsEnumArray[options++] = gps;
#endif
optionsArray[options] = screen->isFrameHidden("lora") ? "Show LoRa" : "Hide LoRa";
optionsEnumArray[options++] = lora;
optionsArray[options] = screen->isFrameHidden("clock") ? "Show Clock" : "Hide Clock";
optionsEnumArray[options++] = clock;
optionsArray[options] = screen->isFrameHidden("show_favorites") ? "Show Favorites" : "Hide Favorites";
optionsEnumArray[options++] = show_favorites;
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Show/Hide Frames";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = options;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.InitialSelected = lastSelectedIndex; // Use index, not enum value
bannerOptions.bannerCallback = [options](int selected) mutable -> void {
// Find the index of selected in optionsEnumArray
int idx = 0;
for (; idx < options; ++idx) {
if (optionsEnumArray[idx] == selected)
break;
}
lastSelectedIndex = idx;
if (selected == Finish) {
screen->setFrames(Screen::FOCUS_DEFAULT);
} else if (selected == nodelist) {
screen->toggleFrameVisibility("nodelist");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == nodelist_lastheard) {
screen->toggleFrameVisibility("nodelist_lastheard");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == nodelist_hopsignal) {
screen->toggleFrameVisibility("nodelist_hopsignal");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == nodelist_distance) {
screen->toggleFrameVisibility("nodelist_distance");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == nodelist_bearings) {
screen->toggleFrameVisibility("nodelist_bearings");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == gps) {
screen->toggleFrameVisibility("gps");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == lora) {
screen->toggleFrameVisibility("lora");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == clock) {
screen->toggleFrameVisibility("clock");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
} else if (selected == show_favorites) {
screen->toggleFrameVisibility("show_favorites");
menuHandler::menuQueue = menuHandler::FrameToggles;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::handleMenuSwitch(OLEDDisplay *display)
{
if (menuQueue != menu_none)
@@ -1344,15 +1150,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
switch (menuQueue) {
case menu_none:
break;
case lora_Menu:
loraMenu();
break;
case lora_picker:
LoraRegionPicker();
break;
case device_role_picker:
DeviceRolePicker();
break;
case no_timeout_lora_picker:
LoraRegionPicker(0);
break;
@@ -1439,9 +1239,6 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case power_menu:
powerMenu();
break;
case FrameToggles:
FrameToggles_menu();
break;
case throttle_message:
screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
break;

View File

@@ -9,9 +9,7 @@ class menuHandler
public:
enum screenMenus {
menu_none,
lora_Menu,
lora_picker,
device_role_picker,
no_timeout_lora_picker,
TZ_picker,
twelve_hour_picker,
@@ -41,14 +39,11 @@ class menuHandler
key_verification_final_prompt,
trace_route_menu,
throttle_message,
FrameToggles
};
static screenMenus menuQueue;
static void OnboardMessage();
static void LoraRegionPicker(uint32_t duration = 30000);
static void loraMenu();
static void DeviceRolePicker();
static void handleMenuSwitch(OLEDDisplay *display);
static void showConfirmationBanner(const char *message, std::function<void()> onConfirm);
static void clockMenu();
@@ -81,7 +76,6 @@ class menuHandler
static void notificationsMenu();
static void screenOptionsMenu();
static void powerMenu();
static void FrameToggles_menu();
private:
static void saveUIConfig();

View File

@@ -7,18 +7,10 @@
#include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h"
#include "graphics/images.h"
#include "input/RotaryEncoderInterruptImpl1.h"
#include "input/UpDownInterruptImpl1.h"
#if HAS_BUTTON
#include "input/ButtonThread.h"
#endif
#include "main.h"
#include <algorithm>
#include <string>
#include <vector>
#if HAS_TRACKBALL
#include "input/TrackballInterruptImpl1.h"
#endif
#ifdef ARCH_ESP32
#include "esp_task_wdt.h"
@@ -26,11 +18,6 @@
using namespace meshtastic;
#if HAS_BUTTON
// Global button thread pointer defined in main.cpp
extern ::ButtonThread *UserButtonThread;
#endif
// External references to global variables from Screen.cpp
extern std::vector<std::string> functionSymbol;
extern std::string functionSymbolString;
@@ -51,8 +38,6 @@ bool NotificationRenderer::pauseBanner = false;
notificationTypeEnum NotificationRenderer::current_notification_type = notificationTypeEnum::none;
uint32_t NotificationRenderer::numDigits = 0;
uint32_t NotificationRenderer::currentNumber = 0;
VirtualKeyboard *NotificationRenderer::virtualKeyboard = nullptr;
std::function<void(const std::string &)> NotificationRenderer::textInputCallback = nullptr;
uint32_t pow_of_10(uint32_t n)
{
@@ -104,33 +89,14 @@ void NotificationRenderer::resetBanner()
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
{
// Handle text_input notifications first - they have their own timeout/banner logic
if (current_notification_type == notificationTypeEnum::text_input) {
// Check for timeout and reset if needed for text input
if (millis() > alertBannerUntil && alertBannerUntil > 0) {
resetBanner();
return;
}
drawTextInput(display, state);
return;
}
if (millis() > alertBannerUntil && alertBannerUntil > 0) {
if (!isOverlayBannerShowing() && alertBannerMessage[0] != '\0')
resetBanner();
}
// Exit if no banner is showing or banner is paused
if (!isOverlayBannerShowing() || pauseBanner) {
if (!isOverlayBannerShowing() || pauseBanner)
return;
}
switch (current_notification_type) {
case notificationTypeEnum::none:
// Do nothing - no notification to display
break;
case notificationTypeEnum::text_input:
// Already handled above with dedicated logic (early return). Keep a case here to satisfy -Wswitch.
break;
case notificationTypeEnum::text_banner:
case notificationTypeEnum::selection_picker:
drawAlertBannerOverlay(display, state);
@@ -301,9 +267,12 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
if (nodeDB->getMeshNodeByIndex(i + 1)->has_user) {
std::string sanitized = sanitizeString(nodeDB->getMeshNodeByIndex(i + 1)->user.long_name);
strncpy(temp_name, sanitized.c_str(), sizeof(temp_name) - 1);
} else {
snprintf(temp_name, sizeof(temp_name), "(%04X)", (uint16_t)(nodeDB->getMeshNodeByIndex(i + 1)->num & 0xFFFF));
}
// make temp buffer for name
// fi
if (i == curSelected) {
selectedNodenum = nodeDB->getMeshNodeByIndex(i + 1)->num;
if (isHighResolution) {
@@ -317,8 +286,7 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
}
scratchLineBuffer[scratchLineNum][39] = '\0';
} else {
strncpy(scratchLineBuffer[scratchLineNum], temp_name, 39);
scratchLineBuffer[scratchLineNum][39] = '\0';
strncpy(scratchLineBuffer[scratchLineNum], temp_name, 36);
}
linePointers[linesShown] = scratchLineBuffer[scratchLineNum++];
}
@@ -607,99 +575,6 @@ void NotificationRenderer::drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUi
"Please be patient and do not power off.");
}
void NotificationRenderer::drawTextInput(OLEDDisplay *display, OLEDDisplayUiState *state)
{
if (virtualKeyboard) {
// Check for timeout and auto-exit if needed
if (virtualKeyboard->isTimedOut()) {
LOG_INFO("Virtual keyboard timeout - auto-exiting");
// Cancel virtual keyboard - call callback with empty string to indicate timeout
auto callback = textInputCallback; // Store callback before clearing
// Clean up first to prevent re-entry
delete virtualKeyboard;
virtualKeyboard = nullptr;
textInputCallback = nullptr;
resetBanner();
// Call callback after cleanup
if (callback) {
callback("");
}
// Restore normal overlays
if (screen) {
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
return;
}
if (inEvent.inputEvent != INPUT_BROKER_NONE) {
if (inEvent.inputEvent == INPUT_BROKER_UP) {
// high frequency for move cursor left/right than up/down with encoders
extern ::RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
extern ::UpDownInterruptImpl1 *upDownInterruptImpl1;
if (::rotaryEncoderInterruptImpl1 || ::upDownInterruptImpl1) {
virtualKeyboard->moveCursorLeft();
} else {
virtualKeyboard->moveCursorUp();
}
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN) {
extern ::RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
extern ::UpDownInterruptImpl1 *upDownInterruptImpl1;
if (::rotaryEncoderInterruptImpl1 || ::upDownInterruptImpl1) {
virtualKeyboard->moveCursorRight();
} else {
virtualKeyboard->moveCursorDown();
}
} else if (inEvent.inputEvent == INPUT_BROKER_LEFT) {
virtualKeyboard->moveCursorLeft();
} else if (inEvent.inputEvent == INPUT_BROKER_RIGHT) {
virtualKeyboard->moveCursorRight();
} else if (inEvent.inputEvent == INPUT_BROKER_UP_LONG) {
virtualKeyboard->moveCursorUp();
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) {
virtualKeyboard->moveCursorDown();
} else if (inEvent.inputEvent == INPUT_BROKER_ALT_PRESS) {
virtualKeyboard->moveCursorLeft();
} else if (inEvent.inputEvent == INPUT_BROKER_USER_PRESS) {
virtualKeyboard->moveCursorRight();
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT) {
virtualKeyboard->handlePress();
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT_LONG) {
virtualKeyboard->handleLongPress();
} else if (inEvent.inputEvent == INPUT_BROKER_CANCEL) {
auto callback = textInputCallback;
delete virtualKeyboard;
virtualKeyboard = nullptr;
textInputCallback = nullptr;
resetBanner();
if (callback) {
callback("");
}
if (screen) {
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
}
return;
}
// Consume the event after processing for virtual keyboard
inEvent.inputEvent = INPUT_BROKER_NONE;
}
// Clear the screen to avoid overlapping with underlying frames or overlays
display->setColor(BLACK);
display->fillRect(0, 0, display->getWidth(), display->getHeight());
display->setColor(WHITE);
// Draw the virtual keyboard
virtualKeyboard->draw(display, 0, 0);
} else {
// If virtualKeyboard is null, reset the banner to avoid getting stuck
LOG_INFO("Virtual keyboard is null - resetting banner");
resetBanner();
}
}
bool NotificationRenderer::isOverlayBannerShowing()
{
return strlen(alertBannerMessage) > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil);

View File

@@ -3,9 +3,6 @@
#include "OLEDDisplay.h"
#include "OLEDDisplayUi.h"
#include "graphics/Screen.h"
#include "graphics/VirtualKeyboard.h"
#include <functional>
#include <string>
#define MAX_LINES 5
namespace graphics
@@ -25,8 +22,6 @@ class NotificationRenderer
static std::function<void(int)> alertBannerCallback;
static uint32_t numDigits;
static uint32_t currentNumber;
static VirtualKeyboard *virtualKeyboard;
static std::function<void(const std::string &)> textInputCallback;
static bool pauseBanner;
@@ -35,7 +30,6 @@ class NotificationRenderer
static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state);
static void drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiState *state);
static void drawNodePicker(OLEDDisplay *display, OLEDDisplayUiState *state);
static void drawTextInput(OLEDDisplay *display, OLEDDisplayUiState *state);
static void drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[MAX_LINES + 1],
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth = 0);

View File

@@ -879,26 +879,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
config.display.heading_bold = false;
const char *displayLine = ""; // Initialize to empty string by default
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
bool usePhoneGPS = (ourNode && nodeDB->hasValidPosition(ourNode) &&
config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED);
if (usePhoneGPS) {
// Phone-provided GPS is active
displayLine = "Phone GPS";
int yOffset = (isHighResolution) ? 3 : 1;
if (isHighResolution) {
NodeListRenderer::drawScaledXBitmap16x16(x, getTextPositions(display)[line] + yOffset - 5, imgSatellite_width,
imgSatellite_height, imgSatellite, display);
} else {
display->drawXbm(x + 1, getTextPositions(display)[line] + yOffset, imgSatellite_width, imgSatellite_height,
imgSatellite);
}
int xOffset = (isHighResolution) ? 6 : 0;
display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine);
} else if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
// GPS disabled / not present
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
if (config.position.fixed_position) {
displayLine = "Fixed GPS";
} else {
@@ -915,7 +896,6 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
int xOffset = (isHighResolution) ? 6 : 0;
display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine);
} else {
// Onboard GPS
UIRenderer::drawGps(display, 0, getTextPositions(display)[line++], gpsStatus);
}
@@ -942,61 +922,32 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
// If GPS is off, no need to display these parts
if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) {
// === Second Row: Last GPS Fix ===
if (gpsStatus->getLastFixMillis() > 0) {
uint32_t delta = (millis() - gpsStatus->getLastFixMillis()) / 1000; // seconds since last fix
uint32_t days = delta / 86400;
uint32_t hours = (delta % 86400) / 3600;
uint32_t mins = (delta % 3600) / 60;
uint32_t secs = delta % 60;
char buf[32];
#if defined(USE_EINK)
// E-Ink: skip seconds, show only days/hours/mins
if (days > 0) {
snprintf(buf, sizeof(buf), " Last: %ud %uh", days, hours);
} else if (hours > 0) {
snprintf(buf, sizeof(buf), " Last: %uh %um", hours, mins);
} else {
snprintf(buf, sizeof(buf), " Last: %um", mins);
}
#else
// Non E-Ink: include seconds where useful
if (days > 0) {
snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours);
} else if (hours > 0) {
snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins);
} else if (mins > 0) {
snprintf(buf, sizeof(buf), "Last: %um %us", mins, secs);
} else {
snprintf(buf, sizeof(buf), "Last: %us", secs);
}
#endif
display->drawString(0, getTextPositions(display)[line++], buf);
} else {
display->drawString(0, getTextPositions(display)[line++], "Last: ?");
}
// === Second Row: Date ===
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true);
char datetimeStr[25];
bool showTime = false; // set to true for full datetime
UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, showTime);
char fullLine[40];
snprintf(fullLine, sizeof(fullLine), " Date: %s", datetimeStr);
display->drawString(0, getTextPositions(display)[line++], fullLine);
// === Third Row: Latitude ===
char latStr[32];
snprintf(latStr, sizeof(latStr), "Lat: %.5f", geoCoord.getLatitude() * 1e-7);
snprintf(latStr, sizeof(latStr), " Lat: %.5f", geoCoord.getLatitude() * 1e-7);
display->drawString(x, getTextPositions(display)[line++], latStr);
// === Fourth Row: Longitude ===
char lonStr[32];
snprintf(lonStr, sizeof(lonStr), "Lon: %.5f", geoCoord.getLongitude() * 1e-7);
snprintf(lonStr, sizeof(lonStr), " Lon: %.5f", geoCoord.getLongitude() * 1e-7);
display->drawString(x, getTextPositions(display)[line++], lonStr);
// === Fifth Row: Altitude ===
char DisplayLineTwo[32] = {0};
int32_t alt = (strcmp(displayLine, "Phone GPS") == 0 && ourNode && nodeDB->hasValidPosition(ourNode))
? ourNode->position.altitude
: geoCoord.getAltitude();
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), "Alt: %.0fft", geoCoord.getAltitude() * METERS_TO_FEET);
snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), " Alt: %.0fft", geoCoord.getAltitude() * METERS_TO_FEET);
} else {
snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), "Alt: %.0im", geoCoord.getAltitude());
snprintf(DisplayLineTwo, sizeof(DisplayLineTwo), " Alt: %.0im", geoCoord.getAltitude());
}
display->drawString(x, getTextPositions(display)[line++], DisplayLineTwo);
}

View File

@@ -1,6 +1,5 @@
#pragma once
#include "NodeDB.h"
#include "graphics/Screen.h"
#include "graphics/emotes.h"
#include <OLEDDisplay.h>

View File

@@ -118,8 +118,8 @@ const uint8_t icon_radio[] PROGMEM = {
0xA9 // Row 7: #..#.#.#
};
// 🪙 System Icon
const uint8_t icon_system[] PROGMEM = {
// 🪙 Memory Icon
const uint8_t icon_memory[] PROGMEM = {
0x24, // Row 0: ..#..#..
0x3C, // Row 1: ..####..
0xC3, // Row 2: ##....##
@@ -288,77 +288,5 @@ const uint8_t digital_icon_clock[] PROGMEM = {0b00111100, 0b01000010, 0b10000101
const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000,
0b00100100, 0b01000010, 0b01000010, 0b11111111};
#define chirpy_width 38
#define chirpy_height 50
static unsigned char chirpy[] = {
0xfe, 0xff, 0xff, 0xff, 0xdf, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0xe3, 0x01,
0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0xc0, 0xe7, 0x01, 0x00, 0x00, 0x80, 0xe3, 0x01, 0x00,
0x00, 0x00, 0xe0, 0x81, 0xff, 0xff, 0x7f, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xcf, 0x7f,
0xfe, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc,
0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0,
0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0x87, 0x3f, 0xfc, 0xe0, 0xc1,
0x87, 0x3f, 0xfc, 0xe0, 0xc1, 0xcf, 0x7f, 0xfe, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0x81, 0xff,
0xff, 0x7f, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0xc3, 0x00, 0xe0, 0x01, 0x00, 0xc3,
0x00, 0xe0, 0x01, 0x80, 0xe1, 0x01, 0xe0, 0x01, 0x80, 0xe1, 0x01, 0xe0, 0x01, 0xc0, 0x30, 0x03, 0xe0, 0x01, 0xc0, 0x30, 0x03,
0xe0, 0x01, 0x60, 0x18, 0x06, 0xe0, 0x01, 0x60, 0x18, 0x06, 0xe0, 0x01, 0x30, 0x0c, 0x0c, 0xe0, 0x01, 0x30, 0x0c, 0x0c, 0xe0,
0x01, 0x18, 0x06, 0x18, 0xe0, 0x01, 0x18, 0x06, 0x18, 0xe0, 0x01, 0x0c, 0x03, 0x30, 0xe0, 0x01, 0x0c, 0x03, 0x30, 0xe0, 0x01,
0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0xfe, 0xff, 0xff, 0xff, 0xdf};
#define chirpy_width_hirez 76
#define chirpy_height_hirez 100
static unsigned char chirpy_hirez[] = {
0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00,
0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc,
0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03,
0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xf0, 0xff, 0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0,
0xff, 0xf0, 0xff, 0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f,
0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0,
0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff,
0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f,
0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0,
0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff,
0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00,
0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc,
0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03,
0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0,
0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f,
0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0,
0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xf0, 0xff,
0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xf0, 0xff, 0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f,
0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc,
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03,
0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00,
0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
0xc0, 0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0,
0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00,
0x0f, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x0f,
0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c,
0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c, 0x00,
0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00,
0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xfc,
0x03, 0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03,
0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00,
0xf0, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0,
0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xf3, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3};
#define chirpy_small_image_width 8
#define chirpy_small_image_height 8
static unsigned char small_chirpy[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
#include "img/icon.xbm"
static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");

View File

@@ -41,78 +41,78 @@ void tftSetup(void)
PacketAPI::create(PacketServer::init());
deviceScreen->init(new PacketClient);
#else
if (portduino_config.displayPanel != no_screen) {
if (settingsMap[displayPanel] != no_screen) {
DisplayDriverConfig displayConfig;
static char *panels[] = {"NOSCREEN", "X11", "FB", "ST7789", "ST7735", "ST7735S",
"ST7796", "ILI9341", "ILI9342", "ILI9486", "ILI9488", "HX8357D"};
static char *touch[] = {"NOTOUCH", "XPT2046", "STMPE610", "GT911", "FT5x06"};
#if defined(USE_X11)
if (portduino_config.displayPanel == x11) {
if (portduino_config.displayWidth && portduino_config.displayHeight)
displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::X11, (uint16_t)portduino_config.displayWidth,
(uint16_t)portduino_config.displayHeight);
if (settingsMap[displayPanel] == x11) {
if (settingsMap[displayWidth] && settingsMap[displayHeight])
displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::X11, (uint16_t)settingsMap[displayWidth],
(uint16_t)settingsMap[displayHeight]);
else
displayConfig.device(DisplayDriverConfig::device_t::X11);
} else
#elif defined(USE_FRAMEBUFFER)
if (portduino_config.displayPanel == fb) {
if (portduino_config.displayWidth && portduino_config.displayHeight)
displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::FB, (uint16_t)portduino_config.displayWidth,
(uint16_t)portduino_config.displayHeight);
if (settingsMap[displayPanel] == fb) {
if (settingsMap[displayWidth] && settingsMap[displayHeight])
displayConfig = DisplayDriverConfig(DisplayDriverConfig::device_t::FB, (uint16_t)settingsMap[displayWidth],
(uint16_t)settingsMap[displayHeight]);
else
displayConfig.device(DisplayDriverConfig::device_t::FB);
} else
#endif
{
displayConfig.device(DisplayDriverConfig::device_t::CUSTOM_TFT)
.panel(DisplayDriverConfig::panel_config_t{.type = panels[portduino_config.displayPanel],
.panel_width = (uint16_t)portduino_config.displayWidth,
.panel_height = (uint16_t)portduino_config.displayHeight,
.rotation = (bool)portduino_config.displayRotate,
.pin_cs = (int16_t)portduino_config.displayCS.pin,
.pin_rst = (int16_t)portduino_config.displayReset.pin,
.offset_x = (uint16_t)portduino_config.displayOffsetX,
.offset_y = (uint16_t)portduino_config.displayOffsetY,
.offset_rotation = (uint8_t)portduino_config.displayOffsetRotate,
.invert = portduino_config.displayInvert ? true : false,
.rgb_order = (bool)portduino_config.displayRGBOrder,
.dlen_16bit = portduino_config.displayPanel == ili9486 ||
portduino_config.displayPanel == ili9488})
.bus(DisplayDriverConfig::bus_config_t{.freq_write = (uint32_t)portduino_config.displayBusFrequency,
.panel(DisplayDriverConfig::panel_config_t{.type = panels[settingsMap[displayPanel]],
.panel_width = (uint16_t)settingsMap[displayWidth],
.panel_height = (uint16_t)settingsMap[displayHeight],
.rotation = (bool)settingsMap[displayRotate],
.pin_cs = (int16_t)settingsMap[displayCS],
.pin_rst = (int16_t)settingsMap[displayReset],
.offset_x = (uint16_t)settingsMap[displayOffsetX],
.offset_y = (uint16_t)settingsMap[displayOffsetY],
.offset_rotation = (uint8_t)settingsMap[displayOffsetRotate],
.invert = settingsMap[displayInvert] ? true : false,
.rgb_order = (bool)settingsMap[displayRGBOrder],
.dlen_16bit = settingsMap[displayPanel] == ili9486 ||
settingsMap[displayPanel] == ili9488})
.bus(DisplayDriverConfig::bus_config_t{.freq_write = (uint32_t)settingsMap[displayBusFrequency],
.freq_read = 16000000,
.spi{.pin_dc = (int8_t)portduino_config.displayDC.pin,
.spi{.pin_dc = (int8_t)settingsMap[displayDC],
.use_lock = true,
.spi_host = (uint16_t)portduino_config.display_spi_dev_int}})
.input(DisplayDriverConfig::input_config_t{.keyboardDevice = portduino_config.keyboardDevice,
.pointerDevice = portduino_config.pointerDevice})
.light(DisplayDriverConfig::light_config_t{.pin_bl = (int16_t)portduino_config.displayBacklight.pin,
.pwm_channel = (int8_t)portduino_config.displayBacklightPWMChannel.pin,
.invert = (bool)portduino_config.displayBacklightInvert});
if (portduino_config.touchscreenI2CAddr == -1) {
.spi_host = (uint16_t)settingsMap[displayspidev]}})
.input(DisplayDriverConfig::input_config_t{.keyboardDevice = settingsStrings[keyboardDevice],
.pointerDevice = settingsStrings[pointerDevice]})
.light(DisplayDriverConfig::light_config_t{.pin_bl = (int16_t)settingsMap[displayBacklight],
.pwm_channel = (int8_t)settingsMap[displayBacklightPWMChannel],
.invert = (bool)settingsMap[displayBacklightInvert]});
if (settingsMap[touchscreenI2CAddr] == -1) {
displayConfig.touch(
DisplayDriverConfig::touch_config_t{.type = touch[portduino_config.touchscreenModule],
.freq = (uint32_t)portduino_config.touchscreenBusFrequency,
.pin_int = (int16_t)portduino_config.touchscreenIRQ.pin,
.offset_rotation = (uint8_t)portduino_config.touchscreenRotate,
DisplayDriverConfig::touch_config_t{.type = touch[settingsMap[touchscreenModule]],
.freq = (uint32_t)settingsMap[touchscreenBusFrequency],
.pin_int = (int16_t)settingsMap[touchscreenIRQ],
.offset_rotation = (uint8_t)settingsMap[touchscreenRotate],
.spi{
.spi_host = (int8_t)portduino_config.touchscreen_spi_dev_int,
.spi_host = (int8_t)settingsMap[touchscreenspidev],
},
.pin_cs = (int16_t)portduino_config.touchscreenCS.pin});
.pin_cs = (int16_t)settingsMap[touchscreenCS]});
} else {
displayConfig.touch(DisplayDriverConfig::touch_config_t{
.type = touch[portduino_config.touchscreenModule],
.freq = (uint32_t)portduino_config.touchscreenBusFrequency,
.type = touch[settingsMap[touchscreenModule]],
.freq = (uint32_t)settingsMap[touchscreenBusFrequency],
.x_min = 0,
.x_max = (int16_t)((portduino_config.touchscreenRotate & 1 ? portduino_config.displayWidth
: portduino_config.displayHeight) -
1),
.x_max =
(int16_t)((settingsMap[touchscreenRotate] & 1 ? settingsMap[displayWidth] : settingsMap[displayHeight]) -
1),
.y_min = 0,
.y_max = (int16_t)((portduino_config.touchscreenRotate & 1 ? portduino_config.displayHeight
: portduino_config.displayWidth) -
1),
.pin_int = (int16_t)portduino_config.touchscreenIRQ.pin,
.offset_rotation = (uint8_t)portduino_config.touchscreenRotate,
.i2c{.i2c_addr = (uint8_t)portduino_config.touchscreenI2CAddr}});
.y_max =
(int16_t)((settingsMap[touchscreenRotate] & 1 ? settingsMap[displayHeight] : settingsMap[displayWidth]) -
1),
.pin_int = (int16_t)settingsMap[touchscreenIRQ],
.offset_rotation = (uint8_t)settingsMap[touchscreenRotate],
.i2c{.i2c_addr = (uint8_t)settingsMap[touchscreenI2CAddr]}});
}
}
deviceScreen = &DeviceScreen::create(&displayConfig);

View File

@@ -76,9 +76,6 @@ class ButtonThread : public Observable<const InputEvent *>, public concurrency::
return digitalRead(buttonPin); // Most buttons are active low by default
}
// Returns true while this thread's button is physically held down
bool isHeld() { return isButtonPressed(_pinNum); }
// Disconnect and reconnect interrupts for light sleep
#ifdef ARCH_ESP32
int beforeLightSleep(void *unused);

View File

@@ -4,9 +4,6 @@
enum input_broker_event {
INPUT_BROKER_NONE = 0,
INPUT_BROKER_SELECT = 10,
INPUT_BROKER_SELECT_LONG = 11,
INPUT_BROKER_UP_LONG = 12,
INPUT_BROKER_DOWN_LONG = 13,
INPUT_BROKER_UP = 17,
INPUT_BROKER_DOWN = 18,
INPUT_BROKER_LEFT = 19,

View File

@@ -33,9 +33,9 @@ int32_t LinuxInput::runOnce()
{
if (firstTime) {
if (portduino_config.keyboardDevice == "")
if (settingsStrings[keyboardDevice] == "")
return disable();
fd = open(portduino_config.keyboardDevice.c_str(), O_RDWR);
fd = open(settingsStrings[keyboardDevice].c_str(), O_RDWR);
if (fd < 0)
return disable();
ret = ioctl(fd, EVIOCGRAB, (void *)1);

View File

@@ -8,17 +8,15 @@ RotaryEncoderInterruptBase::RotaryEncoderInterruptBase(const char *name) : concu
void RotaryEncoderInterruptBase::init(
uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw,
input_broker_event eventPressed, input_broker_event eventPressedLong,
input_broker_event eventPressed,
// std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)> onIntPress) :
void (*onIntA)(), void (*onIntB)(), void (*onIntPress)())
{
this->_pinA = pinA;
this->_pinB = pinB;
this->_pinPress = pinPress;
this->_eventCw = eventCw;
this->_eventCcw = eventCcw;
this->_eventPressed = eventPressed;
this->_eventPressedLong = eventPressedLong;
bool isRAK = false;
#ifdef RAK_4631
@@ -48,37 +46,10 @@ int32_t RotaryEncoderInterruptBase::runOnce()
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
e.source = this->_originName;
unsigned long now = millis();
// Handle press long/short detection
if (this->action == ROTARY_ACTION_PRESSED) {
bool buttonPressed = !digitalRead(_pinPress);
if (!pressDetected && buttonPressed) {
pressDetected = true;
pressStartTime = now;
}
if (pressDetected) {
uint32_t duration = now - pressStartTime;
if (!buttonPressed) {
// released -> if short press, send short, else already sent long
if (duration < LONG_PRESS_DURATION && now - lastPressKeyTime >= pressDebounceMs) {
lastPressKeyTime = now;
LOG_DEBUG("Rotary event Press short");
e.inputEvent = this->_eventPressed;
}
pressDetected = false;
pressStartTime = 0;
lastPressLongEventTime = 0;
this->action = ROTARY_ACTION_NONE;
} else if (duration >= LONG_PRESS_DURATION && this->_eventPressedLong != INPUT_BROKER_NONE &&
lastPressLongEventTime == 0) {
// fire single-shot long press
lastPressLongEventTime = now;
LOG_DEBUG("Rotary event Press long");
e.inputEvent = this->_eventPressedLong;
}
}
LOG_DEBUG("Rotary event Press");
e.inputEvent = this->_eventPressed;
} else if (this->action == ROTARY_ACTION_CW) {
LOG_DEBUG("Rotary event CW");
e.inputEvent = this->_eventCw;
@@ -91,9 +62,7 @@ int32_t RotaryEncoderInterruptBase::runOnce()
this->notifyObservers(&e);
}
if (!pressDetected) {
this->action = ROTARY_ACTION_NONE;
}
this->action = ROTARY_ACTION_NONE;
return INT32_MAX;
}
@@ -101,7 +70,7 @@ int32_t RotaryEncoderInterruptBase::runOnce()
void RotaryEncoderInterruptBase::intPressHandler()
{
this->action = ROTARY_ACTION_PRESSED;
setIntervalFromNow(20); // start checking for long/short
setIntervalFromNow(20); // TODO: this modifies a non-volatile variable!
}
void RotaryEncoderInterruptBase::intAHandler()

View File

@@ -13,7 +13,7 @@ class RotaryEncoderInterruptBase : public Observable<const InputEvent *>, public
public:
explicit RotaryEncoderInterruptBase(const char *name);
void init(uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw,
input_broker_event eventPressed, input_broker_event eventPressedLong,
input_broker_event eventPressed,
// std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)> onIntPress);
void (*onIntA)(), void (*onIntB)(), void (*onIntPress)());
void intPressHandler();
@@ -33,22 +33,10 @@ class RotaryEncoderInterruptBase : public Observable<const InputEvent *>, public
volatile RotaryEncoderInterruptBaseActionType action = ROTARY_ACTION_NONE;
private:
// pins and events
uint8_t _pinA = 0;
uint8_t _pinB = 0;
uint8_t _pinPress = 0;
input_broker_event _eventCw = INPUT_BROKER_NONE;
input_broker_event _eventCcw = INPUT_BROKER_NONE;
input_broker_event _eventPressed = INPUT_BROKER_NONE;
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
const char *_originName;
// Long press detection variables
uint32_t pressStartTime = 0;
bool pressDetected = false;
uint32_t lastPressLongEventTime = 0;
unsigned long lastPressKeyTime = 0;
static const uint32_t LONG_PRESS_DURATION = 300; // ms
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 0; // 0 = single-shot for rotary select
const unsigned long pressDebounceMs = 200; // ms
};

View File

@@ -1,6 +1,5 @@
#include "RotaryEncoderInterruptImpl1.h"
#include "InputBroker.h"
extern bool osk_found;
RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
@@ -20,14 +19,12 @@ bool RotaryEncoderInterruptImpl1::init()
input_broker_event eventCw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_cw);
input_broker_event eventCcw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_ccw);
input_broker_event eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press);
input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG;
// moduleConfig.canned_message.ext_notification_module_output
RotaryEncoderInterruptBase::init(pinA, pinB, pinPress, eventCw, eventCcw, eventPressed, eventPressedLong,
RotaryEncoderInterruptBase::init(pinA, pinB, pinPress, eventCw, eventCcw, eventPressed,
RotaryEncoderInterruptImpl1::handleIntA, RotaryEncoderInterruptImpl1::handleIntB,
RotaryEncoderInterruptImpl1::handleIntPressed);
inputBroker->registerSource(this);
osk_found = true;
return true;
}

View File

@@ -18,7 +18,7 @@ TouchScreenImpl1::TouchScreenImpl1(uint16_t width, uint16_t height, bool (*getTo
void TouchScreenImpl1::init()
{
#if ARCH_PORTDUINO
if (portduino_config.touchscreenModule) {
if (settingsMap[touchscreenModule]) {
TouchScreenBase::init(true);
inputBroker->registerSource(this);
} else {

View File

@@ -1,14 +1,12 @@
#include "TrackballInterruptBase.h"
#include "configuration.h"
extern bool osk_found;
TrackballInterruptBase::TrackballInterruptBase(const char *name) : concurrency::OSThread(name), _originName(name) {}
void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLeft, uint8_t pinRight, uint8_t pinPress,
input_broker_event eventDown, input_broker_event eventUp, input_broker_event eventLeft,
input_broker_event eventRight, input_broker_event eventPressed,
input_broker_event eventPressedLong, void (*onIntDown)(), void (*onIntUp)(),
void (*onIntLeft)(), void (*onIntRight)(), void (*onIntPress)())
input_broker_event eventRight, input_broker_event eventPressed, void (*onIntDown)(),
void (*onIntUp)(), void (*onIntLeft)(), void (*onIntRight)(), void (*onIntPress)())
{
this->_pinDown = pinDown;
this->_pinUp = pinUp;
@@ -20,7 +18,6 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
this->_eventLeft = eventLeft;
this->_eventRight = eventRight;
this->_eventPressed = eventPressed;
this->_eventPressedLong = eventPressedLong;
if (pinPress != 255) {
pinMode(pinPress, INPUT_PULLUP);
@@ -43,9 +40,9 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
attachInterrupt(this->_pinRight, onIntRight, TB_DIRECTION);
}
LOG_DEBUG("Trackball GPIO initialized - UP:%d DOWN:%d LEFT:%d RIGHT:%d PRESS:%d", this->_pinUp, this->_pinDown,
this->_pinLeft, this->_pinRight, pinPress);
osk_found = true;
LOG_DEBUG("Trackball GPIO initialized (%d, %d, %d, %d, %d)", this->_pinUp, this->_pinDown, this->_pinLeft, this->_pinRight,
pinPress);
this->setInterval(100);
}
@@ -53,47 +50,10 @@ int32_t TrackballInterruptBase::runOnce()
{
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
// Handle long press detection for press button
if (pressDetected && pressStartTime > 0) {
uint32_t pressDuration = millis() - pressStartTime;
bool buttonStillPressed = false;
#if defined(T_DECK)
buttonStillPressed = (this->action == TB_ACTION_PRESSED);
#else
buttonStillPressed = !digitalRead(_pinPress);
#endif
if (!buttonStillPressed) {
// Button released
if (pressDuration < LONG_PRESS_DURATION) {
// Short press
e.inputEvent = this->_eventPressed;
}
// Reset state
pressDetected = false;
pressStartTime = 0;
lastLongPressEventTime = 0;
this->action = TB_ACTION_NONE;
} else if (pressDuration >= LONG_PRESS_DURATION) {
// Long press detected
uint32_t currentTime = millis();
// Only trigger long press event if enough time has passed since the last one
if (lastLongPressEventTime == 0 || (currentTime - lastLongPressEventTime) >= LONG_PRESS_REPEAT_INTERVAL) {
e.inputEvent = this->_eventPressedLong;
lastLongPressEventTime = currentTime;
}
this->action = TB_ACTION_PRESSED_LONG;
}
}
#if defined(T_DECK) // T-deck gets a super-simple debounce on trackball
if (this->action == TB_ACTION_PRESSED && !pressDetected) {
// Start long press detection
pressDetected = true;
pressStartTime = millis();
// Don't send event yet, wait to see if it's a long press
if (this->action == TB_ACTION_PRESSED) {
// LOG_DEBUG("Trackball event Press");
e.inputEvent = this->_eventPressed;
} else if (this->action == TB_ACTION_UP && lastEvent == TB_ACTION_UP) {
// LOG_DEBUG("Trackball event UP");
e.inputEvent = this->_eventUp;
@@ -108,11 +68,9 @@ int32_t TrackballInterruptBase::runOnce()
e.inputEvent = this->_eventRight;
}
#else
if (this->action == TB_ACTION_PRESSED && !digitalRead(_pinPress) && !pressDetected) {
// Start long press detection
pressDetected = true;
pressStartTime = millis();
// Don't send event yet, wait to see if it's a long press
if (this->action == TB_ACTION_PRESSED && !digitalRead(_pinPress)) {
// LOG_DEBUG("Trackball event Press");
e.inputEvent = this->_eventPressed;
} else if (this->action == TB_ACTION_UP && !digitalRead(_pinUp)) {
// LOG_DEBUG("Trackball event UP");
e.inputEvent = this->_eventUp;
@@ -133,16 +91,10 @@ int32_t TrackballInterruptBase::runOnce()
e.kbchar = 0x00;
this->notifyObservers(&e);
}
lastEvent = action;
this->action = TB_ACTION_NONE;
// Only update lastEvent for non-press actions or completed press actions
if (this->action != TB_ACTION_PRESSED || !pressDetected) {
lastEvent = action;
if (!pressDetected) {
this->action = TB_ACTION_NONE;
}
}
return 50; // Check more frequently for better long press detection
return 100;
}
void TrackballInterruptBase::intPressHandler()

View File

@@ -6,7 +6,7 @@
#ifndef TB_DIRECTION
#if ARCH_PORTDUINO
#include "PortduinoGlue.h"
#define TB_DIRECTION (PinStatus) portduino_config.lora_usb_vid
#define TB_DIRECTION (PinStatus) settingsMap[tbDirection]
#else
#define TB_DIRECTION RISING
#endif
@@ -18,8 +18,8 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
explicit TrackballInterruptBase(const char *name);
void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLeft, uint8_t pinRight, uint8_t pinPress, input_broker_event eventDown,
input_broker_event eventUp, input_broker_event eventLeft, input_broker_event eventRight,
input_broker_event eventPressed, input_broker_event eventPressedLong, void (*onIntDown)(), void (*onIntUp)(),
void (*onIntLeft)(), void (*onIntRight)(), void (*onIntPress)());
input_broker_event eventPressed, void (*onIntDown)(), void (*onIntUp)(), void (*onIntLeft)(), void (*onIntRight)(),
void (*onIntPress)());
void intPressHandler();
void intDownHandler();
void intUpHandler();
@@ -33,7 +33,6 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
enum TrackballInterruptBaseActionType {
TB_ACTION_NONE,
TB_ACTION_PRESSED,
TB_ACTION_PRESSED_LONG,
TB_ACTION_UP,
TB_ACTION_DOWN,
TB_ACTION_LEFT,
@@ -47,20 +46,12 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
volatile TrackballInterruptBaseActionType action = TB_ACTION_NONE;
// Long press detection for press button
uint32_t pressStartTime = 0;
bool pressDetected = false;
uint32_t lastLongPressEventTime = 0;
static const uint32_t LONG_PRESS_DURATION = 500; // ms
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 500; // ms - interval between repeated long press events
private:
input_broker_event _eventDown = INPUT_BROKER_NONE;
input_broker_event _eventUp = INPUT_BROKER_NONE;
input_broker_event _eventLeft = INPUT_BROKER_NONE;
input_broker_event _eventRight = INPUT_BROKER_NONE;
input_broker_event _eventPressed = INPUT_BROKER_NONE;
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
const char *_originName;
TrackballInterruptBaseActionType lastEvent = TB_ACTION_NONE;
};

View File

@@ -13,12 +13,11 @@ void TrackballInterruptImpl1::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLe
input_broker_event eventLeft = INPUT_BROKER_LEFT;
input_broker_event eventRight = INPUT_BROKER_RIGHT;
input_broker_event eventPressed = INPUT_BROKER_SELECT;
input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG;
TrackballInterruptBase::init(pinDown, pinUp, pinLeft, pinRight, pinPress, eventDown, eventUp, eventLeft, eventRight,
eventPressed, eventPressedLong, TrackballInterruptImpl1::handleIntDown,
TrackballInterruptImpl1::handleIntUp, TrackballInterruptImpl1::handleIntLeft,
TrackballInterruptImpl1::handleIntRight, TrackballInterruptImpl1::handleIntPressed);
eventPressed, TrackballInterruptImpl1::handleIntDown, TrackballInterruptImpl1::handleIntUp,
TrackballInterruptImpl1::handleIntLeft, TrackballInterruptImpl1::handleIntRight,
TrackballInterruptImpl1::handleIntPressed);
inputBroker->registerSource(this);
}

View File

@@ -7,22 +7,14 @@ UpDownInterruptBase::UpDownInterruptBase(const char *name) : concurrency::OSThre
}
void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown,
input_broker_event eventUp, input_broker_event eventPressed, input_broker_event eventPressedLong,
input_broker_event eventUpLong, input_broker_event eventDownLong, void (*onIntDown)(),
input_broker_event eventUp, input_broker_event eventPressed, void (*onIntDown)(),
void (*onIntUp)(), void (*onIntPress)(), unsigned long updownDebounceMs)
{
this->_pinDown = pinDown;
this->_pinUp = pinUp;
this->_pinPress = pinPress;
this->_eventDown = eventDown;
this->_eventUp = eventUp;
this->_eventPressed = eventPressed;
this->_eventPressedLong = eventPressedLong;
this->_eventUpLong = eventUpLong;
this->_eventDownLong = eventDownLong;
// Store debounce configuration passed by caller
this->updownDebounceMs = updownDebounceMs;
bool isRAK = false;
#ifdef RAK_4631
isRAK = true;
@@ -30,20 +22,20 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress,
if (!isRAK || pinPress != 0) {
pinMode(pinPress, INPUT_PULLUP);
attachInterrupt(pinPress, onIntPress, FALLING);
attachInterrupt(pinPress, onIntPress, RISING);
}
if (!isRAK || this->_pinDown != 0) {
pinMode(this->_pinDown, INPUT_PULLUP);
attachInterrupt(this->_pinDown, onIntDown, FALLING);
attachInterrupt(this->_pinDown, onIntDown, RISING);
}
if (!isRAK || this->_pinUp != 0) {
pinMode(this->_pinUp, INPUT_PULLUP);
attachInterrupt(this->_pinUp, onIntUp, FALLING);
attachInterrupt(this->_pinUp, onIntUp, RISING);
}
LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)", this->_pinUp, this->_pinDown, pinPress);
this->setInterval(20);
this->setInterval(100);
}
int32_t UpDownInterruptBase::runOnce()
@@ -51,88 +43,23 @@ int32_t UpDownInterruptBase::runOnce()
InputEvent e;
e.inputEvent = INPUT_BROKER_NONE;
unsigned long now = millis();
// Read all button states once at the beginning
bool pressButtonPressed = !digitalRead(_pinPress);
bool upButtonPressed = !digitalRead(_pinUp);
bool downButtonPressed = !digitalRead(_pinDown);
// Handle initial button press detection - only if not already detected
if (this->action == UPDOWN_ACTION_PRESSED && pressButtonPressed && !pressDetected) {
pressDetected = true;
pressStartTime = now;
} else if (this->action == UPDOWN_ACTION_UP && upButtonPressed && !upDetected) {
upDetected = true;
upStartTime = now;
} else if (this->action == UPDOWN_ACTION_DOWN && downButtonPressed && !downDetected) {
downDetected = true;
downStartTime = now;
}
// Handle long press detection for press button
if (pressDetected && pressStartTime > 0) {
uint32_t pressDuration = now - pressStartTime;
if (!pressButtonPressed) {
// Button released
if (pressDuration < LONG_PRESS_DURATION && now - lastPressKeyTime >= pressDebounceMs) {
lastPressKeyTime = now;
e.inputEvent = this->_eventPressed;
}
// Reset state
pressDetected = false;
pressStartTime = 0;
lastPressLongEventTime = 0;
} else if (pressDuration >= LONG_PRESS_DURATION && lastPressLongEventTime == 0) {
// First long press event only - avoid repeated events causing lag
e.inputEvent = this->_eventPressedLong;
lastPressLongEventTime = now;
if (this->action == UPDOWN_ACTION_PRESSED) {
if (now - lastPressKeyTime >= pressDebounceMs) {
lastPressKeyTime = now;
LOG_DEBUG("GPIO event Press");
e.inputEvent = this->_eventPressed;
}
}
// Handle long press detection for up button
if (upDetected && upStartTime > 0) {
uint32_t upDuration = now - upStartTime;
if (!upButtonPressed) {
// Button released
if (upDuration < LONG_PRESS_DURATION && now - lastUpKeyTime >= updownDebounceMs) {
lastUpKeyTime = now;
e.inputEvent = this->_eventUp;
}
// Reset state
upDetected = false;
upStartTime = 0;
lastUpLongEventTime = 0;
} else if (upDuration >= LONG_PRESS_DURATION) {
// Auto-repeat long press events
if (lastUpLongEventTime == 0 || (now - lastUpLongEventTime) >= LONG_PRESS_REPEAT_INTERVAL) {
e.inputEvent = this->_eventUpLong;
lastUpLongEventTime = now;
}
} else if (this->action == UPDOWN_ACTION_UP) {
if (now - lastUpKeyTime >= updownDebounceMs) {
lastUpKeyTime = now;
LOG_DEBUG("GPIO event Up");
e.inputEvent = this->_eventUp;
}
}
// Handle long press detection for down button
if (downDetected && downStartTime > 0) {
uint32_t downDuration = now - downStartTime;
if (!downButtonPressed) {
// Button released
if (downDuration < LONG_PRESS_DURATION && now - lastDownKeyTime >= updownDebounceMs) {
lastDownKeyTime = now;
e.inputEvent = this->_eventDown;
}
// Reset state
downDetected = false;
downStartTime = 0;
lastDownLongEventTime = 0;
} else if (downDuration >= LONG_PRESS_DURATION) {
// Auto-repeat long press events
if (lastDownLongEventTime == 0 || (now - lastDownLongEventTime) >= LONG_PRESS_REPEAT_INTERVAL) {
e.inputEvent = this->_eventDownLong;
lastDownLongEventTime = now;
}
} else if (this->action == UPDOWN_ACTION_DOWN) {
if (now - lastDownKeyTime >= updownDebounceMs) {
lastDownKeyTime = now;
LOG_DEBUG("GPIO event Down");
e.inputEvent = this->_eventDown;
}
}
@@ -142,11 +69,8 @@ int32_t UpDownInterruptBase::runOnce()
this->notifyObservers(&e);
}
if (!pressDetected && !upDetected && !downDetected) {
this->action = UPDOWN_ACTION_NONE;
}
return 20; // This will control how the input frequency
this->action = UPDOWN_ACTION_NONE;
return 100;
}
void UpDownInterruptBase::intPressHandler()

View File

@@ -8,8 +8,7 @@ class UpDownInterruptBase : public Observable<const InputEvent *>, public concur
public:
explicit UpDownInterruptBase(const char *name);
void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown, input_broker_event eventUp,
input_broker_event eventPressed, input_broker_event eventPressedLong, input_broker_event eventUpLong,
input_broker_event eventDownLong, void (*onIntDown)(), void (*onIntUp)(), void (*onIntPress)(),
input_broker_event eventPressed, void (*onIntDown)(), void (*onIntUp)(), void (*onIntPress)(),
unsigned long updownDebounceMs = 50);
void intPressHandler();
void intDownHandler();
@@ -18,41 +17,16 @@ class UpDownInterruptBase : public Observable<const InputEvent *>, public concur
int32_t runOnce() override;
protected:
enum UpDownInterruptBaseActionType {
UPDOWN_ACTION_NONE,
UPDOWN_ACTION_PRESSED,
UPDOWN_ACTION_PRESSED_LONG,
UPDOWN_ACTION_UP,
UPDOWN_ACTION_UP_LONG,
UPDOWN_ACTION_DOWN,
UPDOWN_ACTION_DOWN_LONG
};
enum UpDownInterruptBaseActionType { UPDOWN_ACTION_NONE, UPDOWN_ACTION_PRESSED, UPDOWN_ACTION_UP, UPDOWN_ACTION_DOWN };
volatile UpDownInterruptBaseActionType action = UPDOWN_ACTION_NONE;
// Long press detection variables
uint32_t pressStartTime = 0;
uint32_t upStartTime = 0;
uint32_t downStartTime = 0;
bool pressDetected = false;
bool upDetected = false;
bool downDetected = false;
uint32_t lastPressLongEventTime = 0;
uint32_t lastUpLongEventTime = 0;
uint32_t lastDownLongEventTime = 0;
static const uint32_t LONG_PRESS_DURATION = 300;
static const uint32_t LONG_PRESS_REPEAT_INTERVAL = 300;
private:
uint8_t _pinDown = 0;
uint8_t _pinUp = 0;
uint8_t _pinPress = 0;
input_broker_event _eventDown = INPUT_BROKER_NONE;
input_broker_event _eventUp = INPUT_BROKER_NONE;
input_broker_event _eventPressed = INPUT_BROKER_NONE;
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
input_broker_event _eventUpLong = INPUT_BROKER_NONE;
input_broker_event _eventDownLong = INPUT_BROKER_NONE;
const char *_originName;
unsigned long lastUpKeyTime = 0;

View File

@@ -1,6 +1,5 @@
#include "UpDownInterruptImpl1.h"
#include "InputBroker.h"
extern bool osk_found;
UpDownInterruptImpl1 *upDownInterruptImpl1;
@@ -18,18 +17,13 @@ bool UpDownInterruptImpl1::init()
uint8_t pinDown = moduleConfig.canned_message.inputbroker_pin_b;
uint8_t pinPress = moduleConfig.canned_message.inputbroker_pin_press;
input_broker_event eventDown = INPUT_BROKER_USER_PRESS; // acts like RIGHT/DOWN
input_broker_event eventUp = INPUT_BROKER_ALT_PRESS; // acts like LEFT/UP
input_broker_event eventDown = INPUT_BROKER_DOWN;
input_broker_event eventUp = INPUT_BROKER_UP;
input_broker_event eventPressed = INPUT_BROKER_SELECT;
input_broker_event eventPressedLong = INPUT_BROKER_SELECT_LONG;
input_broker_event eventUpLong = INPUT_BROKER_UP_LONG;
input_broker_event eventDownLong = INPUT_BROKER_DOWN_LONG;
UpDownInterruptBase::init(pinDown, pinUp, pinPress, eventDown, eventUp, eventPressed, eventPressedLong, eventUpLong,
eventDownLong, UpDownInterruptImpl1::handleIntDown, UpDownInterruptImpl1::handleIntUp,
UpDownInterruptImpl1::handleIntPressed);
UpDownInterruptBase::init(pinDown, pinUp, pinPress, eventDown, eventUp, eventPressed, UpDownInterruptImpl1::handleIntDown,
UpDownInterruptImpl1::handleIntUp, UpDownInterruptImpl1::handleIntPressed);
inputBroker->registerSource(this);
osk_found = true;
return true;
}

View File

@@ -192,8 +192,6 @@ ScanI2C::DeviceAddress cardkb_found = ScanI2C::ADDRESS_NONE;
uint8_t kb_model;
// global bool to record that a kb is present
bool kb_found = false;
// global bool to record that on-screen keyboard (OSK) is present
bool osk_found = false;
// The I2C address of the RTC Module (if found)
ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE;
@@ -390,7 +388,7 @@ void setup()
concurrency::hasBeenSetup = true;
#if ARCH_PORTDUINO
SPISettings spiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0);
SPISettings spiSettings(settingsMap[spiSpeed], MSBFIRST, SPI_MODE0);
#else
SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
#endif
@@ -535,9 +533,9 @@ void setup()
#elif defined(I2C_SDA) && !defined(ARCH_RP2040)
Wire.begin(I2C_SDA, I2C_SCL);
#elif defined(ARCH_PORTDUINO)
if (portduino_config.i2cdev != "") {
LOG_INFO("Use %s as I2C device", portduino_config.i2cdev.c_str());
Wire.begin(portduino_config.i2cdev.c_str());
if (settingsStrings[i2cdev] != "") {
LOG_INFO("Use %s as I2C device", settingsStrings[i2cdev].c_str());
Wire.begin(settingsStrings[i2cdev].c_str());
} else {
LOG_INFO("No I2C device configured, Skip");
}
@@ -583,7 +581,7 @@ void setup()
#if defined(I2C_SDA)
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE);
#elif defined(ARCH_PORTDUINO)
if (portduino_config.i2cdev != "") {
if (settingsStrings[i2cdev] != "") {
LOG_INFO("Scan for i2c devices");
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE);
}
@@ -856,7 +854,7 @@ void setup()
SPI.begin(false);
#endif // HW_SPI1_DEVICE
#elif ARCH_PORTDUINO
if (portduino_config.lora_spi_dev != "ch341") {
if (settingsStrings[spidev] != "ch341") {
SPI.begin();
}
#elif !defined(ARCH_ESP32) // ARCH_RP2040
@@ -882,7 +880,7 @@ void setup()
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS) || defined(ST7796_CS)
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
#elif defined(ARCH_PORTDUINO)
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || portduino_config.displayPanel) &&
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
}
@@ -983,13 +981,13 @@ void setup()
#endif
#if defined(ARCH_PORTDUINO)
if (portduino_config.userButtonPin.enabled) {
if (settingsMap.count(userButtonPin) != 0 && settingsMap[userButtonPin] != RADIOLIB_NC) {
LOG_DEBUG("Use GPIO%02d for button", portduino_config.userButtonPin.pin);
LOG_DEBUG("Use GPIO%02d for button", settingsMap[userButtonPin]);
UserButtonThread = new ButtonThread("UserButton");
if (screen) {
ButtonConfig config;
config.pinNumber = (uint8_t)portduino_config.userButtonPin.pin;
config.pinNumber = (uint8_t)settingsMap[userButtonPin];
config.activeLow = true;
config.activePullup = true;
config.pullupSense = INPUT_PULLUP;
@@ -1146,7 +1144,7 @@ void setup()
if (screen)
screen->setup();
#elif defined(ARCH_PORTDUINO)
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || portduino_config.displayPanel) &&
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
screen->setup();
}
@@ -1162,10 +1160,15 @@ void setup()
#endif
#ifdef ARCH_PORTDUINO
const struct {
configNames cfgName;
std::string strName;
} loraModules[] = {{use_rf95, "RF95"}, {use_sx1262, "sx1262"}, {use_sx1268, "sx1268"}, {use_sx1280, "sx1280"},
{use_lr1110, "lr1110"}, {use_lr1120, "lr1120"}, {use_lr1121, "lr1121"}, {use_llcc68, "LLCC68"}};
// as one can't use a function pointer to the class constructor:
auto loraModuleInterface = [](LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
RADIOLIB_PIN_TYPE busy) {
switch (portduino_config.lora_module) {
auto loraModuleInterface = [](configNames cfgName, LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq,
RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy) {
switch (cfgName) {
case use_rf95:
return (RadioInterface *)new RF95Interface(hal, cs, irq, rst, busy);
case use_sx1262:
@@ -1182,34 +1185,31 @@ void setup()
return (RadioInterface *)new LR1121Interface(hal, cs, irq, rst, busy);
case use_llcc68:
return (RadioInterface *)new LLCC68Interface(hal, cs, irq, rst, busy);
case use_simradio:
return (RadioInterface *)new SimRadio;
default:
assert(0); // shouldn't happen
return (RadioInterface *)nullptr;
}
};
LOG_DEBUG("Activate %s radio on SPI port %s", portduino_config.loraModules[portduino_config.lora_module].c_str(),
portduino_config.lora_spi_dev.c_str());
if (portduino_config.lora_spi_dev == "ch341") {
RadioLibHAL = ch341Hal;
} else {
RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
for (auto &loraModule : loraModules) {
if (settingsMap[loraModule.cfgName] && !rIf) {
LOG_DEBUG("Activate %s radio on SPI port %s", loraModule.strName.c_str(), settingsStrings[spidev].c_str());
if (settingsStrings[spidev] == "ch341") {
RadioLibHAL = ch341Hal;
} else {
RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
}
rIf = loraModuleInterface(loraModule.cfgName, (LockingArduinoHal *)RadioLibHAL, settingsMap[cs_pin],
settingsMap[irq_pin], settingsMap[reset_pin], settingsMap[busy_pin]);
if (!rIf->init()) {
LOG_WARN("No %s radio", loraModule.strName.c_str());
delete rIf;
rIf = NULL;
exit(EXIT_FAILURE);
} else {
LOG_INFO("%s init success", loraModule.strName.c_str());
}
}
}
rIf =
loraModuleInterface((LockingArduinoHal *)RadioLibHAL, portduino_config.lora_cs_pin.pin, portduino_config.lora_irq_pin.pin,
portduino_config.lora_reset_pin.pin, portduino_config.lora_busy_pin.pin);
if (!rIf->init()) {
LOG_WARN("No %s radio", portduino_config.loraModules[portduino_config.lora_module].c_str());
delete rIf;
rIf = NULL;
exit(EXIT_FAILURE);
} else {
LOG_INFO("%s init success", portduino_config.loraModules[portduino_config.lora_module].c_str());
}
#elif defined(HW_SPI1_DEVICE)
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI1, spiSettings);
#else // HW_SPI1_DEVICE
@@ -1231,6 +1231,20 @@ void setup()
}
#endif
#if defined(ARCH_PORTDUINO)
if (!rIf) {
rIf = new SimRadio;
if (!rIf->init()) {
LOG_WARN("No simulated radio");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("Use SIMULATED radio!");
radioType = SIM_RADIO;
}
}
#endif
#if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1
if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1);
@@ -1434,10 +1448,6 @@ void setup()
#endif
#endif
#if defined(HAS_TRACKBALL) || (defined(INPUTDRIVER_ENCODER_TYPE) && INPUTDRIVER_ENCODER_TYPE == 2)
osk_found = true;
#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER
// Start web server thread.
webServerThread = new WebServerThread();
@@ -1445,7 +1455,7 @@ void setup()
#ifdef ARCH_PORTDUINO
#if __has_include(<ulfius.h>)
if (portduino_config.webserverport != -1) {
if (settingsMap[webserverport] != -1) {
piwebServerThread = new PiWebServerThread();
std::atexit([] { delete piwebServerThread; });
}
@@ -1506,9 +1516,6 @@ extern meshtastic_DeviceMetadata getDeviceMetadata()
deviceMetadata.hw_model = HW_VENDOR;
deviceMetadata.hasRemoteHardware = moduleConfig.remote_hardware.enabled;
deviceMetadata.excluded_modules = meshtastic_ExcludedModules_EXCLUDED_NONE;
#if MESHTASTIC_EXCLUDE_MQTT
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_MQTT_CONFIG;
#endif
#if MESHTASTIC_EXCLUDE_REMOTEHARDWARE
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_REMOTEHARDWARE_CONFIG;
#endif
@@ -1531,21 +1538,10 @@ extern meshtastic_DeviceMetadata getDeviceMetadata()
#if NO_EXT_GPIO && NO_GPS || MESHTASTIC_EXCLUDE_SERIAL
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_SERIAL_CONFIG;
#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_PAXCOUNTER
// PAXCOUNTER is only supported on ESP32 due to memory constraints
#else
#ifndef ARCH_ESP32
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_PAXCOUNTER_CONFIG;
#endif
#if MESHTASTIC_EXCLUDE_STOREFORWARD
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_STOREFORWARD_CONFIG;
#endif
#if MESHTASTIC_EXCLUDE_RANGETEST
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_RANGETEST_CONFIG;
#endif
#if MESHTASTIC_EXCLUDE_NEIGHBORINFO
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_NEIGHBORINFO_CONFIG;
#endif
#if (!defined(HAS_RGB_LED) && !defined(RAK_4631)) || defined(MESHTASTIC_EXCLUDE_AMBIENTLIGHTING)
#if !defined(HAS_RGB_LED) && !RAK_4631
deviceMetadata.excluded_modules |= meshtastic_ExcludedModules_AMBIENTLIGHTING_CONFIG;
#endif

View File

@@ -32,7 +32,6 @@ extern ScanI2C::DeviceAddress screen_found;
extern ScanI2C::DeviceAddress cardkb_found;
extern uint8_t kb_model;
extern bool kb_found;
extern bool osk_found;
extern ScanI2C::DeviceAddress rtc_found;
extern ScanI2C::DeviceAddress accelerometer_found;
extern ScanI2C::FoundDevice rgb_found;

View File

@@ -21,7 +21,7 @@ static const Module::RfSwitchMode_t rfswitch_table[] = {
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not
// specified (may be dangerous if using external PA and LR11x0 power config forgotten)
#if ARCH_PORTDUINO
#define LR1110_MAX_POWER portduino_config.lr1110_max_power
#define LR1110_MAX_POWER settingsMap[lr1110_max_power]
#endif
#ifndef LR1110_MAX_POWER
#define LR1110_MAX_POWER 22
@@ -30,7 +30,7 @@ static const Module::RfSwitchMode_t rfswitch_table[] = {
// the 2.4G part maxes at 13dBm
#if ARCH_PORTDUINO
#define LR1120_MAX_POWER portduino_config.lr1120_max_power
#define LR1120_MAX_POWER settingsMap[lr1120_max_power]
#endif
#ifndef LR1120_MAX_POWER
#define LR1120_MAX_POWER 13
@@ -55,7 +55,7 @@ template <typename T> bool LR11x0Interface<T>::init()
#endif
#if ARCH_PORTDUINO
float tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
float tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000;
// FIXME: correct logic to default to not using TCXO if no voltage is specified for LR11x0_DIO3_TCXO_VOLTAGE
#elif !defined(LR11X0_DIO3_TCXO_VOLTAGE)
float tcxoVoltage =

View File

@@ -673,7 +673,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
#endif
#elif ARCH_PORTDUINO
bool hasScreen = false;
if (portduino_config.displayPanel)
if (settingsMap[displayPanel])
hasScreen = true;
else
hasScreen = screen_found.port != ScanI2C::I2CPort::NO_I2C;
@@ -775,9 +775,7 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.version = DEVICESTATE_CUR_VER;
moduleConfig.has_mqtt = true;
#if !MESHTASTIC_EXCLUDE_RANGETEST
moduleConfig.has_range_test = true;
#endif
moduleConfig.has_serial = true;
moduleConfig.has_store_forward = true;
moduleConfig.has_telemetry = true;
@@ -843,12 +841,6 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.canned_message.inputbroker_event_press = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
#endif
moduleConfig.has_canned_message = true;
#if !MESHTASTIC_EXCLUDE_AUDIO
moduleConfig.has_audio = true;
#endif
#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
moduleConfig.has_paxcounter = true;
#endif
#if USERPREFS_MQTT_ENABLED && !MESHTASTIC_EXCLUDE_MQTT
moduleConfig.mqtt.enabled = true;
#endif
@@ -891,14 +883,12 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.detection_sensor.detection_trigger_type = meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH;
moduleConfig.detection_sensor.minimum_broadcast_secs = 45;
#if !MESHTASTIC_EXCLUDE_AMBIENTLIGHTING
moduleConfig.has_ambient_lighting = true;
moduleConfig.ambient_lighting.current = 10;
// Default to a color based on our node number
moduleConfig.ambient_lighting.red = (myNodeInfo.my_node_num & 0xFF0000) >> 16;
moduleConfig.ambient_lighting.green = (myNodeInfo.my_node_num & 0x00FF00) >> 8;
moduleConfig.ambient_lighting.blue = myNodeInfo.my_node_num & 0x0000FF;
#endif
initModuleConfigIntervals();
}
@@ -1344,8 +1334,8 @@ void NodeDB::loadFromDisk()
}
#if ARCH_PORTDUINO
// set any config overrides
if (portduino_config.has_configDisplayMode) {
config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)portduino_config.configDisplayMode;
if (settingsMap[has_configDisplayMode]) {
config.display.displaymode = (_meshtastic_Config_DisplayConfig_DisplayMode)settingsMap[configDisplayMode];
}
#endif
@@ -1438,25 +1428,15 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
moduleConfig.has_canned_message = true;
moduleConfig.has_external_notification = true;
moduleConfig.has_mqtt = true;
#if !MESHTASTIC_EXCLUDE_RANGETEST
moduleConfig.has_range_test = true;
#endif
moduleConfig.has_serial = true;
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
moduleConfig.has_store_forward = true;
#endif
moduleConfig.has_telemetry = true;
moduleConfig.has_neighbor_info = true;
moduleConfig.has_detection_sensor = true;
#if !MESHTASTIC_EXCLUDE_AMBIENTLIGHTING
moduleConfig.has_ambient_lighting = true;
#endif
#if !MESHTASTIC_EXCLUDE_AUDIO
moduleConfig.has_audio = true;
#endif
#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
moduleConfig.has_paxcounter = true;
#endif
success &=
saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);

View File

@@ -34,21 +34,6 @@
// Flag to indicate a heartbeat was received and we should send queue status
bool heartbeatReceived = false;
// Helper function to skip excluded module configs and advance state
size_t PhoneAPI::skipExcludedModuleConfig(uint8_t *buf)
{
config_state++;
if (config_state > (_meshtastic_AdminMessage_ModuleConfigType_MAX + 1)) {
if (config_nonce == SPECIAL_NONCE_ONLY_CONFIG) {
state = STATE_SEND_FILEMANIFEST;
} else {
state = STATE_SEND_OTHER_NODEINFOS;
}
config_state = 0;
}
return getFromRadio(buf);
}
PhoneAPI::PhoneAPI()
{
lastContactMsec = millis();
@@ -369,35 +354,20 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.moduleConfig.payload_variant.serial = moduleConfig.serial;
break;
case meshtastic_ModuleConfig_external_notification_tag:
#if !(NO_EXT_GPIO || MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION)
LOG_DEBUG("Send module config: ext notification");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_external_notification_tag;
fromRadioScratch.moduleConfig.payload_variant.external_notification = moduleConfig.external_notification;
break;
#else
LOG_DEBUG("External Notification module excluded from build, skipping");
return skipExcludedModuleConfig(buf);
#endif
case meshtastic_ModuleConfig_store_forward_tag:
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
LOG_DEBUG("Send module config: store forward");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_store_forward_tag;
fromRadioScratch.moduleConfig.payload_variant.store_forward = moduleConfig.store_forward;
break;
#else
LOG_DEBUG("Store & Forward module excluded from build, skipping");
return skipExcludedModuleConfig(buf);
#endif
case meshtastic_ModuleConfig_range_test_tag:
#if !MESHTASTIC_EXCLUDE_RANGETEST
LOG_DEBUG("Send module config: range test");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_range_test_tag;
fromRadioScratch.moduleConfig.payload_variant.range_test = moduleConfig.range_test;
break;
#else
LOG_DEBUG("Range Test module excluded from build, skipping");
return skipExcludedModuleConfig(buf);
#endif
case meshtastic_ModuleConfig_telemetry_tag:
LOG_DEBUG("Send module config: telemetry");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_telemetry_tag;
@@ -409,15 +379,10 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.moduleConfig.payload_variant.canned_message = moduleConfig.canned_message;
break;
case meshtastic_ModuleConfig_audio_tag:
#if !MESHTASTIC_EXCLUDE_AUDIO
LOG_DEBUG("Send module config: audio");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_audio_tag;
fromRadioScratch.moduleConfig.payload_variant.audio = moduleConfig.audio;
break;
#else
LOG_DEBUG("Audio module excluded from build, skipping");
return skipExcludedModuleConfig(buf);
#endif
case meshtastic_ModuleConfig_remote_hardware_tag:
LOG_DEBUG("Send module config: remote hardware");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_remote_hardware_tag;
@@ -434,25 +399,15 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.moduleConfig.payload_variant.detection_sensor = moduleConfig.detection_sensor;
break;
case meshtastic_ModuleConfig_ambient_lighting_tag:
#if !MESHTASTIC_EXCLUDE_AMBIENTLIGHTING
LOG_DEBUG("Send module config: ambient lighting");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_ambient_lighting_tag;
fromRadioScratch.moduleConfig.payload_variant.ambient_lighting = moduleConfig.ambient_lighting;
break;
#else
LOG_DEBUG("Ambient Lighting module excluded from build, skipping");
return skipExcludedModuleConfig(buf);
#endif
case meshtastic_ModuleConfig_paxcounter_tag:
#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
LOG_DEBUG("Send module config: paxcounter");
fromRadioScratch.moduleConfig.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag;
fromRadioScratch.moduleConfig.payload_variant.paxcounter = moduleConfig.paxcounter;
break;
#else
LOG_DEBUG("Paxcounter module excluded from build, skipping");
return skipExcludedModuleConfig(buf);
#endif
default:
LOG_ERROR("Unknown module config type %d", config_state);
}

View File

@@ -172,7 +172,4 @@ class PhoneAPI
/// If the mesh service tells us fromNum has changed, tell the phone
virtual int onNotify(uint32_t newValue) override;
/// Helper function to skip excluded module configs and advance state
size_t skipExcludedModuleConfig(uint8_t *buf);
};

View File

@@ -10,7 +10,7 @@
#endif
#if ARCH_PORTDUINO
#define RF95_MAX_POWER portduino_config.rf95_max_power
#define RF95_MAX_POWER settingsMap[rf95_max_power]
#endif
#ifndef RF95_MAX_POWER
#define RF95_MAX_POWER 20
@@ -94,16 +94,16 @@ void RF95Interface::setTransmitEnable(bool txon)
#ifdef RF95_TXEN
digitalWrite(RF95_TXEN, txon ? 1 : 0);
#elif ARCH_PORTDUINO
if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
digitalWrite(portduino_config.lora_txen_pin.pin, txon ? 1 : 0);
if (settingsMap[txen_pin] != RADIOLIB_NC) {
digitalWrite(settingsMap[txen_pin], txon ? 1 : 0);
}
#endif
#ifdef RF95_RXEN
digitalWrite(RF95_RXEN, txon ? 0 : 1);
#elif ARCH_PORTDUINO
if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
digitalWrite(portduino_config.lora_rxen_pin.pin, txon ? 0 : 1);
if (settingsMap[rxen_pin] != RADIOLIB_NC) {
digitalWrite(settingsMap[rxen_pin], txon ? 0 : 1);
}
#endif
}
@@ -164,13 +164,13 @@ bool RF95Interface::init()
digitalWrite(RF95_RXEN, 1);
#endif
#if ARCH_PORTDUINO
if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
pinMode(portduino_config.lora_txen_pin.pin, OUTPUT);
digitalWrite(portduino_config.lora_txen_pin.pin, 0);
if (settingsMap[txen_pin] != RADIOLIB_NC) {
pinMode(settingsMap[txen_pin], OUTPUT);
digitalWrite(settingsMap[txen_pin], 0);
}
if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
pinMode(portduino_config.lora_rxen_pin.pin, OUTPUT);
digitalWrite(portduino_config.lora_rxen_pin.pin, 0);
if (settingsMap[rxen_pin] != RADIOLIB_NC) {
pinMode(settingsMap[rxen_pin], OUTPUT);
digitalWrite(settingsMap[rxen_pin], 0);
}
#endif
setTransmitEnable(false);

View File

@@ -32,12 +32,9 @@ const RegionInfo regions[] = {
RDEF(US, 902.0f, 928.0f, 100, 0, 30, true, false, false),
/*
EN300220 ETSI V3.2.1 [Table B.1, Item H, p. 21]
https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.02.01_60/en_30022002v030201p.pdf
FIXME: https://github.com/meshtastic/firmware/issues/3371
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
*/
RDEF(EU_433, 433.0f, 434.0f, 10, 0, 10, true, false, false),
RDEF(EU_433, 433.0f, 434.0f, 10, 0, 12, true, false, false),
/*
https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/

View File

@@ -417,13 +417,12 @@ void RadioLibInterface::handleReceiveInterrupt()
int state = iface->readData((uint8_t *)&radioBuffer, length);
#if ARCH_PORTDUINO
if (portduino_config.logoutputlevel == level_trace) {
if (settingsMap[logoutputlevel] == level_trace) {
printBytes("Raw incoming packet: ", (uint8_t *)&radioBuffer, length);
}
#endif
if (state != RADIOLIB_ERR_NONE) {
LOG_ERROR("Ignore received packet due to error=%d (maybe to=0x%08x, from=0x%08x, flags=0x%02x)", state,
radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags);
LOG_ERROR("Ignore received packet due to error=%d", state);
rxBad++;
airTime->logAirtime(RX_ALL_LOG, xmitMsec);

View File

@@ -446,7 +446,7 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
#if ENABLE_JSON_LOGGING
LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str());
#elif ARCH_PORTDUINO
if (portduino_config.traceFilename != "" || portduino_config.logoutputlevel == level_trace) {
if (settingsStrings[traceFilename] != "" || settingsMap[logoutputlevel] == level_trace) {
LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str());
}
#endif
@@ -685,7 +685,7 @@ void Router::perhapsHandleReceived(meshtastic_MeshPacket *p)
LOG_TRACE("%s", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str());
#elif ARCH_PORTDUINO
// Even ignored packets get logged in the trace
if (portduino_config.traceFilename != "" || portduino_config.logoutputlevel == level_trace) {
if (settingsStrings[traceFilename] != "" || settingsMap[logoutputlevel] == level_trace) {
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
LOG_TRACE("%s", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str());
}

View File

@@ -12,7 +12,7 @@
// Particular boards might define a different max power based on what their hardware can do, default to max power output if not
// specified (may be dangerous if using external PA and SX126x power config forgotten)
#if ARCH_PORTDUINO
#define SX126X_MAX_POWER portduino_config.sx126x_max_power
#define SX126X_MAX_POWER settingsMap[sx126x_max_power]
#endif
#ifndef SX126X_MAX_POWER
#define SX126X_MAX_POWER 22
@@ -53,10 +53,10 @@ template <typename T> bool SX126xInterface<T>::init()
#endif
#if ARCH_PORTDUINO
tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
if (portduino_config.lora_sx126x_ant_sw_pin.pin != RADIOLIB_NC) {
digitalWrite(portduino_config.lora_sx126x_ant_sw_pin.pin, HIGH);
pinMode(portduino_config.lora_sx126x_ant_sw_pin.pin, OUTPUT);
tcxoVoltage = (float)settingsMap[dio3_tcxo_voltage] / 1000;
if (settingsMap[sx126x_ant_sw_pin] != RADIOLIB_NC) {
digitalWrite(settingsMap[sx126x_ant_sw_pin], HIGH);
pinMode(settingsMap[sx126x_ant_sw_pin], OUTPUT);
}
#endif
if (tcxoVoltage == 0.0)
@@ -98,7 +98,7 @@ template <typename T> bool SX126xInterface<T>::init()
bool dio2AsRfSwitch = true;
#elif defined(ARCH_PORTDUINO)
bool dio2AsRfSwitch = false;
if (portduino_config.dio2_as_rf_switch) {
if (settingsMap[dio2_as_rf_switch]) {
dio2AsRfSwitch = true;
}
#else
@@ -112,9 +112,9 @@ template <typename T> bool SX126xInterface<T>::init()
// no effect
#if ARCH_PORTDUINO
if (res == RADIOLIB_ERR_NONE) {
LOG_DEBUG("Use MCU pin %i as RXEN and pin %i as TXEN to control RF switching", portduino_config.lora_rxen_pin.pin,
portduino_config.lora_txen_pin.pin);
lora.setRfSwitchPins(portduino_config.lora_rxen_pin.pin, portduino_config.lora_txen_pin.pin);
LOG_DEBUG("Use MCU pin %i as RXEN and pin %i as TXEN to control RF switching", settingsMap[rxen_pin],
settingsMap[txen_pin]);
lora.setRfSwitchPins(settingsMap[rxen_pin], settingsMap[txen_pin]);
}
#else
#ifndef SX126X_RXEN

View File

@@ -11,7 +11,7 @@
// Particular boards might define a different max power based on what their hardware can do
#if ARCH_PORTDUINO
#define SX128X_MAX_POWER portduino_config.sx128x_max_power
#define SX128X_MAX_POWER settingsMap[sx128x_max_power]
#endif
#ifndef SX128X_MAX_POWER
#define SX128X_MAX_POWER 13
@@ -41,13 +41,13 @@ template <typename T> bool SX128xInterface<T>::init()
#endif
#if ARCH_PORTDUINO
if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
pinMode(portduino_config.lora_rxen_pin.pin, OUTPUT);
digitalWrite(portduino_config.lora_rxen_pin.pin, LOW); // Set low before becoming an output
if (settingsMap[rxen_pin] != RADIOLIB_NC) {
pinMode(settingsMap[rxen_pin], OUTPUT);
digitalWrite(settingsMap[rxen_pin], LOW); // Set low before becoming an output
}
if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
pinMode(portduino_config.lora_txen_pin.pin, OUTPUT);
digitalWrite(portduino_config.lora_txen_pin.pin, LOW); // Set low before becoming an output
if (settingsMap[txen_pin] != RADIOLIB_NC) {
pinMode(settingsMap[txen_pin], OUTPUT);
digitalWrite(settingsMap[txen_pin], LOW); // Set low before becoming an output
}
#else
#if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // set not rx or tx mode
@@ -93,9 +93,8 @@ template <typename T> bool SX128xInterface<T>::init()
lora.setRfSwitchPins(SX128X_RXEN, SX128X_TXEN);
}
#elif ARCH_PORTDUINO
if (res == RADIOLIB_ERR_NONE && portduino_config.lora_rxen_pin.pin != RADIOLIB_NC &&
portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
lora.setRfSwitchPins(portduino_config.lora_rxen_pin.pin, portduino_config.lora_txen_pin.pin);
if (res == RADIOLIB_ERR_NONE && settingsMap[rxen_pin] != RADIOLIB_NC && settingsMap[txen_pin] != RADIOLIB_NC) {
lora.setRfSwitchPins(settingsMap[rxen_pin], settingsMap[txen_pin]);
}
#endif
@@ -175,11 +174,11 @@ template <typename T> void SX128xInterface<T>::setStandby()
LOG_ERROR("SX128x standby %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
#if ARCH_PORTDUINO
if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
digitalWrite(portduino_config.lora_rxen_pin.pin, LOW);
if (settingsMap[rxen_pin] != RADIOLIB_NC) {
digitalWrite(settingsMap[rxen_pin], LOW);
}
if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
digitalWrite(portduino_config.lora_txen_pin.pin, LOW);
if (settingsMap[txen_pin] != RADIOLIB_NC) {
digitalWrite(settingsMap[txen_pin], LOW);
}
#else
#if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn off RX and TX power
@@ -211,11 +210,11 @@ template <typename T> void SX128xInterface<T>::addReceiveMetadata(meshtastic_Mes
template <typename T> void SX128xInterface<T>::configHardwareForSend()
{
#if ARCH_PORTDUINO
if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
digitalWrite(portduino_config.lora_txen_pin.pin, HIGH);
if (settingsMap[txen_pin] != RADIOLIB_NC) {
digitalWrite(settingsMap[txen_pin], HIGH);
}
if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
digitalWrite(portduino_config.lora_rxen_pin.pin, LOW);
if (settingsMap[rxen_pin] != RADIOLIB_NC) {
digitalWrite(settingsMap[rxen_pin], LOW);
}
#else
@@ -242,11 +241,11 @@ template <typename T> void SX128xInterface<T>::startReceive()
setStandby();
#if ARCH_PORTDUINO
if (portduino_config.lora_rxen_pin.pin != RADIOLIB_NC) {
digitalWrite(portduino_config.lora_rxen_pin.pin, HIGH);
if (settingsMap[rxen_pin] != RADIOLIB_NC) {
digitalWrite(settingsMap[rxen_pin], HIGH);
}
if (portduino_config.lora_txen_pin.pin != RADIOLIB_NC) {
digitalWrite(portduino_config.lora_txen_pin.pin, LOW);
if (settingsMap[txen_pin] != RADIOLIB_NC) {
digitalWrite(settingsMap[txen_pin], LOW);
}
#else

View File

@@ -65,8 +65,8 @@ mail: marchammermann@googlemail.com
#define DEFAULT_REALM "default_realm"
#define PREFIX ""
#define KEY_PATH portduino_config.webserver_ssl_key_path.c_str()
#define CERT_PATH portduino_config.webserver_ssl_cert_path.c_str()
#define KEY_PATH settingsStrings[websslkeypath].c_str()
#define CERT_PATH settingsStrings[websslcertpath].c_str()
struct _file_config configWeb;
@@ -458,8 +458,8 @@ PiWebServerThread::PiWebServerThread()
}
}
if (portduino_config.webserverport != 0) {
webservport = portduino_config.webserverport;
if (settingsMap[webserverport] != 0) {
webservport = settingsMap[webserverport];
LOG_INFO("Use webserver port from yaml config %i ", webservport);
} else {
LOG_INFO("Webserver port in yaml config set to 0, defaulting to port 9443");
@@ -490,7 +490,7 @@ PiWebServerThread::PiWebServerThread()
u_map_put(&configWeb.mime_types, ".ico", "image/x-icon");
u_map_put(&configWeb.mime_types, ".svg", "image/svg+xml");
webrootpath = portduino_config.webserver_root_path;
webrootpath = settingsStrings[webserverrootpath];
configWeb.files_path = (char *)webrootpath.c_str();
configWeb.url_prefix = "";

View File

@@ -1040,32 +1040,19 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const
res.get_module_config_response.payload_variant.serial = moduleConfig.serial;
break;
case meshtastic_AdminMessage_ModuleConfigType_EXTNOTIF_CONFIG:
#if !MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION
LOG_INFO("Get module config: External Notification");
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_external_notification_tag;
res.get_module_config_response.payload_variant.external_notification = moduleConfig.external_notification;
#else
LOG_DEBUG("External Notification module excluded from build, skipping config");
#endif
break;
case meshtastic_AdminMessage_ModuleConfigType_STOREFORWARD_CONFIG:
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
LOG_INFO("Get module config: Store & Forward");
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_store_forward_tag;
res.get_module_config_response.payload_variant.store_forward = moduleConfig.store_forward;
#else
LOG_DEBUG("Store & Forward module excluded from build, skipping config");
#endif
break;
case meshtastic_AdminMessage_ModuleConfigType_RANGETEST_CONFIG:
#if !MESHTASTIC_EXCLUDE_RANGETEST
LOG_INFO("Get module config: Range Test");
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_range_test_tag;
res.get_module_config_response.payload_variant.range_test = moduleConfig.range_test;
#else
LOG_DEBUG("Range Test module excluded from build, skipping config");
// Don't set payload variant - will result in empty response
#endif
break;
case meshtastic_AdminMessage_ModuleConfigType_TELEMETRY_CONFIG:
LOG_INFO("Get module config: Telemetry");
@@ -1078,13 +1065,9 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const
res.get_module_config_response.payload_variant.canned_message = moduleConfig.canned_message;
break;
case meshtastic_AdminMessage_ModuleConfigType_AUDIO_CONFIG:
#if !MESHTASTIC_EXCLUDE_AUDIO
LOG_INFO("Get module config: Audio");
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_audio_tag;
res.get_module_config_response.payload_variant.audio = moduleConfig.audio;
#else
LOG_DEBUG("Audio module excluded from build, skipping config");
#endif
break;
case meshtastic_AdminMessage_ModuleConfigType_REMOTEHARDWARE_CONFIG:
LOG_INFO("Get module config: Remote Hardware");
@@ -1097,31 +1080,19 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const
res.get_module_config_response.payload_variant.neighbor_info = moduleConfig.neighbor_info;
break;
case meshtastic_AdminMessage_ModuleConfigType_DETECTIONSENSOR_CONFIG:
#if !(NO_EXT_GPIO || MESHTASTIC_EXCLUDE_DETECTIONSENSOR)
LOG_INFO("Get module config: Detection Sensor");
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_detection_sensor_tag;
res.get_module_config_response.payload_variant.detection_sensor = moduleConfig.detection_sensor;
#else
LOG_DEBUG("Detection Sensor module excluded from build, skipping config");
#endif
break;
case meshtastic_AdminMessage_ModuleConfigType_AMBIENTLIGHTING_CONFIG:
#if !MESHTASTIC_EXCLUDE_AMBIENTLIGHTING
LOG_INFO("Get module config: Ambient Lighting");
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_ambient_lighting_tag;
res.get_module_config_response.payload_variant.ambient_lighting = moduleConfig.ambient_lighting;
#else
LOG_DEBUG("Ambient Lighting module excluded from build, skipping config");
#endif
break;
case meshtastic_AdminMessage_ModuleConfigType_PAXCOUNTER_CONFIG:
#if !MESHTASTIC_EXCLUDE_PAXCOUNTER
LOG_INFO("Get module config: Paxcounter");
res.get_module_config_response.which_payload_variant = meshtastic_ModuleConfig_paxcounter_tag;
res.get_module_config_response.payload_variant.paxcounter = moduleConfig.paxcounter;
#else
LOG_DEBUG("Paxcounter module excluded from build, skipping config");
#endif
break;
}

View File

@@ -13,16 +13,12 @@
#include "detect/ScanI2C.h"
#include "graphics/Screen.h"
#include "graphics/SharedUIDisplay.h"
#include "graphics/draw/NotificationRenderer.h"
#include "graphics/emotes.h"
#include "graphics/images.h"
#include "main.h" // for cardkb_found
#include "mesh/generated/meshtastic/cannedmessages.pb.h"
#include "modules/AdminModule.h"
#include "modules/ExternalNotificationModule.h" // for buzzer control
#if HAS_TRACKBALL
#include "input/TrackballInterruptImpl1.h"
#endif
#if !MESHTASTIC_EXCLUDE_GPS
#include "GPS.h"
#endif
@@ -42,7 +38,6 @@
extern ScanI2C::DeviceAddress cardkb_found;
extern bool graphics::isMuted;
extern bool osk_found;
static const char *cannedMessagesConfigFile = "/prefs/cannedConf.proto";
static NodeNum lastDest = NODENUM_BROADCAST;
@@ -156,13 +151,10 @@ int CannedMessageModule::splitConfiguredMessages()
int tempCount = 0;
// Insert at position 0 (top)
tempMessages[tempCount++] = "[Select Destination]";
#if defined(USE_VIRTUAL_KEYBOARD)
// Add a "Free Text" entry at the top if using a touch screen virtual keyboard
// Add a "Free Text" entry at the top if using a keyboard
tempMessages[tempCount++] = "[-- Free Text --]";
#else
if (osk_found && screen) {
tempMessages[tempCount++] = "[-- Free Text --]";
}
#endif
// First message always starts at buffer start
@@ -349,8 +341,6 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event)
case CANNED_MESSAGE_RUN_STATE_FREETEXT:
return handleFreeTextInput(event); // All allowed input for this state
// Virtual keyboard mode: Show virtual keyboard and handle input
// If sending, block all input except global/system (handled above)
case CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE:
return 1;
@@ -637,56 +627,6 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo
notifyObservers(&e);
return true;
}
#else
if (strcmp(current, "[-- Free Text --]") == 0) {
if (osk_found && screen) {
char headerBuffer[64];
if (this->dest == NODENUM_BROADCAST) {
snprintf(headerBuffer, sizeof(headerBuffer), "To: Broadcast@%s", channels.getName(this->channel));
} else {
snprintf(headerBuffer, sizeof(headerBuffer), "To: %s", getNodeName(this->dest));
}
screen->showTextInput(headerBuffer, "", 300000, [this](const std::string &text) {
if (!text.empty()) {
this->freetext = text.c_str();
this->payload = CANNED_MESSAGE_RUN_STATE_FREETEXT;
runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE;
currentMessageIndex = -1;
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
this->notifyObservers(&e);
screen->forceDisplay();
setIntervalFromNow(500);
return;
} else {
// Don't delete virtual keyboard immediately - it might still be executing
// Instead, just clear the callback and reset banner to stop input processing
graphics::NotificationRenderer::textInputCallback = nullptr;
graphics::NotificationRenderer::resetBanner();
// Return to inactive state
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
this->currentMessageIndex = -1;
this->freetext = "";
this->cursor = 0;
// Force display update to show normal screen
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
this->notifyObservers(&e);
screen->forceDisplay();
// Schedule cleanup for next loop iteration to ensure safe deletion
setIntervalFromNow(50);
return;
}
});
return true;
}
}
#endif
// Normal canned message selection
@@ -1003,54 +943,12 @@ int32_t CannedMessageModule::runOnce()
// Normal module disable/idle handling
if ((this->runState == CANNED_MESSAGE_RUN_STATE_DISABLED) || (this->runState == CANNED_MESSAGE_RUN_STATE_INACTIVE)) {
// Clean up virtual keyboard if needed when going inactive
if (graphics::NotificationRenderer::virtualKeyboard && graphics::NotificationRenderer::textInputCallback == nullptr) {
LOG_INFO("Performing delayed virtual keyboard cleanup");
delete graphics::NotificationRenderer::virtualKeyboard;
graphics::NotificationRenderer::virtualKeyboard = nullptr;
}
temporaryMessage = "";
return INT32_MAX;
}
// Handle delayed virtual keyboard message sending
if (this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE && this->payload == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
// Virtual keyboard message sending case - text was not empty
if (this->freetext.length() > 0) {
LOG_INFO("Processing delayed virtual keyboard send: '%s'", this->freetext.c_str());
sendText(this->dest, this->channel, this->freetext.c_str(), true);
// Clean up virtual keyboard after sending
if (graphics::NotificationRenderer::virtualKeyboard) {
LOG_INFO("Cleaning up virtual keyboard after message send");
delete graphics::NotificationRenderer::virtualKeyboard;
graphics::NotificationRenderer::virtualKeyboard = nullptr;
graphics::NotificationRenderer::textInputCallback = nullptr;
graphics::NotificationRenderer::resetBanner();
}
// Clear payload to indicate virtual keyboard processing is complete
// But keep SENDING_ACTIVE state to show "Sending..." screen for 2 seconds
this->payload = 0;
} else {
// Empty message, just go inactive
LOG_INFO("Empty freetext detected in delayed processing, returning to inactive state");
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
}
UIFrameEvent e;
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
this->currentMessageIndex = -1;
this->freetext = "";
this->cursor = 0;
this->notifyObservers(&e);
return 2000;
}
UIFrameEvent e;
if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE && this->payload != 0 &&
this->payload != CANNED_MESSAGE_RUN_STATE_FREETEXT) ||
if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) ||
(this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) ||
(this->runState == CANNED_MESSAGE_RUN_STATE_MESSAGE_SELECTION)) {
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
@@ -1060,18 +958,6 @@ int32_t CannedMessageModule::runOnce()
this->freetext = "";
this->cursor = 0;
this->notifyObservers(&e);
}
// Handle SENDING_ACTIVE state transition after virtual keyboard message
else if (this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE && this->payload == 0) {
// This happens after virtual keyboard message sending is complete
LOG_INFO("Virtual keyboard message sending completed, returning to inactive state");
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
temporaryMessage = "";
e.action = UIFrameEvent::Action::REGENERATE_FRAMESET;
this->currentMessageIndex = -1;
this->freetext = "";
this->cursor = 0;
this->notifyObservers(&e);
} else if (((this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT)) &&
!Throttle::isWithinTimespanMs(this->lastTouchMillis, INACTIVATE_AFTER_MS)) {
// Reset module on inactivity
@@ -1080,23 +966,9 @@ int32_t CannedMessageModule::runOnce()
this->freetext = "";
this->cursor = 0;
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
// Clean up virtual keyboard if it exists during timeout
if (graphics::NotificationRenderer::virtualKeyboard) {
LOG_INFO("Cleaning up virtual keyboard due to module timeout");
delete graphics::NotificationRenderer::virtualKeyboard;
graphics::NotificationRenderer::virtualKeyboard = nullptr;
graphics::NotificationRenderer::textInputCallback = nullptr;
graphics::NotificationRenderer::resetBanner();
}
this->notifyObservers(&e);
} else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) {
if (this->payload == 0) {
// [Exit] button pressed - return to inactive state
LOG_INFO("Processing [Exit] action - returning to inactive state");
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
} else if (this->payload == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
if (this->payload == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
if (this->freetext.length() > 0) {
sendText(this->dest, this->channel, this->freetext.c_str(), true);
this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE;

View File

@@ -141,7 +141,10 @@ void setupModules()
detectionSensorModule = new DetectionSensorModule();
#endif
#if !MESHTASTIC_EXCLUDE_ATAK
atakPluginModule = new AtakPluginModule();
if (IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TAK,
meshtastic_Config_DeviceConfig_Role_TAK_TRACKER)) {
atakPluginModule = new AtakPluginModule();
}
#endif
#if !MESHTASTIC_EXCLUDE_PKI
keyVerificationModule = new KeyVerificationModule();

View File

@@ -30,8 +30,7 @@
namespace graphics
{
extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert,
bool show_date);
extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool battery_only);
}
#if __has_include(<Adafruit_AHTX0.h>)
#include "Sensor/AHT10.h"

View File

@@ -9,11 +9,11 @@
int32_t HostMetricsModule::runOnce()
{
#if ARCH_PORTDUINO
if (portduino_config.hostMetrics_interval == 0) {
if (settingsMap[hostMetrics_interval] == 0) {
return disable();
} else {
sendMetrics();
return 60 * 1000 * portduino_config.hostMetrics_interval;
return 60 * 1000 * settingsMap[hostMetrics_interval];
}
#else
return disable();
@@ -110,8 +110,8 @@ meshtastic_Telemetry HostMetricsModule::getHostMetrics()
proc_loadavg.close();
}
}
if (portduino_config.hostMetrics_user_command != "") {
std::string userCommandResult = exec(portduino_config.hostMetrics_user_command.c_str());
if (settingsStrings[hostMetrics_user_command] != "") {
std::string userCommandResult = exec(settingsStrings[hostMetrics_user_command].c_str());
if (userCommandResult.length() > 1) {
strncpy(t.variant.host_metrics.user_string, userCommandResult.c_str(), sizeof(t.variant.host_metrics.user_string));
t.variant.host_metrics.user_string[sizeof(t.variant.host_metrics.user_string) - 1] = '\0';
@@ -135,7 +135,7 @@ bool HostMetricsModule::sendMetrics()
p->to = NODENUM_BROADCAST;
p->decoded.want_response = false;
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
p->channel = portduino_config.hostMetrics_channel;
p->channel = settingsMap[hostMetrics_channel];
LOG_INFO("Send packet to mesh");
service->sendToMesh(p, RX_SRC_LOCAL, true);
return true;

View File

@@ -24,8 +24,7 @@
namespace graphics
{
extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool force_no_invert,
bool show_date);
extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr, bool battery_only);
}
int32_t PowerTelemetryModule::runOnce()

View File

@@ -45,9 +45,6 @@
#ifndef HAS_CUSTOM_CRYPTO_ENGINE
#define HAS_CUSTOM_CRYPTO_ENGINE 1
#endif
#ifndef HAS_32768HZ
#define HAS_32768HZ 0
#endif
#if defined(HAS_AXP192) || defined(HAS_AXP2101)
#define HAS_PMU
@@ -220,13 +217,3 @@
#endif
#define SERIAL0_RX_GPIO 3 // Always GPIO3 on ESP32 // FIXME: may be different on ESP32-S3, etc.
// Setup flag, which indicates if our device supports power management
#ifdef CONFIG_PM_ENABLE
#define HAS_ESP32_PM_SUPPORT 1
#endif
// Setup flag, which indicates if our device supports dynamic light sleep
#if defined(HAS_ESP32_PM_SUPPORT) && defined(CONFIG_FREERTOS_USE_TICKLESS_IDLE)
#define HAS_ESP32_DYNAMIC_LIGHT_SLEEP 1
#endif

View File

@@ -64,7 +64,7 @@ void getMacAddr(uint8_t *dmac)
#endif
}
#if HAS_32768HZ
#ifdef HAS_32768HZ
#define CALIBRATE_ONE(cali_clk) calibrate_one(cali_clk, #cali_clk)
static uint32_t calibrate_one(rtc_cal_sel_t cal_clk, const char *name)
@@ -86,17 +86,17 @@ void enableSlowCLK()
uint32_t cal_32k = CALIBRATE_ONE(RTC_CAL_32K_XTAL);
if (cal_32k == 0) {
LOG_DEBUG("32k XTAL OSC has not started up");
LOG_DEBUG("32K XTAL OSC has not started up");
} else {
rtc_clk_slow_freq_set(RTC_SLOW_FREQ_32K_XTAL);
LOG_DEBUG("Switch RTC Source to 32.768kHz succeeded, using 32k XTAL");
LOG_DEBUG("Switch RTC Source to 32.768Khz succeeded, using 32K XTAL");
CALIBRATE_ONE(RTC_CAL_RTC_MUX);
CALIBRATE_ONE(RTC_CAL_32K_XTAL);
}
CALIBRATE_ONE(RTC_CAL_RTC_MUX);
CALIBRATE_ONE(RTC_CAL_32K_XTAL);
if (rtc_clk_slow_freq_get() != RTC_SLOW_FREQ_32K_XTAL) {
LOG_WARN("Failed to switch 32K XTAL RTC source to 32.768kHz !!! ");
LOG_WARN("Failed to switch 32K XTAL RTC source to 32.768Khz !!! ");
return;
}
}
@@ -182,7 +182,7 @@ void esp32Setup()
res = esp_task_wdt_add(NULL);
assert(res == ESP_OK);
#if HAS_32768HZ
#ifdef HAS_32768HZ
enableSlowCLK();
#endif
}

View File

@@ -9,6 +9,7 @@
#include "api/ServerAPI.h"
#include "linux/gpio/LinuxGPIOPin.h"
#include "meshUtils.h"
#include "yaml-cpp/yaml.h"
#include <ErriezCRC32.h>
#include <Utility.h>
#include <assert.h>
@@ -27,13 +28,14 @@
#include "platform/portduino/USBHal.h"
std::map<configNames, int> settingsMap;
std::map<configNames, std::string> settingsStrings;
portduino_config_struct portduino_config;
std::ofstream traceFile;
Ch341Hal *ch341Hal = nullptr;
char *configPath = nullptr;
char *optionMac = nullptr;
bool verboseEnabled = false;
bool yamlOnly = false;
const char *argp_program_version = optstr(APP_VERSION);
@@ -73,9 +75,6 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state)
case 'v':
verboseEnabled = true;
break;
case 'y':
yamlOnly = true;
break;
case ARGP_KEY_ARG:
return 0;
default:
@@ -91,7 +90,6 @@ void portduinoCustomInit()
{"hwid", 'h', "HWID", 0, "The mac address to assign to this virtual machine"},
{"sim", 's', 0, 0, "Run in Simulated radio mode"},
{"verbose", 'v', 0, 0, "Set log level to full debug"},
{"output-yaml", 'y', 0, 0, "Output config yaml and exit"},
{0}};
static void *childArguments;
static char doc[] = "Meshtastic native build.";
@@ -117,8 +115,8 @@ void getMacAddr(uint8_t *dmac)
dmac[4] = hwId >> 8;
dmac[5] = hwId & 0xff;
}
} else if (portduino_config.mac_address.length() > 11) {
MAC_from_string(portduino_config.mac_address, dmac);
} else if (settingsStrings[mac_address].length() > 11) {
MAC_from_string(settingsStrings[mac_address], dmac);
exit;
} else {
@@ -150,46 +148,89 @@ void getMacAddr(uint8_t *dmac)
*/
void portduinoSetup()
{
printf("Set up Meshtastic on Portduino...\n");
int max_GPIO = 0;
const configNames GPIO_lines[] = {cs_pin,
irq_pin,
busy_pin,
reset_pin,
sx126x_ant_sw_pin,
txen_pin,
rxen_pin,
displayDC,
displayCS,
displayBacklight,
displayBacklightPWMChannel,
displayReset,
touchscreenCS,
touchscreenIRQ,
userButtonPin,
tbUpPin,
tbDownPin,
tbLeftPin,
tbRightPin,
tbPressPin};
std::string gpioChipName = "gpiochip";
portduino_config.displayPanel = no_screen;
settingsStrings[i2cdev] = "";
settingsStrings[keyboardDevice] = "";
settingsStrings[pointerDevice] = "";
settingsStrings[webserverrootpath] = "";
settingsStrings[spidev] = "";
settingsStrings[displayspidev] = "";
settingsMap[spiSpeed] = 2000000;
settingsMap[ascii_logs] = !isatty(1);
settingsMap[displayPanel] = no_screen;
settingsMap[touchscreenModule] = no_touchscreen;
settingsMap[tbUpPin] = RADIOLIB_NC;
settingsMap[tbDownPin] = RADIOLIB_NC;
settingsMap[tbLeftPin] = RADIOLIB_NC;
settingsMap[tbRightPin] = RADIOLIB_NC;
settingsMap[tbPressPin] = RADIOLIB_NC;
YAML::Node yamlConfig;
if (portduino_config.force_simradio == true) {
portduino_config.lora_module = use_simradio;
settingsMap[use_simradio] = true;
} else if (configPath != nullptr) {
if (loadConfig(configPath)) {
if (!yamlOnly)
std::cout << "Using " << configPath << " as config file" << std::endl;
std::cout << "Using " << configPath << " as config file" << std::endl;
} else {
std::cout << "Unable to use " << configPath << " as config file" << std::endl;
exit(EXIT_FAILURE);
}
} else if (access("config.yaml", R_OK) == 0) {
if (loadConfig("config.yaml")) {
if (!yamlOnly)
std::cout << "Using local config.yaml as config file" << std::endl;
std::cout << "Using local config.yaml as config file" << std::endl;
} else {
std::cout << "Unable to use local config.yaml as config file" << std::endl;
exit(EXIT_FAILURE);
}
} else if (access("/etc/meshtasticd/config.yaml", R_OK) == 0) {
if (loadConfig("/etc/meshtasticd/config.yaml")) {
if (!yamlOnly)
std::cout << "Using /etc/meshtasticd/config.yaml as config file" << std::endl;
std::cout << "Using /etc/meshtasticd/config.yaml as config file" << std::endl;
} else {
std::cout << "Unable to use /etc/meshtasticd/config.yaml as config file" << std::endl;
exit(EXIT_FAILURE);
}
} else {
if (!yamlOnly)
std::cout << "No 'config.yaml' found..." << std::endl;
portduino_config.lora_module = use_simradio;
std::cout << "No 'config.yaml' found..." << std::endl;
settingsMap[use_simradio] = true;
}
if (portduino_config.config_directory != "") {
if (settingsMap[use_simradio] == true) {
std::cout << "Running in simulated mode." << std::endl;
settingsMap[maxnodes] = 200; // Default to 200 nodes
settingsMap[logoutputlevel] = level_debug; // Default to debug
// Set the random seed equal to TCPPort to have a different seed per instance
randomSeed(TCPPort);
return;
}
if (settingsStrings[config_directory] != "") {
std::string filetype = ".yaml";
for (const std::filesystem::directory_entry &entry :
std::filesystem::directory_iterator{portduino_config.config_directory}) {
std::filesystem::directory_iterator{settingsStrings[config_directory]}) {
if (ends_with(entry.path().string(), ".yaml")) {
std::cout << "Also using " << entry << " as additional config file" << std::endl;
loadConfig(entry.path().c_str());
@@ -197,28 +238,15 @@ void portduinoSetup()
}
}
if (yamlOnly) {
std::cout << portduino_config.emit_yaml() << std::endl;
exit(EXIT_SUCCESS);
}
if (portduino_config.lora_module == use_simradio) {
std::cout << "Running in simulated mode." << std::endl;
portduino_config.MaxNodes = 200; // Default to 200 nodes
// Set the random seed equal to TCPPort to have a different seed per instance
randomSeed(TCPPort);
return;
}
// If LoRa `Module: auto` (default in config.yaml),
// attempt to auto config based on Product Strings
if (portduino_config.lora_module == use_autoconf) {
if (settingsMap[use_autoconf] == true) {
char autoconf_product[96] = {0};
// Try CH341
try {
std::cout << "autoconf: Looking for CH341 device..." << std::endl;
ch341Hal = new Ch341Hal(0, portduino_config.lora_usb_serial_num, portduino_config.lora_usb_vid,
portduino_config.lora_usb_pid);
ch341Hal =
new Ch341Hal(0, settingsStrings[lora_usb_serial_num], settingsMap[lora_usb_vid], settingsMap[lora_usb_pid]);
ch341Hal->getProductString(autoconf_product, 95);
delete ch341Hal;
std::cout << "autoconf: Found CH341 device " << autoconf_product << std::endl;
@@ -295,7 +323,7 @@ void portduinoSetup()
if (mac_start != nullptr) {
std::cout << "autoconf: Found mac data " << mac_start << std::endl;
if (strlen(mac_start) == 12)
portduino_config.mac_address = std::string(mac_start);
settingsStrings[mac_address] = std::string(mac_start);
}
if (devID_start != nullptr) {
std::cout << "autoconf: Found deviceid data " << devID_start << std::endl;
@@ -326,7 +354,7 @@ void portduinoSetup()
std::cerr << "autoconf: Unable to find config for " << autoconf_product << std::endl;
exit(EXIT_FAILURE);
}
if (loadConfig((portduino_config.available_directory + product_config).c_str())) {
if (loadConfig((settingsStrings[available_directory] + product_config).c_str())) {
std::cout << "autoconf: Using " << product_config << " as config file for " << autoconf_product << std::endl;
} else {
std::cerr << "autoconf: Unable to use " << product_config << " as config file for " << autoconf_product
@@ -335,16 +363,15 @@ void portduinoSetup()
}
} else {
std::cerr << "autoconf: Could not locate any devices" << std::endl;
exit(EXIT_FAILURE);
}
}
// if we're using a usermode driver, we need to initialize it here, to get a serial number back for mac address
uint8_t dmac[6] = {0};
if (portduino_config.lora_spi_dev == "ch341") {
if (settingsStrings[spidev] == "ch341") {
try {
ch341Hal = new Ch341Hal(0, portduino_config.lora_usb_serial_num, portduino_config.lora_usb_vid,
portduino_config.lora_usb_pid);
ch341Hal =
new Ch341Hal(0, settingsStrings[lora_usb_serial_num], settingsMap[lora_usb_vid], settingsMap[lora_usb_pid]);
} catch (std::exception &e) {
std::cerr << e.what() << std::endl;
std::cerr << "Could not initialize CH341 device!" << std::endl;
@@ -356,7 +383,7 @@ void portduinoSetup()
char product_string[96] = {0};
ch341Hal->getProductString(product_string, 95);
std::cout << "CH341 Product " << product_string << std::endl;
if (strlen(serial) == 8 && portduino_config.mac_address.length() < 12) {
if (strlen(serial) == 8 && settingsStrings[mac_address].length() < 12) {
uint8_t hash[32] = {0};
memcpy(hash, serial, 8);
crypto->hash(hash, 8);
@@ -368,7 +395,7 @@ void portduinoSetup()
dmac[5] = hash[5];
char macBuf[13] = {0};
sprintf(macBuf, "%02X%02X%02X%02X%02X%02X", dmac[0], dmac[1], dmac[2], dmac[3], dmac[4], dmac[5]);
portduino_config.mac_address = macBuf;
settingsStrings[mac_address] = macBuf;
}
}
@@ -382,38 +409,100 @@ void portduinoSetup()
// Rather important to set this, if not running simulated.
randomSeed(time(NULL));
std::string defaultGpioChipName = gpioChipName + std::to_string(portduino_config.lora_default_gpiochip);
for (auto i : portduino_config.all_pins) {
if (i->enabled && i->pin > max_GPIO)
max_GPIO = i->pin;
std::string defaultGpioChipName = gpioChipName + std::to_string(settingsMap[default_gpiochip]);
for (configNames i : GPIO_lines) {
if (settingsMap.count(i) && settingsMap[i] > max_GPIO)
max_GPIO = settingsMap[i];
}
gpioInit(max_GPIO + 1); // Done here so we can inform Portduino how many GPIOs we need.
// Need to bind all the configured GPIO pins so they're not simulated
// TODO: If one of these fails, we should log and terminate
for (auto i : portduino_config.all_pins) {
if (i->enabled)
if (initGPIOPin(i->pin, gpioChipName + std::to_string(i->gpiochip), i->line) != ERRNO_OK) {
printf("Error setting pin number %d. It may not exist, or may already be in use.\n", i->line);
exit(EXIT_FAILURE);
}
if (settingsMap.count(userButtonPin) > 0 && settingsMap[userButtonPin] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[userButtonPin], defaultGpioChipName, settingsMap[userButtonPin]) != ERRNO_OK) {
settingsMap[userButtonPin] = RADIOLIB_NC;
}
}
if (settingsMap.count(tbUpPin) > 0 && settingsMap[tbUpPin] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[tbUpPin], defaultGpioChipName, settingsMap[tbUpPin]) != ERRNO_OK) {
settingsMap[tbUpPin] = RADIOLIB_NC;
}
}
if (settingsMap.count(tbDownPin) > 0 && settingsMap[tbDownPin] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[tbDownPin], defaultGpioChipName, settingsMap[tbDownPin]) != ERRNO_OK) {
settingsMap[tbDownPin] = RADIOLIB_NC;
}
}
if (settingsMap.count(tbLeftPin) > 0 && settingsMap[tbLeftPin] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[tbLeftPin], defaultGpioChipName, settingsMap[tbLeftPin]) != ERRNO_OK) {
settingsMap[tbLeftPin] = RADIOLIB_NC;
}
}
if (settingsMap.count(tbRightPin) > 0 && settingsMap[tbRightPin] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[tbRightPin], defaultGpioChipName, settingsMap[tbRightPin]) != ERRNO_OK) {
settingsMap[tbRightPin] = RADIOLIB_NC;
}
}
if (settingsMap.count(tbPressPin) > 0 && settingsMap[tbPressPin] != RADIOLIB_NC) {
if (initGPIOPin(settingsMap[tbPressPin], defaultGpioChipName, settingsMap[tbPressPin]) != ERRNO_OK) {
settingsMap[tbPressPin] = RADIOLIB_NC;
}
}
if (settingsMap[displayPanel] != no_screen) {
if (settingsMap[displayCS] > 0)
initGPIOPin(settingsMap[displayCS], defaultGpioChipName, settingsMap[displayCS]);
if (settingsMap[displayDC] > 0)
initGPIOPin(settingsMap[displayDC], defaultGpioChipName, settingsMap[displayDC]);
if (settingsMap[displayBacklight] > 0)
initGPIOPin(settingsMap[displayBacklight], defaultGpioChipName, settingsMap[displayBacklight]);
if (settingsMap[displayReset] > 0)
initGPIOPin(settingsMap[displayReset], defaultGpioChipName, settingsMap[displayReset]);
}
if (settingsMap[touchscreenModule] != no_touchscreen) {
if (settingsMap[touchscreenCS] > 0)
initGPIOPin(settingsMap[touchscreenCS], defaultGpioChipName, settingsMap[touchscreenCS]);
if (settingsMap[touchscreenIRQ] > 0)
initGPIOPin(settingsMap[touchscreenIRQ], defaultGpioChipName, settingsMap[touchscreenIRQ]);
}
// Only initialize the radio pins when dealing with real, kernel controlled SPI hardware
if (portduino_config.lora_spi_dev != "" && portduino_config.lora_spi_dev != "ch341") {
SPI.begin(portduino_config.lora_spi_dev.c_str());
if (settingsStrings[spidev] != "" && settingsStrings[spidev] != "ch341") {
const struct {
configNames pin;
configNames gpiochip;
configNames line;
} pinMappings[] = {{cs_pin, cs_gpiochip, cs_line},
{irq_pin, irq_gpiochip, irq_line},
{busy_pin, busy_gpiochip, busy_line},
{reset_pin, reset_gpiochip, reset_line},
{rxen_pin, rxen_gpiochip, rxen_line},
{txen_pin, txen_gpiochip, txen_line},
{sx126x_ant_sw_pin, sx126x_ant_sw_gpiochip, sx126x_ant_sw_line}};
for (auto &pinMap : pinMappings) {
auto setMapIter = settingsMap.find(pinMap.pin);
if (setMapIter != settingsMap.end() && setMapIter->second != RADIOLIB_NC) {
if (initGPIOPin(setMapIter->second, gpioChipName + std::to_string(settingsMap[pinMap.gpiochip]),
settingsMap[pinMap.line]) != ERRNO_OK) {
printf("Error setting pin number %d. It may not exist, or may already be in use.\n",
settingsMap[pinMap.line]);
exit(EXIT_FAILURE);
}
}
}
SPI.begin(settingsStrings[spidev].c_str());
}
if (portduino_config.traceFilename != "") {
if (settingsStrings[traceFilename] != "") {
try {
traceFile.open(portduino_config.traceFilename, std::ios::out | std::ios::app);
traceFile.open(settingsStrings[traceFilename], std::ios::out | std::ios::app);
} catch (std::ofstream::failure &e) {
std::cout << "*** traceFile Exception " << e.what() << std::endl;
exit(EXIT_FAILURE);
}
}
if (verboseEnabled && portduino_config.logoutputlevel != level_trace) {
portduino_config.logoutputlevel = level_debug;
if (verboseEnabled && settingsMap[logoutputlevel] != level_trace) {
settingsMap[logoutputlevel] = level_debug;
}
return;
@@ -448,78 +537,99 @@ bool loadConfig(const char *configPath)
yamlConfig = YAML::LoadFile(configPath);
if (yamlConfig["Logging"]) {
if (yamlConfig["Logging"]["LogLevel"].as<std::string>("info") == "trace") {
portduino_config.logoutputlevel = level_trace;
settingsMap[logoutputlevel] = level_trace;
} else if (yamlConfig["Logging"]["LogLevel"].as<std::string>("info") == "debug") {
portduino_config.logoutputlevel = level_debug;
settingsMap[logoutputlevel] = level_debug;
} else if (yamlConfig["Logging"]["LogLevel"].as<std::string>("info") == "info") {
portduino_config.logoutputlevel = level_info;
settingsMap[logoutputlevel] = level_info;
} else if (yamlConfig["Logging"]["LogLevel"].as<std::string>("info") == "warn") {
portduino_config.logoutputlevel = level_warn;
settingsMap[logoutputlevel] = level_warn;
} else if (yamlConfig["Logging"]["LogLevel"].as<std::string>("info") == "error") {
portduino_config.logoutputlevel = level_error;
settingsMap[logoutputlevel] = level_error;
}
portduino_config.traceFilename = yamlConfig["Logging"]["TraceFile"].as<std::string>("");
settingsStrings[traceFilename] = yamlConfig["Logging"]["TraceFile"].as<std::string>("");
if (yamlConfig["Logging"]["AsciiLogs"]) {
// Default is !isatty(1) but can be set explicitly in config.yaml
portduino_config.ascii_logs = yamlConfig["Logging"]["AsciiLogs"].as<bool>();
portduino_config.ascii_logs_explicit = true;
settingsMap[ascii_logs] = yamlConfig["Logging"]["AsciiLogs"].as<bool>();
}
}
if (yamlConfig["Lora"]) {
const struct {
configNames cfgName;
std::string strName;
} loraModules[] = {{use_simradio, "sim"}, {use_autoconf, "auto"}, {use_rf95, "RF95"}, {use_sx1262, "sx1262"},
{use_sx1268, "sx1268"}, {use_sx1280, "sx1280"}, {use_lr1110, "lr1110"}, {use_lr1120, "lr1120"},
{use_lr1121, "lr1121"}, {use_llcc68, "LLCC68"}};
for (auto &loraModule : loraModules) {
settingsMap[loraModule.cfgName] = false;
}
if (yamlConfig["Lora"]["Module"]) {
for (auto &loraModule : portduino_config.loraModules) {
if (yamlConfig["Lora"]["Module"].as<std::string>("") == loraModule.second) {
portduino_config.lora_module = loraModule.first;
for (auto &loraModule : loraModules) {
if (yamlConfig["Lora"]["Module"].as<std::string>("") == loraModule.strName) {
settingsMap[loraModule.cfgName] = true;
break;
}
}
}
if (yamlConfig["Lora"]["SX126X_MAX_POWER"])
portduino_config.sx126x_max_power = yamlConfig["Lora"]["SX126X_MAX_POWER"].as<int>(22);
if (yamlConfig["Lora"]["SX128X_MAX_POWER"])
portduino_config.sx128x_max_power = yamlConfig["Lora"]["SX128X_MAX_POWER"].as<int>(13);
if (yamlConfig["Lora"]["LR1110_MAX_POWER"])
portduino_config.lr1110_max_power = yamlConfig["Lora"]["LR1110_MAX_POWER"].as<int>(22);
if (yamlConfig["Lora"]["LR1120_MAX_POWER"])
portduino_config.lr1120_max_power = yamlConfig["Lora"]["LR1120_MAX_POWER"].as<int>(13);
if (yamlConfig["Lora"]["RF95_MAX_POWER"])
portduino_config.rf95_max_power = yamlConfig["Lora"]["RF95_MAX_POWER"].as<int>(20);
if (portduino_config.lora_module != use_autoconf && portduino_config.lora_module != use_simradio &&
!portduino_config.force_simradio) {
portduino_config.dio2_as_rf_switch = yamlConfig["Lora"]["DIO2_AS_RF_SWITCH"].as<bool>(false);
portduino_config.dio3_tcxo_voltage = yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as<float>(0) * 1000;
if (portduino_config.dio3_tcxo_voltage == 0 && yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as<bool>(false)) {
portduino_config.dio3_tcxo_voltage = 1800; // default millivolts for "true"
}
settingsMap[sx126x_max_power] = yamlConfig["Lora"]["SX126X_MAX_POWER"].as<int>(22);
settingsMap[sx128x_max_power] = yamlConfig["Lora"]["SX128X_MAX_POWER"].as<int>(13);
settingsMap[lr1110_max_power] = yamlConfig["Lora"]["LR1110_MAX_POWER"].as<int>(22);
settingsMap[lr1120_max_power] = yamlConfig["Lora"]["LR1120_MAX_POWER"].as<int>(13);
settingsMap[rf95_max_power] = yamlConfig["Lora"]["RF95_MAX_POWER"].as<int>(20);
// backwards API compatibility and to globally set gpiochip once
portduino_config.lora_default_gpiochip = yamlConfig["Lora"]["gpiochip"].as<int>(0);
for (auto this_pin : portduino_config.all_pins) {
if (this_pin->config_section == "Lora") {
readGPIOFromYaml(yamlConfig["Lora"][this_pin->config_name], *this_pin);
}
settingsMap[dio2_as_rf_switch] = yamlConfig["Lora"]["DIO2_AS_RF_SWITCH"].as<bool>(false);
settingsMap[dio3_tcxo_voltage] = yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as<float>(0) * 1000;
if (settingsMap[dio3_tcxo_voltage] == 0 && yamlConfig["Lora"]["DIO3_TCXO_VOLTAGE"].as<bool>(false)) {
settingsMap[dio3_tcxo_voltage] = 1800; // default millivolts for "true"
}
// backwards API compatibility and to globally set gpiochip once
int defaultGpioChip = settingsMap[default_gpiochip] = yamlConfig["Lora"]["gpiochip"].as<int>(0);
const struct {
configNames pin;
configNames gpiochip;
configNames line;
std::string strName;
} pinMappings[] = {
{cs_pin, cs_gpiochip, cs_line, "CS"},
{irq_pin, irq_gpiochip, irq_line, "IRQ"},
{busy_pin, busy_gpiochip, busy_line, "Busy"},
{reset_pin, reset_gpiochip, reset_line, "Reset"},
{txen_pin, txen_gpiochip, txen_line, "TXen"},
{rxen_pin, rxen_gpiochip, rxen_line, "RXen"},
{sx126x_ant_sw_pin, sx126x_ant_sw_gpiochip, sx126x_ant_sw_line, "SX126X_ANT_SW"},
};
for (auto &pinMap : pinMappings) {
if (yamlConfig["Lora"][pinMap.strName].IsMap()) {
settingsMap[pinMap.pin] = yamlConfig["Lora"][pinMap.strName]["pin"].as<int>(RADIOLIB_NC);
settingsMap[pinMap.line] = yamlConfig["Lora"][pinMap.strName]["line"].as<int>(settingsMap[pinMap.pin]);
settingsMap[pinMap.gpiochip] = yamlConfig["Lora"][pinMap.strName]["gpiochip"].as<int>(defaultGpioChip);
} else { // backwards API compatibility
settingsMap[pinMap.pin] = yamlConfig["Lora"][pinMap.strName].as<int>(RADIOLIB_NC);
settingsMap[pinMap.line] = settingsMap[pinMap.pin];
settingsMap[pinMap.gpiochip] = defaultGpioChip;
}
}
portduino_config.spiSpeed = yamlConfig["Lora"]["spiSpeed"].as<int>(2000000);
portduino_config.lora_usb_serial_num = yamlConfig["Lora"]["USB_Serialnum"].as<std::string>("");
portduino_config.lora_usb_pid = yamlConfig["Lora"]["USB_PID"].as<int>(0x5512);
portduino_config.lora_usb_vid = yamlConfig["Lora"]["USB_VID"].as<int>(0x1A86);
settingsMap[spiSpeed] = yamlConfig["Lora"]["spiSpeed"].as<int>(2000000);
settingsStrings[lora_usb_serial_num] = yamlConfig["Lora"]["USB_Serialnum"].as<std::string>("");
settingsMap[lora_usb_pid] = yamlConfig["Lora"]["USB_PID"].as<int>(0x5512);
settingsMap[lora_usb_vid] = yamlConfig["Lora"]["USB_VID"].as<int>(0x1A86);
portduino_config.lora_spi_dev = yamlConfig["Lora"]["spidev"].as<std::string>("spidev0.0");
if (portduino_config.lora_spi_dev != "ch341") {
portduino_config.lora_spi_dev = "/dev/" + portduino_config.lora_spi_dev;
if (portduino_config.lora_spi_dev.length() == 14) {
int x = portduino_config.lora_spi_dev.at(11) - '0';
int y = portduino_config.lora_spi_dev.at(13) - '0';
settingsStrings[spidev] = yamlConfig["Lora"]["spidev"].as<std::string>("spidev0.0");
if (settingsStrings[spidev] != "ch341") {
settingsStrings[spidev] = "/dev/" + settingsStrings[spidev];
if (settingsStrings[spidev].length() == 14) {
int x = settingsStrings[spidev].at(11) - '0';
int y = settingsStrings[spidev].at(13) - '0';
// Pretty sure this is always true
if (x >= 0 && x < 10 && y >= 0 && y < 10) {
// I believe this bit of weirdness is specifically for the new GUI
portduino_config.lora_spi_dev_int = x + y << 4;
portduino_config.display_spi_dev_int = portduino_config.lora_spi_dev_int;
portduino_config.touchscreen_spi_dev_int = portduino_config.lora_spi_dev_int;
settingsMap[spidev] = x + y << 4;
settingsMap[displayspidev] = settingsMap[spidev];
settingsMap[touchscreenspidev] = settingsMap[spidev];
}
}
}
@@ -566,152 +676,163 @@ bool loadConfig(const char *configPath)
}
}
}
readGPIOFromYaml(yamlConfig["GPIO"]["User"], portduino_config.userButtonPin);
if (yamlConfig["GPIO"]) {
settingsMap[userButtonPin] = yamlConfig["GPIO"]["User"].as<int>(RADIOLIB_NC);
}
if (yamlConfig["GPS"]) {
std::string serialPath = yamlConfig["GPS"]["SerialPath"].as<std::string>("");
if (serialPath != "") {
Serial1.setPath(serialPath);
portduino_config.has_gps = 1;
settingsMap[has_gps] = 1;
}
}
if (yamlConfig["I2C"]) {
portduino_config.i2cdev = yamlConfig["I2C"]["I2CDevice"].as<std::string>("");
settingsStrings[i2cdev] = yamlConfig["I2C"]["I2CDevice"].as<std::string>("");
}
if (yamlConfig["Display"]) {
for (auto &screen_name : portduino_config.screen_names) {
if (yamlConfig["Display"]["Panel"].as<std::string>("") == screen_name.second)
portduino_config.displayPanel = screen_name.first;
}
portduino_config.displayHeight = yamlConfig["Display"]["Height"].as<int>(0);
portduino_config.displayWidth = yamlConfig["Display"]["Width"].as<int>(0);
readGPIOFromYaml(yamlConfig["Display"]["DC"], portduino_config.displayDC, -1);
readGPIOFromYaml(yamlConfig["Display"]["CS"], portduino_config.displayCS, -1);
readGPIOFromYaml(yamlConfig["Display"]["Backlight"], portduino_config.displayBacklight, -1);
readGPIOFromYaml(yamlConfig["Display"]["BacklightPWMChannel"], portduino_config.displayBacklightPWMChannel, -1);
readGPIOFromYaml(yamlConfig["Display"]["Reset"], portduino_config.displayReset, -1);
portduino_config.displayBacklightInvert = yamlConfig["Display"]["BacklightInvert"].as<bool>(false);
portduino_config.displayRGBOrder = yamlConfig["Display"]["RGBOrder"].as<bool>(false);
portduino_config.displayOffsetX = yamlConfig["Display"]["OffsetX"].as<int>(0);
portduino_config.displayOffsetY = yamlConfig["Display"]["OffsetY"].as<int>(0);
portduino_config.displayRotate = yamlConfig["Display"]["Rotate"].as<bool>(false);
portduino_config.displayOffsetRotate = yamlConfig["Display"]["OffsetRotate"].as<int>(1);
portduino_config.displayInvert = yamlConfig["Display"]["Invert"].as<bool>(false);
portduino_config.displayBusFrequency = yamlConfig["Display"]["BusFrequency"].as<int>(40000000);
if (yamlConfig["Display"]["Panel"].as<std::string>("") == "ST7789")
settingsMap[displayPanel] = st7789;
else if (yamlConfig["Display"]["Panel"].as<std::string>("") == "ST7735")
settingsMap[displayPanel] = st7735;
else if (yamlConfig["Display"]["Panel"].as<std::string>("") == "ST7735S")
settingsMap[displayPanel] = st7735s;
else if (yamlConfig["Display"]["Panel"].as<std::string>("") == "ST7796")
settingsMap[displayPanel] = st7796;
else if (yamlConfig["Display"]["Panel"].as<std::string>("") == "ILI9341")
settingsMap[displayPanel] = ili9341;
else if (yamlConfig["Display"]["Panel"].as<std::string>("") == "ILI9342")
settingsMap[displayPanel] = ili9342;
else if (yamlConfig["Display"]["Panel"].as<std::string>("") == "ILI9486")
settingsMap[displayPanel] = ili9486;
else if (yamlConfig["Display"]["Panel"].as<std::string>("") == "ILI9488")
settingsMap[displayPanel] = ili9488;
else if (yamlConfig["Display"]["Panel"].as<std::string>("") == "HX8357D")
settingsMap[displayPanel] = hx8357d;
else if (yamlConfig["Display"]["Panel"].as<std::string>("") == "X11")
settingsMap[displayPanel] = x11;
else if (yamlConfig["Display"]["Panel"].as<std::string>("") == "FB")
settingsMap[displayPanel] = fb;
settingsMap[displayHeight] = yamlConfig["Display"]["Height"].as<int>(0);
settingsMap[displayWidth] = yamlConfig["Display"]["Width"].as<int>(0);
settingsMap[displayDC] = yamlConfig["Display"]["DC"].as<int>(-1);
settingsMap[displayCS] = yamlConfig["Display"]["CS"].as<int>(-1);
settingsMap[displayRGBOrder] = yamlConfig["Display"]["RGBOrder"].as<bool>(false);
settingsMap[displayBacklight] = yamlConfig["Display"]["Backlight"].as<int>(-1);
settingsMap[displayBacklightInvert] = yamlConfig["Display"]["BacklightInvert"].as<bool>(false);
settingsMap[displayBacklightPWMChannel] = yamlConfig["Display"]["BacklightPWMChannel"].as<int>(-1);
settingsMap[displayReset] = yamlConfig["Display"]["Reset"].as<int>(-1);
settingsMap[displayOffsetX] = yamlConfig["Display"]["OffsetX"].as<int>(0);
settingsMap[displayOffsetY] = yamlConfig["Display"]["OffsetY"].as<int>(0);
settingsMap[displayRotate] = yamlConfig["Display"]["Rotate"].as<bool>(false);
settingsMap[displayOffsetRotate] = yamlConfig["Display"]["OffsetRotate"].as<int>(1);
settingsMap[displayInvert] = yamlConfig["Display"]["Invert"].as<bool>(false);
settingsMap[displayBusFrequency] = yamlConfig["Display"]["BusFrequency"].as<int>(40000000);
if (yamlConfig["Display"]["spidev"]) {
portduino_config.display_spi_dev = "/dev/" + yamlConfig["Display"]["spidev"].as<std::string>("spidev0.1");
if (portduino_config.display_spi_dev.length() == 14) {
int x = portduino_config.display_spi_dev.at(11) - '0';
int y = portduino_config.display_spi_dev.at(13) - '0';
settingsStrings[displayspidev] = "/dev/" + yamlConfig["Display"]["spidev"].as<std::string>("spidev0.1");
if (settingsStrings[displayspidev].length() == 14) {
int x = settingsStrings[displayspidev].at(11) - '0';
int y = settingsStrings[displayspidev].at(13) - '0';
if (x >= 0 && x < 10 && y >= 0 && y < 10) {
portduino_config.display_spi_dev_int = x + y << 4;
portduino_config.touchscreen_spi_dev_int = portduino_config.display_spi_dev_int;
settingsMap[displayspidev] = x + y << 4;
settingsMap[touchscreenspidev] = settingsMap[displayspidev];
}
}
}
}
if (yamlConfig["Touchscreen"]) {
if (yamlConfig["Touchscreen"]["Module"].as<std::string>("") == "XPT2046")
portduino_config.touchscreenModule = xpt2046;
settingsMap[touchscreenModule] = xpt2046;
else if (yamlConfig["Touchscreen"]["Module"].as<std::string>("") == "STMPE610")
portduino_config.touchscreenModule = stmpe610;
settingsMap[touchscreenModule] = stmpe610;
else if (yamlConfig["Touchscreen"]["Module"].as<std::string>("") == "GT911")
portduino_config.touchscreenModule = gt911;
settingsMap[touchscreenModule] = gt911;
else if (yamlConfig["Touchscreen"]["Module"].as<std::string>("") == "FT5x06")
portduino_config.touchscreenModule = ft5x06;
readGPIOFromYaml(yamlConfig["Touchscreen"]["CS"], portduino_config.touchscreenCS, -1);
readGPIOFromYaml(yamlConfig["Touchscreen"]["IRQ"], portduino_config.touchscreenIRQ, -1);
portduino_config.touchscreenBusFrequency = yamlConfig["Touchscreen"]["BusFrequency"].as<int>(1000000);
portduino_config.touchscreenRotate = yamlConfig["Touchscreen"]["Rotate"].as<int>(-1);
portduino_config.touchscreenI2CAddr = yamlConfig["Touchscreen"]["I2CAddr"].as<int>(-1);
settingsMap[touchscreenModule] = ft5x06;
settingsMap[touchscreenCS] = yamlConfig["Touchscreen"]["CS"].as<int>(-1);
settingsMap[touchscreenIRQ] = yamlConfig["Touchscreen"]["IRQ"].as<int>(-1);
settingsMap[touchscreenBusFrequency] = yamlConfig["Touchscreen"]["BusFrequency"].as<int>(1000000);
settingsMap[touchscreenRotate] = yamlConfig["Touchscreen"]["Rotate"].as<int>(-1);
settingsMap[touchscreenI2CAddr] = yamlConfig["Touchscreen"]["I2CAddr"].as<int>(-1);
if (yamlConfig["Touchscreen"]["spidev"]) {
portduino_config.touchscreen_spi_dev = "/dev/" + yamlConfig["Touchscreen"]["spidev"].as<std::string>("");
if (portduino_config.touchscreen_spi_dev.length() == 14) {
int x = portduino_config.touchscreen_spi_dev.at(11) - '0';
int y = portduino_config.touchscreen_spi_dev.at(13) - '0';
settingsStrings[touchscreenspidev] = "/dev/" + yamlConfig["Touchscreen"]["spidev"].as<std::string>("");
if (settingsStrings[touchscreenspidev].length() == 14) {
int x = settingsStrings[touchscreenspidev].at(11) - '0';
int y = settingsStrings[touchscreenspidev].at(13) - '0';
if (x >= 0 && x < 10 && y >= 0 && y < 10) {
portduino_config.touchscreen_spi_dev_int = x + y << 4;
settingsMap[touchscreenspidev] = x + y << 4;
}
}
}
}
if (yamlConfig["Input"]) {
portduino_config.keyboardDevice = (yamlConfig["Input"]["KeyboardDevice"]).as<std::string>("");
portduino_config.pointerDevice = (yamlConfig["Input"]["PointerDevice"]).as<std::string>("");
readGPIOFromYaml(yamlConfig["Input"]["User"], portduino_config.userButtonPin);
readGPIOFromYaml(yamlConfig["Input"]["TrackballUp"], portduino_config.tbUpPin);
readGPIOFromYaml(yamlConfig["Input"]["TrackballDown"], portduino_config.tbDownPin);
readGPIOFromYaml(yamlConfig["Input"]["TrackballLeft"], portduino_config.tbLeftPin);
readGPIOFromYaml(yamlConfig["Input"]["TrackballRight"], portduino_config.tbRightPin);
readGPIOFromYaml(yamlConfig["Input"]["TrackballPress"], portduino_config.tbPressPin);
settingsStrings[keyboardDevice] = (yamlConfig["Input"]["KeyboardDevice"]).as<std::string>("");
settingsStrings[pointerDevice] = (yamlConfig["Input"]["PointerDevice"]).as<std::string>("");
settingsMap[userButtonPin] = yamlConfig["Input"]["User"].as<int>(RADIOLIB_NC);
settingsMap[tbUpPin] = yamlConfig["Input"]["TrackballUp"].as<int>(RADIOLIB_NC);
settingsMap[tbDownPin] = yamlConfig["Input"]["TrackballDown"].as<int>(RADIOLIB_NC);
settingsMap[tbLeftPin] = yamlConfig["Input"]["TrackballLeft"].as<int>(RADIOLIB_NC);
settingsMap[tbRightPin] = yamlConfig["Input"]["TrackballRight"].as<int>(RADIOLIB_NC);
settingsMap[tbPressPin] = yamlConfig["Input"]["TrackballPress"].as<int>(RADIOLIB_NC);
if (yamlConfig["Input"]["TrackballDirection"].as<std::string>("RISING") == "RISING") {
portduino_config.tbDirection = 4;
settingsMap[tbDirection] = 4;
} else if (yamlConfig["Input"]["TrackballDirection"].as<std::string>("RISING") == "FALLING") {
portduino_config.tbDirection = 3;
settingsMap[tbDirection] = 3;
}
}
if (yamlConfig["Webserver"]) {
portduino_config.webserverport = (yamlConfig["Webserver"]["Port"]).as<int>(-1);
portduino_config.webserver_root_path =
settingsMap[webserverport] = (yamlConfig["Webserver"]["Port"]).as<int>(-1);
settingsStrings[webserverrootpath] =
(yamlConfig["Webserver"]["RootPath"]).as<std::string>("/usr/share/meshtasticd/web");
portduino_config.webserver_ssl_key_path =
settingsStrings[websslkeypath] =
(yamlConfig["Webserver"]["SSLKey"]).as<std::string>("/etc/meshtasticd/ssl/private_key.pem");
portduino_config.webserver_ssl_cert_path =
settingsStrings[websslcertpath] =
(yamlConfig["Webserver"]["SSLCert"]).as<std::string>("/etc/meshtasticd/ssl/certificate.pem");
}
if (yamlConfig["HostMetrics"]) {
portduino_config.hostMetrics_channel = (yamlConfig["HostMetrics"]["Channel"]).as<int>(0);
portduino_config.hostMetrics_interval = (yamlConfig["HostMetrics"]["ReportInterval"]).as<int>(0);
portduino_config.hostMetrics_user_command = (yamlConfig["HostMetrics"]["UserStringCommand"]).as<std::string>("");
settingsMap[hostMetrics_channel] = (yamlConfig["HostMetrics"]["Channel"]).as<int>(0);
settingsMap[hostMetrics_interval] = (yamlConfig["HostMetrics"]["ReportInterval"]).as<int>(0);
settingsStrings[hostMetrics_user_command] = (yamlConfig["HostMetrics"]["UserStringCommand"]).as<std::string>("");
}
if (yamlConfig["Config"]) {
if (yamlConfig["Config"]["DisplayMode"]) {
portduino_config.has_configDisplayMode = true;
settingsMap[has_configDisplayMode] = true;
if ((yamlConfig["Config"]["DisplayMode"]).as<std::string>("") == "TWOCOLOR") {
portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR;
settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR;
} else if ((yamlConfig["Config"]["DisplayMode"]).as<std::string>("") == "INVERTED") {
portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_INVERTED;
settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_INVERTED;
} else if ((yamlConfig["Config"]["DisplayMode"]).as<std::string>("") == "COLOR") {
portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_COLOR;
settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_COLOR;
} else {
portduino_config.configDisplayMode = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT;
settingsMap[configDisplayMode] = meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT;
}
}
}
if (yamlConfig["General"]) {
portduino_config.MaxNodes = (yamlConfig["General"]["MaxNodes"]).as<int>(200);
portduino_config.maxtophone = (yamlConfig["General"]["MaxMessageQueue"]).as<int>(100);
portduino_config.config_directory = (yamlConfig["General"]["ConfigDirectory"]).as<std::string>("");
portduino_config.available_directory =
settingsMap[maxnodes] = (yamlConfig["General"]["MaxNodes"]).as<int>(200);
settingsMap[maxtophone] = (yamlConfig["General"]["MaxMessageQueue"]).as<int>(100);
settingsStrings[config_directory] = (yamlConfig["General"]["ConfigDirectory"]).as<std::string>("");
settingsStrings[available_directory] =
(yamlConfig["General"]["AvailableDirectory"]).as<std::string>("/etc/meshtasticd/available.d/");
if ((yamlConfig["General"]["MACAddress"]).as<std::string>("") != "" &&
(yamlConfig["General"]["MACAddressSource"]).as<std::string>("") != "") {
std::cout << "Cannot set both MACAddress and MACAddressSource!" << std::endl;
exit(EXIT_FAILURE);
}
portduino_config.mac_address = (yamlConfig["General"]["MACAddress"]).as<std::string>("");
if (portduino_config.mac_address != "") {
portduino_config.mac_address_explicit = true;
} else if ((yamlConfig["General"]["MACAddressSource"]).as<std::string>("") != "") {
portduino_config.mac_address_source = (yamlConfig["General"]["MACAddressSource"]).as<std::string>("");
std::ifstream infile("/sys/class/net/" + portduino_config.mac_address_source + "/address");
std::getline(infile, portduino_config.mac_address);
settingsStrings[mac_address] = (yamlConfig["General"]["MACAddress"]).as<std::string>("");
if ((yamlConfig["General"]["MACAddressSource"]).as<std::string>("") != "") {
std::ifstream infile("/sys/class/net/" + (yamlConfig["General"]["MACAddressSource"]).as<std::string>("") +
"/address");
std::getline(infile, settingsStrings[mac_address]);
}
// https://stackoverflow.com/a/20326454
portduino_config.mac_address.erase(
std::remove(portduino_config.mac_address.begin(), portduino_config.mac_address.end(), ':'),
portduino_config.mac_address.end());
settingsStrings[mac_address].erase(
std::remove(settingsStrings[mac_address].begin(), settingsStrings[mac_address].end(), ':'),
settingsStrings[mac_address].end());
}
} catch (YAML::Exception &e) {
std::cout << "*** Exception " << e.what() << std::endl;
@@ -730,12 +851,12 @@ bool MAC_from_string(std::string mac_str, uint8_t *dmac)
{
mac_str.erase(std::remove(mac_str.begin(), mac_str.end(), ':'), mac_str.end());
if (mac_str.length() == 12) {
dmac[0] = std::stoi(portduino_config.mac_address.substr(0, 2), nullptr, 16);
dmac[1] = std::stoi(portduino_config.mac_address.substr(2, 2), nullptr, 16);
dmac[2] = std::stoi(portduino_config.mac_address.substr(4, 2), nullptr, 16);
dmac[3] = std::stoi(portduino_config.mac_address.substr(6, 2), nullptr, 16);
dmac[4] = std::stoi(portduino_config.mac_address.substr(8, 2), nullptr, 16);
dmac[5] = std::stoi(portduino_config.mac_address.substr(10, 2), nullptr, 16);
dmac[0] = std::stoi(settingsStrings[mac_address].substr(0, 2), nullptr, 16);
dmac[1] = std::stoi(settingsStrings[mac_address].substr(2, 2), nullptr, 16);
dmac[2] = std::stoi(settingsStrings[mac_address].substr(4, 2), nullptr, 16);
dmac[3] = std::stoi(settingsStrings[mac_address].substr(6, 2), nullptr, 16);
dmac[4] = std::stoi(settingsStrings[mac_address].substr(8, 2), nullptr, 16);
dmac[5] = std::stoi(settingsStrings[mac_address].substr(10, 2), nullptr, 16);
return true;
} else {
return false;
@@ -754,19 +875,4 @@ std::string exec(const char *cmd)
result += buffer.data();
}
return result;
}
void readGPIOFromYaml(YAML::Node sourceNode, pinMapping &destPin, int pinDefault)
{
if (sourceNode.IsMap()) {
destPin.enabled = true;
destPin.pin = sourceNode["pin"].as<int>(pinDefault);
destPin.line = sourceNode["line"].as<int>(destPin.pin);
destPin.gpiochip = sourceNode["gpiochip"].as<int>(portduino_config.lora_default_gpiochip);
} else if (sourceNode) { // backwards API compatibility
destPin.enabled = true;
destPin.pin = sourceNode.as<int>(pinDefault);
destPin.line = destPin.pin;
destPin.gpiochip = portduino_config.lora_default_gpiochip;
}
}

View File

@@ -6,7 +6,6 @@
#include "LR11x0Interface.h"
#include "Module.h"
#include "platform/portduino/USBHal.h"
#include "yaml-cpp/yaml.h"
// Product strings for auto-configuration
// {"PRODUCT_STRING", "CONFIG.YAML"}
@@ -20,10 +19,36 @@ inline const std::unordered_map<std::string, std::string> configProducts = {
{"RAK6421-13300-S1", "lora-RAK6421-13300-slot1.yaml"},
{"RAK6421-13300-S2", "lora-RAK6421-13300-slot2.yaml"}};
enum screen_modules { no_screen, x11, fb, st7789, st7735, st7735s, st7796, ili9341, ili9342, ili9486, ili9488, hx8357d };
enum touchscreen_modules { no_touchscreen, xpt2046, stmpe610, gt911, ft5x06 };
enum portduino_log_level { level_error, level_warn, level_info, level_debug, level_trace };
enum lora_module_enum {
enum configNames {
default_gpiochip,
cs_pin,
cs_line,
cs_gpiochip,
irq_pin,
irq_line,
irq_gpiochip,
busy_pin,
busy_line,
busy_gpiochip,
reset_pin,
reset_line,
reset_gpiochip,
txen_pin,
txen_line,
txen_gpiochip,
rxen_pin,
rxen_line,
rxen_gpiochip,
sx126x_ant_sw_pin,
sx126x_ant_sw_line,
sx126x_ant_sw_gpiochip,
sx126x_max_power,
sx128x_max_power,
lr1110_max_power,
lr1120_max_power,
rf95_max_power,
dio2_as_rf_switch,
dio3_tcxo_voltage,
use_simradio,
use_autoconf,
use_rf95,
@@ -33,18 +58,72 @@ enum lora_module_enum {
use_lr1110,
use_lr1120,
use_lr1121,
use_llcc68
};
struct pinMapping {
std::string config_section;
std::string config_name;
int pin = RADIOLIB_NC;
int gpiochip;
int line;
bool enabled = false;
use_llcc68,
lora_usb_serial_num,
lora_usb_pid,
lora_usb_vid,
userButtonPin,
tbUpPin,
tbDownPin,
tbLeftPin,
tbRightPin,
tbPressPin,
tbDirection,
spidev,
spiSpeed,
i2cdev,
has_gps,
touchscreenModule,
touchscreenCS,
touchscreenIRQ,
touchscreenI2CAddr,
touchscreenBusFrequency,
touchscreenRotate,
touchscreenspidev,
displayspidev,
displayBusFrequency,
displayPanel,
displayWidth,
displayHeight,
displayCS,
displayDC,
displayRGBOrder,
displayBacklight,
displayBacklightPWMChannel,
displayBacklightInvert,
displayReset,
displayRotate,
displayOffsetRotate,
displayOffsetX,
displayOffsetY,
displayInvert,
keyboardDevice,
pointerDevice,
logoutputlevel,
traceFilename,
webserver,
webserverport,
webserverrootpath,
websslkeypath,
websslcertpath,
maxtophone,
maxnodes,
ascii_logs,
config_directory,
available_directory,
mac_address,
hostMetrics_interval,
hostMetrics_channel,
hostMetrics_user_command,
configDisplayMode,
has_configDisplayMode
};
enum { no_screen, x11, fb, st7789, st7735, st7735s, st7796, ili9341, ili9342, ili9486, ili9488, hx8357d };
enum { no_touchscreen, xpt2046, stmpe610, gt911, ft5x06 };
enum { level_error, level_warn, level_info, level_debug, level_trace };
extern std::map<configNames, int> settingsMap;
extern std::map<configNames, std::string> settingsStrings;
extern std::ofstream traceFile;
extern Ch341Hal *ch341Hal;
int initGPIOPin(int pinNum, std::string gpioChipname, int line);
@@ -52,422 +131,13 @@ bool loadConfig(const char *configPath);
static bool ends_with(std::string_view str, std::string_view suffix);
void getMacAddr(uint8_t *dmac);
bool MAC_from_string(std::string mac_str, uint8_t *dmac);
void readGPIOFromYaml(YAML::Node sourceNode, pinMapping &destPin, int pinDefault = RADIOLIB_NC);
std::string exec(const char *cmd);
extern struct portduino_config_struct {
// Lora
std::map<lora_module_enum, std::string> loraModules = {
{use_simradio, "sim"}, {use_autoconf, "auto"}, {use_rf95, "RF95"}, {use_sx1262, "sx1262"}, {use_sx1268, "sx1268"},
{use_sx1280, "sx1280"}, {use_lr1110, "lr1110"}, {use_lr1120, "lr1120"}, {use_lr1121, "lr1121"}, {use_llcc68, "LLCC68"}};
std::map<screen_modules, std::string> screen_names = {{x11, "X11"}, {fb, "FB"}, {st7789, "ST7789"},
{st7735, "ST7735"}, {st7735s, "ST7735S"}, {st7796, "ST7796"},
{ili9341, "ILI9341"}, {ili9342, "ILI9342"}, {ili9486, "ILI9486"},
{ili9488, "ILI9488"}, {hx8357d, "HX8357D"}};
lora_module_enum lora_module;
bool has_rfswitch_table = false;
uint32_t rfswitch_dio_pins[5] = {RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
Module::RfSwitchMode_t rfswitch_table[8];
bool force_simradio = false;
bool has_device_id = false;
uint8_t device_id[16] = {0};
std::string lora_spi_dev = "";
std::string lora_usb_serial_num = "";
int lora_spi_dev_int = 0;
int lora_default_gpiochip = 0;
int sx126x_max_power = 22;
int sx128x_max_power = 13;
int lr1110_max_power = 22;
int lr1120_max_power = 13;
int rf95_max_power = 20;
bool dio2_as_rf_switch = false;
int dio3_tcxo_voltage = 0;
int lora_usb_pid = 0x5512;
int lora_usb_vid = 0x1A86;
int spiSpeed = 2000000;
pinMapping lora_cs_pin = {"Lora", "CS"};
pinMapping lora_irq_pin = {"Lora", "IRQ"};
pinMapping lora_busy_pin = {"Lora", "Busy"};
pinMapping lora_reset_pin = {"Lora", "Reset"};
pinMapping lora_txen_pin = {"Lora", "TXen"};
pinMapping lora_rxen_pin = {"Lora", "RXen"};
pinMapping lora_sx126x_ant_sw_pin = {"Lora", "SX126X_ANT_SW"};
// GPS
bool has_gps = false;
// I2C
std::string i2cdev = "";
// Display
std::string display_spi_dev = "";
int display_spi_dev_int = 0;
int displayBusFrequency = 40000000;
screen_modules displayPanel = no_screen;
int displayWidth = 0;
int displayHeight = 0;
bool displayRGBOrder = false;
bool displayBacklightInvert = false;
bool displayRotate = false;
int displayOffsetRotate = 1;
bool displayInvert = false;
int displayOffsetX = 0;
int displayOffsetY = 0;
pinMapping displayDC = {"Display", "DC"};
pinMapping displayCS = {"Display", "CS"};
pinMapping displayBacklight = {"Display", "Backlight"};
pinMapping displayBacklightPWMChannel = {"Display", "BacklightPWMChannel"};
pinMapping displayReset = {"Display", "Reset"};
// Touchscreen
std::string touchscreen_spi_dev = "";
int touchscreen_spi_dev_int = 0;
touchscreen_modules touchscreenModule = no_touchscreen;
int touchscreenI2CAddr = -1;
int touchscreenBusFrequency = 1000000;
int touchscreenRotate = -1;
pinMapping touchscreenCS = {"Touchscreen", "CS"};
pinMapping touchscreenIRQ = {"Touchscreen", "IRQ"};
// Input
std::string keyboardDevice = "";
std::string pointerDevice = "";
int tbDirection;
pinMapping userButtonPin = {"Input", "User"};
pinMapping tbUpPin = {"Input", "TrackballUp"};
pinMapping tbDownPin = {"Input", "TrackballDown"};
pinMapping tbLeftPin = {"Input", "TrackballLwft"};
pinMapping tbRightPin = {"Input", "TrackballRight"};
pinMapping tbPressPin = {"Input", "TrackballPress"};
// Logging
portduino_log_level logoutputlevel = level_debug;
std::string traceFilename;
bool ascii_logs = !isatty(1);
bool ascii_logs_explicit = false;
// Webserver
std::string webserver_root_path = "";
std::string webserver_ssl_key_path = "/etc/meshtasticd/ssl/private_key.pem";
std::string webserver_ssl_cert_path = "/etc/meshtasticd/ssl/certificate.pem";
int webserverport = -1;
// HostMetrics
std::string hostMetrics_user_command = "";
int hostMetrics_interval = 0;
int hostMetrics_channel = 0;
// config
int configDisplayMode = 0;
bool has_configDisplayMode = false;
// General
std::string mac_address = "";
bool mac_address_explicit = false;
std::string mac_address_source = "";
std::string config_directory = "";
std::string available_directory = "/etc/meshtasticd/available.d/";
int maxtophone = 100;
int MaxNodes = 200;
pinMapping *all_pins[20] = {&lora_cs_pin,
&lora_irq_pin,
&lora_busy_pin,
&lora_reset_pin,
&lora_txen_pin,
&lora_rxen_pin,
&lora_sx126x_ant_sw_pin,
&displayDC,
&displayCS,
&displayBacklight,
&displayBacklightPWMChannel,
&displayReset,
&touchscreenCS,
&touchscreenIRQ,
&userButtonPin,
&tbUpPin,
&tbDownPin,
&tbLeftPin,
&tbRightPin,
&tbPressPin};
std::string emit_yaml()
{
YAML::Emitter out;
out << YAML::BeginMap;
// Lora
out << YAML::Key << "Lora" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "Module" << YAML::Value << loraModules[lora_module];
for (auto lora_pin : all_pins) {
if (lora_pin->config_section == "Lora" && lora_pin->enabled) {
out << YAML::Key << lora_pin->config_name << YAML::Value << YAML::BeginMap;
out << YAML::Key << "pin" << YAML::Value << lora_pin->pin;
out << YAML::Key << "line" << YAML::Value << lora_pin->line;
out << YAML::Key << "gpiochip" << YAML::Value << lora_pin->gpiochip;
out << YAML::EndMap; // User
}
}
if (sx126x_max_power != 22)
out << YAML::Key << "SX126X_MAX_POWER" << YAML::Value << sx126x_max_power;
if (sx128x_max_power != 13)
out << YAML::Key << "SX128X_MAX_POWER" << YAML::Value << sx128x_max_power;
if (lr1110_max_power != 22)
out << YAML::Key << "LR1110_MAX_POWER" << YAML::Value << lr1110_max_power;
if (lr1120_max_power != 13)
out << YAML::Key << "LR1120_MAX_POWER" << YAML::Value << lr1120_max_power;
if (rf95_max_power != 20)
out << YAML::Key << "RF95_MAX_POWER" << YAML::Value << rf95_max_power;
out << YAML::Key << "DIO2_AS_RF_SWITCH" << YAML::Value << dio2_as_rf_switch;
if (dio3_tcxo_voltage != 0)
out << YAML::Key << "DIO3_TCXO_VOLTAGE" << YAML::Value << dio3_tcxo_voltage;
if (lora_usb_pid != 0x5512)
out << YAML::Key << "USB_PID" << YAML::Value << YAML::Hex << lora_usb_pid;
if (lora_usb_vid != 0x1A86)
out << YAML::Key << "USB_VID" << YAML::Value << YAML::Hex << lora_usb_vid;
if (lora_spi_dev != "")
out << YAML::Key << "spidev" << YAML::Value << lora_spi_dev;
if (lora_usb_serial_num != "")
out << YAML::Key << "USB_Serialnum" << YAML::Value << lora_usb_serial_num;
out << YAML::Key << "spiSpeed" << YAML::Value << spiSpeed;
if (rfswitch_dio_pins[0] != RADIOLIB_NC) {
out << YAML::Key << "rfswitch_table" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "pins";
out << YAML::Value << YAML::Flow << YAML::BeginSeq;
for (int i = 0; i < 5; i++) {
// set up the pin array first
if (rfswitch_dio_pins[i] == RADIOLIB_LR11X0_DIO5)
out << "DIO5";
if (rfswitch_dio_pins[i] == RADIOLIB_LR11X0_DIO6)
out << "DIO6";
if (rfswitch_dio_pins[i] == RADIOLIB_LR11X0_DIO7)
out << "DIO7";
if (rfswitch_dio_pins[i] == RADIOLIB_LR11X0_DIO8)
out << "DIO8";
if (rfswitch_dio_pins[i] == RADIOLIB_LR11X0_DIO10)
out << "DIO10";
}
out << YAML::EndSeq;
for (int i = 0; i < 7; i++) {
switch (i) {
case 0:
out << YAML::Key << "MODE_STBY";
break;
case 1:
out << YAML::Key << "MODE_RX";
break;
case 2:
out << YAML::Key << "MODE_TX";
break;
case 3:
out << YAML::Key << "MODE_TX_HP";
break;
case 4:
out << YAML::Key << "MODE_TX_HF";
break;
case 5:
out << YAML::Key << "MODE_GNSS";
break;
case 6:
out << YAML::Key << "MODE_WIFI";
break;
}
out << YAML::Value << YAML::Flow << YAML::BeginSeq;
for (int j = 0; j < 5; j++) {
if (rfswitch_table[i].values[j] == HIGH) {
out << "HIGH";
} else {
out << "LOW";
}
}
out << YAML::EndSeq;
}
out << YAML::EndMap; // rfswitch_table
}
out << YAML::EndMap; // Lora
if (i2cdev != "") {
out << YAML::Key << "I2C" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "I2CDevice" << YAML::Value << i2cdev;
out << YAML::EndMap; // I2C
}
// Display
if (displayPanel != no_screen) {
out << YAML::Key << "Display" << YAML::Value << YAML::BeginMap;
for (auto &screen_name : screen_names) {
if (displayPanel == screen_name.first)
out << YAML::Key << "Module" << YAML::Value << screen_name.second;
}
for (auto display_pin : all_pins) {
if (display_pin->config_section == "Display" && display_pin->enabled) {
out << YAML::Key << display_pin->config_name << YAML::Value << YAML::BeginMap;
out << YAML::Key << "pin" << YAML::Value << display_pin->pin;
out << YAML::Key << "line" << YAML::Value << display_pin->line;
out << YAML::Key << "gpiochip" << YAML::Value << display_pin->gpiochip;
out << YAML::EndMap;
}
}
out << YAML::Key << "spidev" << YAML::Value << display_spi_dev;
out << YAML::Key << "BusFrequency" << YAML::Value << displayBusFrequency;
if (displayWidth)
out << YAML::Key << "Width" << YAML::Value << displayWidth;
if (displayHeight)
out << YAML::Key << "Height" << YAML::Value << displayHeight;
if (displayRGBOrder)
out << YAML::Key << "RGBOrder" << YAML::Value << true;
if (displayBacklightInvert)
out << YAML::Key << "BacklightInvert" << YAML::Value << true;
if (displayRotate)
out << YAML::Key << "Rotate" << YAML::Value << true;
if (displayInvert)
out << YAML::Key << "Invert" << YAML::Value << true;
if (displayOffsetX)
out << YAML::Key << "OffsetX" << YAML::Value << displayOffsetX;
if (displayOffsetY)
out << YAML::Key << "OffsetY" << YAML::Value << displayOffsetY;
out << YAML::Key << "OffsetRotate" << YAML::Value << displayOffsetRotate;
out << YAML::EndMap; // Display
}
// Touchscreen
if (touchscreen_spi_dev != "") {
out << YAML::Key << "Touchscreen" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "spidev" << YAML::Value << touchscreen_spi_dev;
out << YAML::Key << "BusFrequency" << YAML::Value << touchscreenBusFrequency;
switch (touchscreenModule) {
case xpt2046:
out << YAML::Key << "Module" << YAML::Value << "XPT2046";
case stmpe610:
out << YAML::Key << "Module" << YAML::Value << "STMPE610";
case gt911:
out << YAML::Key << "Module" << YAML::Value << "GT911";
case ft5x06:
out << YAML::Key << "Module" << YAML::Value << "FT5x06";
}
for (auto touchscreen_pin : all_pins) {
if (touchscreen_pin->config_section == "Touchscreen" && touchscreen_pin->enabled) {
out << YAML::Key << touchscreen_pin->config_name << YAML::Value << YAML::BeginMap;
out << YAML::Key << "pin" << YAML::Value << touchscreen_pin->pin;
out << YAML::Key << "line" << YAML::Value << touchscreen_pin->line;
out << YAML::Key << "gpiochip" << YAML::Value << touchscreen_pin->gpiochip;
out << YAML::EndMap;
}
}
if (touchscreenRotate != -1)
out << YAML::Key << "Rotate" << YAML::Value << touchscreenRotate;
if (touchscreenI2CAddr != -1)
out << YAML::Key << "I2CAddr" << YAML::Value << touchscreenI2CAddr;
out << YAML::EndMap; // Touchscreen
}
// Input
out << YAML::Key << "Input" << YAML::Value << YAML::BeginMap;
if (keyboardDevice != "")
out << YAML::Key << "KeyboardDevice" << YAML::Value << keyboardDevice;
if (pointerDevice != "")
out << YAML::Key << "PointerDevice" << YAML::Value << pointerDevice;
for (auto input_pin : all_pins) {
if (input_pin->config_section == "Input" && input_pin->enabled) {
out << YAML::Key << input_pin->config_name << YAML::Value << YAML::BeginMap;
out << YAML::Key << "pin" << YAML::Value << input_pin->pin;
out << YAML::Key << "line" << YAML::Value << input_pin->line;
out << YAML::Key << "gpiochip" << YAML::Value << input_pin->gpiochip;
out << YAML::EndMap;
}
}
if (tbDirection == 3)
out << YAML::Key << "TrackballDirection" << YAML::Value << "FALLING";
out << YAML::EndMap; // Input
out << YAML::Key << "Logging" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "LogLevel" << YAML::Value;
switch (logoutputlevel) {
case level_error:
out << "error";
break;
case level_warn:
out << "warn";
break;
case level_info:
out << "info";
break;
case level_debug:
out << "debug";
break;
case level_trace:
out << "trace";
break;
}
if (traceFilename != "")
out << YAML::Key << "TraceFile" << YAML::Value << traceFilename;
if (ascii_logs_explicit) {
out << YAML::Key << "AsciiLogs" << YAML::Value << ascii_logs;
}
out << YAML::EndMap; // Logging
// Webserver
if (webserver_root_path != "") {
out << YAML::Key << "Webserver" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "RootPath" << YAML::Value << webserver_root_path;
out << YAML::Key << "SSLKey" << YAML::Value << webserver_ssl_key_path;
out << YAML::Key << "SSLCert" << YAML::Value << webserver_ssl_cert_path;
out << YAML::Key << "Port" << YAML::Value << webserverport;
out << YAML::EndMap; // Webserver
}
// HostMetrics
if (hostMetrics_user_command != "") {
out << YAML::Key << "HostMetrics" << YAML::Value << YAML::BeginMap;
out << YAML::Key << "UserStringCommand" << YAML::Value << hostMetrics_user_command;
out << YAML::Key << "ReportInterval" << YAML::Value << hostMetrics_interval;
out << YAML::Key << "Channel" << YAML::Value << hostMetrics_channel;
out << YAML::EndMap; // HostMetrics
}
// config
if (has_configDisplayMode) {
out << YAML::Key << "Config" << YAML::Value << YAML::BeginMap;
switch (configDisplayMode) {
case meshtastic_Config_DisplayConfig_DisplayMode_TWOCOLOR:
out << YAML::Key << "DisplayMode" << YAML::Value << "TWOCOLOR";
case meshtastic_Config_DisplayConfig_DisplayMode_INVERTED:
out << YAML::Key << "DisplayMode" << YAML::Value << "INVERTED";
case meshtastic_Config_DisplayConfig_DisplayMode_COLOR:
out << YAML::Key << "DisplayMode" << YAML::Value << "COLOR";
case meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT:
out << YAML::Key << "DisplayMode" << YAML::Value << "DEFAULT";
}
out << YAML::EndMap; // Config
}
// General
out << YAML::Key << "General" << YAML::Value << YAML::BeginMap;
if (config_directory != "")
out << YAML::Key << "ConfigDirectory" << YAML::Value << config_directory;
if (mac_address_explicit)
out << YAML::Key << "MACAddress" << YAML::Value << mac_address;
if (mac_address_source != "")
out << YAML::Key << "MACAddressSource" << YAML::Value << mac_address_source;
if (available_directory != "")
out << YAML::Key << "AvailableDirectory" << YAML::Value << available_directory;
out << YAML::Key << "MaxMessageQueue" << YAML::Value << maxtophone;
out << YAML::Key << "MaxNodes" << YAML::Value << MaxNodes;
out << YAML::EndMap; // General
return out.c_str();
}
} portduino_config;

View File

@@ -28,9 +28,9 @@
#endif
#ifndef HAS_TRACKBALL
#define HAS_TRACKBALL 1
#define TB_DOWN (uint8_t) portduino_config.tbDownPin.pin
#define TB_UP (uint8_t) portduino_config.tbUpPin.pin
#define TB_LEFT (uint8_t) portduino_config.tbLeftPin.pin
#define TB_RIGHT (uint8_t) portduino_config.tbRightPin.pin
#define TB_PRESS (uint8_t) portduino_config.tbPressPin.pin
#define TB_DOWN (uint8_t) settingsMap[tbDownPin]
#define TB_UP (uint8_t) settingsMap[tbUpPin]
#define TB_LEFT (uint8_t) settingsMap[tbLeftPin]
#define TB_RIGHT (uint8_t) settingsMap[tbRightPin]
#define TB_PRESS (uint8_t) settingsMap[tbPressPin]
#endif

View File

@@ -1,105 +1,42 @@
#include "../test_helpers.h"
#include <memory>
// Helper function to test common packet fields and structure
void verify_text_message_packet_structure(const std::string &json, const char *expected_text)
{
TEST_ASSERT_TRUE(json.length() > 0);
// Use smart pointer for automatic memory management
std::unique_ptr<JSONValue> root(JSON::Parse(json.c_str()));
TEST_ASSERT_NOT_NULL(root.get());
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check basic packet fields - use helper function to reduce duplication
auto check_field = [&](const char *field, uint32_t expected_value) {
auto it = jsonObj.find(field);
TEST_ASSERT_TRUE(it != jsonObj.end());
TEST_ASSERT_EQUAL(expected_value, (uint32_t)it->second->AsNumber());
};
check_field("from", 0x11223344);
check_field("to", 0x55667788);
check_field("id", 0x9999);
// Check message type
auto type_it = jsonObj.find("type");
TEST_ASSERT_TRUE(type_it != jsonObj.end());
TEST_ASSERT_EQUAL_STRING("text", type_it->second->AsString().c_str());
// Check payload
auto payload_it = jsonObj.find("payload");
TEST_ASSERT_TRUE(payload_it != jsonObj.end());
TEST_ASSERT_TRUE(payload_it->second->IsObject());
JSONObject payload = payload_it->second->AsObject();
auto text_it = payload.find("text");
TEST_ASSERT_TRUE(text_it != payload.end());
TEST_ASSERT_EQUAL_STRING(expected_text, text_it->second->AsString().c_str());
// No need for manual delete with smart pointer
}
// Test TEXT_MESSAGE_APP port
void test_text_message_serialization()
{
const char *test_text = "Hello Meshtastic!";
meshtastic_MeshPacket packet =
create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast<const uint8_t *>(test_text), strlen(test_text));
create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, (const uint8_t *)test_text, strlen(test_text));
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
verify_text_message_packet_structure(json, test_text);
}
// Test with nullptr to check robustness
void test_text_message_serialization_null()
{
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, nullptr, 0);
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
verify_text_message_packet_structure(json, "");
}
// Test TEXT_MESSAGE_APP port with very long message (boundary testing)
void test_text_message_serialization_long_text()
{
// Test with actual message size limits
constexpr size_t MAX_MESSAGE_SIZE = 200; // Typical LoRa payload limit
std::string long_text(MAX_MESSAGE_SIZE, 'A');
meshtastic_MeshPacket packet = create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP,
reinterpret_cast<const uint8_t *>(long_text.c_str()), long_text.length());
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
verify_text_message_packet_structure(json, long_text.c_str());
}
// Test with message over size limit (should fail)
void test_text_message_serialization_oversized()
{
constexpr size_t OVERSIZED_MESSAGE = 250; // Over the limit
std::string oversized_text(OVERSIZED_MESSAGE, 'B');
meshtastic_MeshPacket packet = create_test_packet(
meshtastic_PortNum_TEXT_MESSAGE_APP, reinterpret_cast<const uint8_t *>(oversized_text.c_str()), oversized_text.length());
// Should fail or return empty/error
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
// Should only verify first 234 characters for oversized messages
std::string expected_text = oversized_text.substr(0, 234);
verify_text_message_packet_structure(json, expected_text.c_str());
}
// Add test for malformed UTF-8 sequences
void test_text_message_serialization_invalid_utf8()
{
const uint8_t invalid_utf8[] = {0xFF, 0xFE, 0xFD, 0x00}; // Invalid UTF-8
meshtastic_MeshPacket packet =
create_test_packet(meshtastic_PortNum_TEXT_MESSAGE_APP, invalid_utf8, sizeof(invalid_utf8) - 1);
// Should not crash, may produce replacement characters
std::string json = MeshPacketSerializer::JsonSerialize(&packet, false);
TEST_ASSERT_TRUE(json.length() > 0);
JSONValue *root = JSON::Parse(json.c_str());
TEST_ASSERT_NOT_NULL(root);
TEST_ASSERT_TRUE(root->IsObject());
JSONObject jsonObj = root->AsObject();
// Check basic packet fields
TEST_ASSERT_TRUE(jsonObj.find("from") != jsonObj.end());
TEST_ASSERT_EQUAL(0x11223344, (uint32_t)jsonObj["from"]->AsNumber());
TEST_ASSERT_TRUE(jsonObj.find("to") != jsonObj.end());
TEST_ASSERT_EQUAL(0x55667788, (uint32_t)jsonObj["to"]->AsNumber());
TEST_ASSERT_TRUE(jsonObj.find("id") != jsonObj.end());
TEST_ASSERT_EQUAL(0x9999, (uint32_t)jsonObj["id"]->AsNumber());
// Check message type
TEST_ASSERT_TRUE(jsonObj.find("type") != jsonObj.end());
TEST_ASSERT_EQUAL_STRING("text", jsonObj["type"]->AsString().c_str());
// Check payload
TEST_ASSERT_TRUE(jsonObj.find("payload") != jsonObj.end());
TEST_ASSERT_TRUE(jsonObj["payload"]->IsObject());
JSONObject payload = jsonObj["payload"]->AsObject();
TEST_ASSERT_TRUE(payload.find("text") != payload.end());
TEST_ASSERT_EQUAL_STRING("Hello Meshtastic!", payload["text"]->AsString().c_str());
delete root;
}

View File

@@ -40,5 +40,3 @@
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define HAS_32768HZ 1

View File

@@ -26,7 +26,6 @@ extends = env:picomputer-s3
build_flags =
${env:picomputer-s3.build_flags}
-D MESHTASTIC_EXCLUDE_WEBSERVER=1
-D INPUTDRIVER_MATRIX_TYPE=1
-D USE_PIN_BUZZER=PIN_BUZZER
-D USE_SX127x

View File

@@ -70,7 +70,6 @@ build_flags =
${ft5x06.build_flags}
-D LGFX_SCREEN_WIDTH=240
-D LGFX_SCREEN_HEIGHT=320
-D DISPLAY_SIZE=320x240 ; landscape mode
-D LGFX_PANEL=ST7789
-D LGFX_ROTATION=1
-D LGFX_TOUCH_X_MIN=0

View File

@@ -62,6 +62,6 @@
// #define PCF8563_RTC 0x51 //Putting definitions in variant. h does not compile correctly
// has 32768 Hz crystal
#define HAS_32768HZ 1
#define HAS_32768HZ
#define USE_SH1106
#define USE_SH1106

View File

@@ -1,5 +1,5 @@
#define HAS_SCREEN 1
#define CANNED_MESSAGE_MODULE_ENABLE 1
#define HAS_GPS 1
#define MAX_RX_TOPHONE portduino_config.maxtophone
#define MAX_NUM_NODES portduino_config.MaxNodes
#define MAX_RX_TOPHONE settingsMap[maxtophone]
#define MAX_NUM_NODES settingsMap[maxnodes]

View File

@@ -3,8 +3,8 @@
#endif
#define CANNED_MESSAGE_MODULE_ENABLE 1
#define HAS_GPS 1
#define MAX_RX_TOPHONE portduino_config.maxtophone
#define MAX_NUM_NODES portduino_config.MaxNodes
#define MAX_RX_TOPHONE settingsMap[maxtophone]
#define MAX_NUM_NODES settingsMap[maxnodes]
// RAK12002 RTC Module
#define RV3028_RTC (uint8_t)0b1010010
#define RV3028_RTC (uint8_t)0b1010010

View File

@@ -1,15 +0,0 @@
; GAT562 Mesh Tracker Pro with Trackball support
[env:gat562_mesh_tracker_pro]
extends = nrf52840_base
board = gat562_mesh_tracker_pro
board_level = extra
build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/gat562_mesh_tracker_pro
-D GAT562_MESH_TRACKER_PRO
-DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
-DRADIOLIB_EXCLUDE_SX128X=1
-DRADIOLIB_EXCLUDE_SX127X=1
-DRADIOLIB_EXCLUDE_LR11X0=1
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/gat562_mesh_tracker_pro>
lib_deps =
${nrf52840_base.lib_deps}

View File

@@ -1,54 +0,0 @@
/*
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
Copyright (c) 2016 Sandeep Mistry All right reserved.
Copyright (c) 2018, Adafruit Industries (adafruit.com)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "variant.h"
#include "nrf.h"
#include "wiring_constants.h"
#include "wiring_digital.h"
const uint32_t g_ADigitalPinMap[] = {
// P0
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
// P1
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
void initVariant()
{
// LED1 & LED2
pinMode(PIN_LED1, OUTPUT);
ledOff(PIN_LED1);
pinMode(PIN_LED2, OUTPUT);
ledOff(PIN_LED2);
// 3V3 Power Rail
pinMode(PIN_3V3_EN, OUTPUT);
digitalWrite(PIN_3V3_EN, HIGH);
// Initialize trackball pins as inputs with pullup
#ifdef HAS_TRACKBALL
pinMode(TB_UP, INPUT_PULLUP);
pinMode(TB_DOWN, INPUT_PULLUP);
pinMode(TB_LEFT, INPUT_PULLUP);
pinMode(TB_RIGHT, INPUT_PULLUP);
pinMode(TB_PRESS, INPUT_PULLUP);
#endif
}

View File

@@ -1,300 +0,0 @@
/*
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
Copyright (c) 2016 Sandeep Mistry All right reserved.
Copyright (c) 2018, Adafruit Industries (adafruit.com)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _VARIANT_GAT562_MESH_TRACKER_PRO_
#define _VARIANT_GAT562_MESH_TRACKER_PRO_
// led pin 2 (blue), see https://github.com/meshtastic/firmware/blob/master/src/mesh/NodeDB.cpp#L723
#define RAK4630
/** Master clock frequency */
#define VARIANT_MCK (64000000ul)
#define USE_LFXO // Board uses 32khz crystal for LF
// define USE_LFRC // Board uses RC for LF
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#include "WVariant.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
// Number of pins defined in PinDescription array
#define PINS_COUNT (48)
#define NUM_DIGITAL_PINS (48)
#define NUM_ANALOG_INPUTS (6)
#define NUM_ANALOG_OUTPUTS (0)
// LEDs
#define PIN_LED1 (35)
#define PIN_LED2 (36)
#define LED_BUILTIN PIN_LED1
#define LED_CONN PIN_LED2
#define LED_GREEN PIN_LED1
#define LED_BLUE PIN_LED2
#define LED_STATE_ON 1 // State when LED is litted
/*
* Buttons
*/
#define CANCEL_BUTTON_PIN 9
#define BUTTON_NEED_PULLUP
#define CANCEL_BUTTON_ACTIVE_LOW true
#define CANCEL_BUTTON_ACTIVE_PULLUP false
/*
* Analog pins
*/
#define PIN_A0 (5)
#define PIN_A1 (31)
#define PIN_A2 (28)
#define PIN_A3 (29)
#define PIN_A4 (30)
#define PIN_A5 (31)
#define PIN_A6 (0xff)
#define PIN_A7 (0xff)
static const uint8_t A0 = PIN_A0;
static const uint8_t A1 = PIN_A1;
static const uint8_t A2 = PIN_A2;
static const uint8_t A3 = PIN_A3;
static const uint8_t A4 = PIN_A4;
static const uint8_t A5 = PIN_A5;
static const uint8_t A6 = PIN_A6;
static const uint8_t A7 = PIN_A7;
#define ADC_RESOLUTION 14
// Other pins
#define PIN_AREF (2)
#define PIN_NFC1 (9)
#define PIN_NFC2 (10)
static const uint8_t AREF = PIN_AREF;
/*
* Serial interfaces
*/
#define PIN_SERIAL1_RX (15)
#define PIN_SERIAL1_TX (16)
// Connected to Jlink CDC
#define PIN_SERIAL2_RX (8)
#define PIN_SERIAL2_TX (6)
/*
* SPI Interfaces
*/
#define SPI_INTERFACES_COUNT 2
#define PIN_SPI_MISO (45)
#define PIN_SPI_MOSI (44)
#define PIN_SPI_SCK (43)
#define PIN_SPI1_MISO (29) // (0 + 29)
#define PIN_SPI1_MOSI (30) // (0 + 30)
#define PIN_SPI1_SCK (3) // (0 + 3)
static const uint8_t SS = 42;
static const uint8_t MOSI = PIN_SPI_MOSI;
static const uint8_t MISO = PIN_SPI_MISO;
static const uint8_t SCK = PIN_SPI_SCK;
/*
* eink display pins
*/
// #define PIN_EINK_CS (0 + 26)
// #define PIN_EINK_BUSY (0 + 4)
// #define PIN_EINK_DC (0 + 17)
// #define PIN_EINK_RES (-1)
// #define PIN_EINK_SCLK (0 + 3)
// #define PIN_EINK_MOSI (0 + 30) // also called SDI
// #define USE_EINK
// Display - OLED connected via I2C
#define HAS_SCREEN 1
#define USE_SSD1306
// RAKRGB
// #define HAS_NCP5623
/*
* Wire Interfaces
*/
#define WIRE_INTERFACES_COUNT 1
#define PIN_WIRE_SDA (13)
#define PIN_WIRE_SCL (14)
// QSPI Pins
#define PIN_QSPI_SCK 3
#define PIN_QSPI_CS 26
#define PIN_QSPI_IO0 30
#define PIN_QSPI_IO1 29
#define PIN_QSPI_IO2 28
#define PIN_QSPI_IO3 2
// On-board QSPI Flash
#define EXTERNAL_FLASH_DEVICES IS25LP080D
#define EXTERNAL_FLASH_USE_QSPI
/* @note RAK5005-O GPIO mapping to RAK4631 GPIO ports
RAK5005-O <-> nRF52840
IO1 <-> P0.17 (Arduino GPIO number 17)
IO2 <-> P1.02 (Arduino GPIO number 34)
IO3 <-> P0.21 (Arduino GPIO number 21)
IO4 <-> P0.04 (Arduino GPIO number 4)
IO5 <-> P0.09 (Arduino GPIO number 9)
IO6 <-> P0.10 (Arduino GPIO number 10)
IO7 <-> P0.28 (Arduino GPIO number 28)
SW1 <-> P0.01 (Arduino GPIO number 1)
A0 <-> P0.04/AIN2 (Arduino Analog A2
A1 <-> P0.31/AIN7 (Arduino Analog A7
SPI_CS <-> P0.26 (Arduino GPIO number 26)
*/
// RAK4630 LoRa module
/* Setup of the SX1262 LoRa module ( https://docs.rakwireless.com/Product-Categories/WisBlock/RAK4631/Datasheet/ )
P1.10 NSS SPI NSS (Arduino GPIO number 42)
P1.11 SCK SPI CLK (Arduino GPIO number 43)
P1.12 MOSI SPI MOSI (Arduino GPIO number 44)
P1.13 MISO SPI MISO (Arduino GPIO number 45)
P1.14 BUSY BUSY signal (Arduino GPIO number 46)
P1.15 DIO1 DIO1 event interrupt (Arduino GPIO number 47)
P1.06 NRESET NRESET manual reset of the SX1262 (Arduino GPIO number 38)
Important for successful SX1262 initialization:
* Setup DIO2 to control the antenna switch
* Setup DIO3 to control the TCXO power supply
* Setup the SX1262 to use it's DCDC regulator and not the LDO
* RAK4630 schematics show GPIO P1.07 connected to the antenna switch, but it should not be initialized, as DIO2 will do the
control of the antenna switch
SO GPIO 39/TXEN MAY NOT BE DEFINED FOR SUCCESSFUL OPERATION OF THE SX1262 - TG
*/
// configure the SET pin on the RAK12039 sensor board to disable the sensor while not reading
// air quality telemetry. PIN_NFC2 doesn't seem to be used anywhere else in the codebase, but if
// you're having problems with your node behaving weirdly when a RAK12039 board isn't connected,
// try disabling this.
// #define PMSA003I_ENABLE_PIN PIN_NFC2
// #define DETECTION_SENSOR_EN 4
#define USE_SX1262
#define SX126X_CS (42)
#define SX126X_DIO1 (47)
#define SX126X_BUSY (46)
#define SX126X_RESET (38)
// #define SX126X_TXEN (39)
// #define SX126X_RXEN (37)
#define SX126X_POWER_EN (37)
// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
// Testing USB detection
#define NRF_APM
// enables 3.3V periphery like GPS or IO Module
// Do not toggle this for GPS power savings
#define PIN_3V3_EN (34)
// RAK1910 GPS module
// If using the wisblock GPS module and pluged into Port A on WisBlock base
// IO1 is hooked to PPS (pin 12 on header) = gpio 17
// IO2 is hooked to GPS RESET = gpio 34, but it can not be used to this because IO2 is ALSO used to control 3V3_S power (1 is on).
// Therefore must be 1 to keep peripherals powered
// Power is on the controllable 3V3_S rail
// #define PIN_GPS_RESET (34)
// #define PIN_GPS_EN PIN_3V3_EN
#define PIN_GPS_PPS (17) // Pulse per second input from the GPS
#define GPS_BAUDRATE 9600
#define GPS_RX_PIN PIN_SERIAL1_RX
#define GPS_TX_PIN PIN_SERIAL1_TX
// Define pin to enable GPS toggle (set GPIO to LOW) via user button triple press
// RAK12002 RTC Module
// #define RV3028_RTC (uint8_t)0b1010010
// RAK18001 Buzzer in Slot C
// #define PIN_BUZZER 21 // IO3 is PWM2
// NEW: set this via protobuf instead!
// Battery
// The battery sense is hooked to pin A0 (5)
#define BATTERY_PIN PIN_A0
// and has 12 bit resolution
#define BATTERY_SENSE_RESOLUTION_BITS 12
#define BATTERY_SENSE_RESOLUTION 4096.0
#undef AREF_VOLTAGE
#define AREF_VOLTAGE 3.0
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
#define ADC_MULTIPLIER 1.73
// #define HAS_RTC 1
// #define HAS_ETHERNET 1
// #define RAK_4631 1
// #define PIN_ETHERNET_RESET 21
// #define PIN_ETHERNET_SS PIN_EINK_CS
// #define ETH_SPI_PORT SPI1
// #define AQ_SET_PIN 10
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
// Trackball Configuration
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#define CANNED_MESSAGE_MODULE_ENABLE 1
#define CANNED_MESSAGE_ADD_CONFIRMATION 1
// Trackball pins
#define HAS_TRACKBALL 1
#define TB_LEFT 30 // P0.30
#define TB_DOWN 4 // P0.04
#define TB_RIGHT 31 // P0.31
#define TB_UP 28 // P0.28
#define TB_PRESS 26 // P0.26 (SELECT)
#define TB_DIRECTION FALLING
#ifdef __cplusplus
}
#endif
/*----------------------------------------------------------------------------
* Arduino objects - C++ only
*----------------------------------------------------------------------------*/
#endif

View File

@@ -4,7 +4,15 @@ extends = nrf52840_base
board = meshtiny
board_level = extra
build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/meshtiny -D MESHTINY
-D USE_PIN_BUZZER
-DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
-DRADIOLIB_EXCLUDE_SX128X=1
-DRADIOLIB_EXCLUDE_SX127X=1
-DRADIOLIB_EXCLUDE_LR11X0=1
-D INPUTDRIVER_ENCODER_TYPE=2
-D INPUTDRIVER_ENCODER_UP=4
-D INPUTDRIVER_ENCODER_DOWN=26
-D INPUTDRIVER_ENCODER_BTN=28
-D USE_PIN_BUZZER=PIN_BUZZER
-D MESHTASTIC_EXCLUDE_GPS=1
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/meshtiny>
lib_deps =

View File

@@ -21,6 +21,8 @@
#define MESHTINY
// #define RAK4630
/** Master clock frequency */
#define VARIANT_MCK (64000000ul)
@@ -74,10 +76,11 @@ extern "C" {
* Buttons
*/
#define CANCEL_BUTTON_PIN 9
#define PIN_BUTTON1 9
#define BUTTON_NEED_PULLUP
#define CANCEL_BUTTON_ACTIVE_LOW true
#define CANCEL_BUTTON_ACTIVE_PULLUP false
#define PIN_BUTTON2 12
#define PIN_BUTTON3 24
#define PIN_BUTTON4 25
/*
* Analog pins