mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-15 07:12:34 +00:00
Compare commits
3 Commits
self-hoste
...
c6l-fixes
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee89bdd569 | ||
|
|
cc1c568916 | ||
|
|
0fecbbf86b |
4
.github/workflows/build_firmware.yml
vendored
4
.github/workflows/build_firmware.yml
vendored
@@ -18,7 +18,7 @@ permissions: read-all
|
|||||||
jobs:
|
jobs:
|
||||||
pio-build:
|
pio-build:
|
||||||
name: build-${{ inputs.platform }}
|
name: build-${{ inputs.platform }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build ${{ inputs.platform }}
|
- name: Build ${{ inputs.platform }}
|
||||||
id: build
|
id: build
|
||||||
uses: fifieldt/gh-action-firmware@fifield
|
uses: meshtastic/gh-action-firmware@main
|
||||||
with:
|
with:
|
||||||
pio_platform: ${{ inputs.platform }}
|
pio_platform: ${{ inputs.platform }}
|
||||||
pio_env: ${{ inputs.pio_env }}
|
pio_env: ${{ inputs.pio_env }}
|
||||||
|
|||||||
2
.github/workflows/build_one_arch.yml
vendored
2
.github/workflows/build_one_arch.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
setup:
|
setup:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v6
|
||||||
|
|||||||
2
.github/workflows/build_one_target.yml
vendored
2
.github/workflows/build_one_target.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
|||||||
- rp2350
|
- rp2350
|
||||||
- stm32
|
- stm32
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v6
|
||||||
|
|||||||
7
.github/workflows/main_matrix.yml
vendored
7
.github/workflows/main_matrix.yml
vendored
@@ -19,7 +19,6 @@ on:
|
|||||||
- master
|
- master
|
||||||
- develop
|
- develop
|
||||||
- event/*
|
- event/*
|
||||||
- self-hosted-testing
|
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- "**.md"
|
- "**.md"
|
||||||
#- "**.yml"
|
#- "**.yml"
|
||||||
@@ -42,7 +41,7 @@ jobs:
|
|||||||
- rp2350
|
- rp2350
|
||||||
- stm32
|
- stm32
|
||||||
- check
|
- check
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v6
|
||||||
@@ -50,10 +49,6 @@ jobs:
|
|||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
cache: pip
|
cache: pip
|
||||||
- run: pip install -U platformio
|
- 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
|
- name: Generate matrix
|
||||||
id: jsonStep
|
id: jsonStep
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/merge_queue.yml
vendored
2
.github/workflows/merge_queue.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
- rp2350
|
- rp2350
|
||||||
- stm32
|
- stm32
|
||||||
- check
|
- check
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v6
|
||||||
|
|||||||
2
.github/workflows/pr_enforce_labels.yml
vendored
2
.github/workflows/pr_enforce_labels.yml
vendored
@@ -10,7 +10,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-label:
|
check-label:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Check for PR labels
|
- name: Check for PR labels
|
||||||
uses: actions/github-script@v8
|
uses: actions/github-script@v8
|
||||||
|
|||||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -10,5 +10,10 @@
|
|||||||
},
|
},
|
||||||
"[powershell]": {
|
"[powershell]": {
|
||||||
"editor.defaultFormatter": "ms-vscode.powershell"
|
"editor.defaultFormatter": "ms-vscode.powershell"
|
||||||
|
},
|
||||||
|
"files.associations": {
|
||||||
|
"deque": "cpp",
|
||||||
|
"string": "cpp",
|
||||||
|
"vector": "cpp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
{
|
|
||||||
"build": {
|
|
||||||
"arduino": {
|
|
||||||
"ldscript": "esp32s3_out.ld",
|
|
||||||
"partitions": "default_16MB.csv",
|
|
||||||
"memory_type": "qio_qspi"
|
|
||||||
},
|
|
||||||
"core": "esp32",
|
|
||||||
"extra_flags": [
|
|
||||||
"-DBOARD_HAS_PSRAM",
|
|
||||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
|
||||||
"-DARDUINO_USB_MODE=0",
|
|
||||||
"-DARDUINO_RUNNING_CORE=1",
|
|
||||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
|
||||||
],
|
|
||||||
"f_cpu": "240000000L",
|
|
||||||
"f_flash": "80000000L",
|
|
||||||
"flash_mode": "qio",
|
|
||||||
"psram_type": "qspi",
|
|
||||||
"hwids": [["0x303A", "0x1001"]],
|
|
||||||
"mcu": "esp32s3",
|
|
||||||
"variant": "heltec_v4"
|
|
||||||
},
|
|
||||||
"connectivity": ["wifi", "bluetooth", "lora"],
|
|
||||||
"debug": {
|
|
||||||
"default_tool": "esp-builtin",
|
|
||||||
"onboard_tools": ["esp-builtin"],
|
|
||||||
"openocd_target": "esp32s3.cfg"
|
|
||||||
},
|
|
||||||
"frameworks": ["arduino", "espidf"],
|
|
||||||
"name": "heltec_wifi_lora_32 v4 (16 MB FLASH, 2 MB PSRAM)",
|
|
||||||
"upload": {
|
|
||||||
"flash_size": "16MB",
|
|
||||||
"maximum_ram_size": 2097152,
|
|
||||||
"maximum_size": 16777216,
|
|
||||||
"use_1200bps_touch": true,
|
|
||||||
"wait_for_upload_port": true,
|
|
||||||
"require_upload_port": true,
|
|
||||||
"speed": 921600
|
|
||||||
},
|
|
||||||
"url": "https://heltec.org/",
|
|
||||||
"vendor": "heltec"
|
|
||||||
}
|
|
||||||
45
debian/changelog
vendored
45
debian/changelog
vendored
@@ -3,47 +3,4 @@ meshtasticd (2.7.10.0) UNRELEASED; urgency=medium
|
|||||||
* Initial packaging
|
* Initial packaging
|
||||||
* Version 2.5.19
|
* Version 2.5.19
|
||||||
|
|
||||||
[ ]
|
-- Austin Lane <vidplace7@gmail.com> Thu, 02 Jan 2025 12:00:00 +0000
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ Ubuntu ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
[ ]
|
|
||||||
* GitHub Actions Automatic version bump
|
|
||||||
|
|
||||||
-- <github-actions[bot]@users.noreply.github.com> Thu, 18 Sep 2025 22:11:37 +0000
|
|
||||||
|
|||||||
5
debian/ci_changelog.sh
vendored
5
debian/ci_changelog.sh
vendored
@@ -1,8 +1,7 @@
|
|||||||
#!/usr/bin/bash
|
#!/usr/bin/bash
|
||||||
export DEBFULLNAME="GitHub Actions"
|
|
||||||
export DEBEMAIL="github-actions[bot]@users.noreply.github.com"
|
export DEBEMAIL="github-actions[bot]@users.noreply.github.com"
|
||||||
PKG_VERSION=$(python3 bin/buildinfo.py short)
|
PKG_VERSION=$(python3 bin/buildinfo.py short)
|
||||||
|
|
||||||
dch --newversion "$PKG_VERSION.0" \
|
dch --newversion "$PKG_VERSION.0" \
|
||||||
--distribution unstable \
|
--distribution UNRELEASED \
|
||||||
"Version $PKG_VERSION"
|
"GitHub Actions Automatic version bump"
|
||||||
|
|||||||
2
debian/meshtasticd.postinst
vendored
2
debian/meshtasticd.postinst
vendored
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
# postinst script for meshtasticd
|
# postinst script for meshtasticd
|
||||||
#
|
#
|
||||||
# see: dh_installdeb(1)
|
# see: dh_installdeb(1)
|
||||||
|
|||||||
2
debian/meshtasticd.postrm
vendored
2
debian/meshtasticd.postrm
vendored
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
# postrm script for meshtasticd
|
# postrm script for meshtasticd
|
||||||
#
|
#
|
||||||
# see: dh_installdeb(1)
|
# see: dh_installdeb(1)
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ lib_deps =
|
|||||||
[radiolib_base]
|
[radiolib_base]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
||||||
jgromes/RadioLib@7.3.0
|
jgromes/RadioLib@7.2.1
|
||||||
|
|
||||||
[device-ui_base]
|
[device-ui_base]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
"replacements:all",
|
"replacements:all",
|
||||||
"workarounds:all"
|
"workarounds:all"
|
||||||
],
|
],
|
||||||
"baseBranchPatterns": ["master"],
|
|
||||||
"forkProcessing": "enabled",
|
"forkProcessing": "enabled",
|
||||||
"ignoreDeps": [
|
"ignoreDeps": [
|
||||||
"protobufs"
|
"protobufs"
|
||||||
|
|||||||
@@ -28,14 +28,11 @@ int BuzzerFeedbackThread::handleInputEvent(const InputEvent *event)
|
|||||||
case INPUT_BROKER_USER_PRESS:
|
case INPUT_BROKER_USER_PRESS:
|
||||||
case INPUT_BROKER_ALT_PRESS:
|
case INPUT_BROKER_ALT_PRESS:
|
||||||
case INPUT_BROKER_SELECT:
|
case INPUT_BROKER_SELECT:
|
||||||
case INPUT_BROKER_SELECT_LONG:
|
|
||||||
playBeep(); // Confirmation feedback
|
playBeep(); // Confirmation feedback
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case INPUT_BROKER_UP:
|
case INPUT_BROKER_UP:
|
||||||
case INPUT_BROKER_UP_LONG:
|
|
||||||
case INPUT_BROKER_DOWN:
|
case INPUT_BROKER_DOWN:
|
||||||
case INPUT_BROKER_DOWN_LONG:
|
|
||||||
case INPUT_BROKER_LEFT:
|
case INPUT_BROKER_LEFT:
|
||||||
case INPUT_BROKER_RIGHT:
|
case INPUT_BROKER_RIGHT:
|
||||||
playChirp(); // Navigation feedback
|
playChirp(); // Navigation feedback
|
||||||
|
|||||||
@@ -216,44 +216,6 @@ void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t
|
|||||||
ui->update();
|
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)
|
static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
uint8_t module_frame;
|
uint8_t module_frame;
|
||||||
@@ -763,19 +725,13 @@ int32_t Screen::runOnce()
|
|||||||
handleSetOn(false);
|
handleSetOn(false);
|
||||||
break;
|
break;
|
||||||
case Cmd::ON_PRESS:
|
case Cmd::ON_PRESS:
|
||||||
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
handleOnPress();
|
||||||
handleOnPress();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case Cmd::SHOW_PREV_FRAME:
|
case Cmd::SHOW_PREV_FRAME:
|
||||||
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
handleShowPrevFrame();
|
||||||
handleShowPrevFrame();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case Cmd::SHOW_NEXT_FRAME:
|
case Cmd::SHOW_NEXT_FRAME:
|
||||||
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
handleShowNextFrame();
|
||||||
handleShowNextFrame();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case Cmd::START_ALERT_FRAME: {
|
case Cmd::START_ALERT_FRAME: {
|
||||||
showingBootScreen = false; // this should avoid the edge case where an alert triggers before the boot screen goes away
|
showingBootScreen = false; // this should avoid the edge case where an alert triggers before the boot screen goes away
|
||||||
@@ -797,9 +753,7 @@ int32_t Screen::runOnce()
|
|||||||
NotificationRenderer::pauseBanner = false;
|
NotificationRenderer::pauseBanner = false;
|
||||||
case Cmd::STOP_BOOT_SCREEN:
|
case Cmd::STOP_BOOT_SCREEN:
|
||||||
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
|
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;
|
break;
|
||||||
case Cmd::NOOP:
|
case Cmd::NOOP:
|
||||||
break;
|
break;
|
||||||
@@ -835,7 +789,6 @@ int32_t Screen::runOnce()
|
|||||||
if (showingNormalScreen) {
|
if (showingNormalScreen) {
|
||||||
// standard screen loop handling here
|
// standard screen loop handling here
|
||||||
if (config.display.auto_screen_carousel_secs > 0 &&
|
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)) {
|
!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
|
// If an E-Ink display struggles with fast refresh, force carousel to use full refresh instead
|
||||||
@@ -926,11 +879,6 @@ void Screen::setScreensaverFrames(FrameCallback einkScreensaver)
|
|||||||
// Called when a frame should be added / removed, or custom frames should be cleared
|
// Called when a frame should be added / removed, or custom frames should be cleared
|
||||||
void Screen::setFrames(FrameFocus focus)
|
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 originalPosition = ui->getUiState()->currentFrame;
|
||||||
uint8_t previousFrameCount = framesetInfo.frameCount;
|
uint8_t previousFrameCount = framesetInfo.frameCount;
|
||||||
FramesetInfo fsi; // Location of specific frames, for applying focus parameter
|
FramesetInfo fsi; // Location of specific frames, for applying focus parameter
|
||||||
@@ -1037,7 +985,7 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
if (!hiddenFrames.chirpy) {
|
if (!hiddenFrames.chirpy) {
|
||||||
fsi.positions.chirpy = numframes;
|
fsi.positions.chirpy = numframes;
|
||||||
normalFrames[numframes++] = graphics::DebugRenderer::drawChirpy;
|
normalFrames[numframes++] = graphics::DebugRenderer::drawChirpy;
|
||||||
indicatorIcons.push_back(chirpy_small);
|
indicatorIcons.push_back(small_chirpy);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
|
#if HAS_WIFI && !defined(ARCH_PORTDUINO)
|
||||||
@@ -1299,8 +1247,7 @@ void Screen::blink()
|
|||||||
delay(50);
|
delay(50);
|
||||||
count = count - 1;
|
count = count - 1;
|
||||||
}
|
}
|
||||||
// The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in
|
// The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in OLEDDisplay.
|
||||||
// OLEDDisplay.
|
|
||||||
dispdev->setBrightness(brightness);
|
dispdev->setBrightness(brightness);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1489,11 +1436,6 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
|||||||
// Triggered by MeshModules
|
// Triggered by MeshModules
|
||||||
int Screen::handleUIFrameEvent(const UIFrameEvent *event)
|
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) {
|
if (showingNormalScreen) {
|
||||||
// Regenerate the frameset, potentially honoring a module's internal requestFocus() call
|
// Regenerate the frameset, potentially honoring a module's internal requestFocus() call
|
||||||
if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET)
|
if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET)
|
||||||
@@ -1516,16 +1458,6 @@ int Screen::handleInputEvent(const InputEvent *event)
|
|||||||
if (!screenOn)
|
if (!screenOn)
|
||||||
return 0;
|
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.
|
#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, 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
|
EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
|
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
|
||||||
namespace graphics
|
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 {
|
struct BannerOverlayOptions {
|
||||||
const char *message;
|
const char *message;
|
||||||
@@ -315,8 +315,6 @@ class Screen : public concurrency::OSThread
|
|||||||
|
|
||||||
void showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback);
|
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 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)
|
void requestMenu(graphics::menuHandler::screenMenus menuToShow)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -8,7 +8,6 @@
|
|||||||
#include "gps/RTC.h"
|
#include "gps/RTC.h"
|
||||||
#include "graphics/ScreenFonts.h"
|
#include "graphics/ScreenFonts.h"
|
||||||
#include "graphics/SharedUIDisplay.h"
|
#include "graphics/SharedUIDisplay.h"
|
||||||
#include "graphics/draw/UIRenderer.h"
|
|
||||||
#include "graphics/emotes.h"
|
#include "graphics/emotes.h"
|
||||||
#include "graphics/images.h"
|
#include "graphics/images.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
|||||||
@@ -691,7 +691,6 @@ void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int1
|
|||||||
textX = (display->getWidth() / 2) - textX_offset - (display->getStringWidth("World!") / 2);
|
textX = (display->getWidth() / 2) - textX_offset - (display->getStringWidth("World!") / 2);
|
||||||
display->drawString(textX, getTextPositions(display)[line++], "World!");
|
display->drawString(textX, getTextPositions(display)[line++], "World!");
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace DebugRenderer
|
} // namespace DebugRenderer
|
||||||
} // namespace graphics
|
} // namespace graphics
|
||||||
#endif
|
#endif
|
||||||
@@ -33,6 +33,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
|
|
||||||
// System screen display
|
// System screen display
|
||||||
void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
|
||||||
// Chirpy screen display
|
// Chirpy screen display
|
||||||
void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
} // namespace DebugRenderer
|
} // namespace DebugRenderer
|
||||||
|
|||||||
@@ -10,10 +10,7 @@
|
|||||||
#include "graphics/Screen.h"
|
#include "graphics/Screen.h"
|
||||||
#include "graphics/SharedUIDisplay.h"
|
#include "graphics/SharedUIDisplay.h"
|
||||||
#include "graphics/draw/UIRenderer.h"
|
#include "graphics/draw/UIRenderer.h"
|
||||||
#include "input/RotaryEncoderInterruptImpl1.h"
|
|
||||||
#include "input/UpDownInterruptImpl1.h"
|
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "mesh/MeshTypes.h"
|
|
||||||
#include "modules/AdminModule.h"
|
#include "modules/AdminModule.h"
|
||||||
#include "modules/CannedMessageModule.h"
|
#include "modules/CannedMessageModule.h"
|
||||||
#include "modules/KeyVerificationModule.h"
|
#include "modules/KeyVerificationModule.h"
|
||||||
@@ -31,21 +28,19 @@ uint8_t test_count = 0;
|
|||||||
|
|
||||||
void menuHandler::loraMenu()
|
void menuHandler::loraMenu()
|
||||||
{
|
{
|
||||||
static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "LoRa Region"};
|
static const char *optionsArray[] = {"Back", "Region Picker", "Device Role"};
|
||||||
enum optionsNumbers { Back = 0, device_role_picker = 1, radio_preset_picker = 2, lora_picker = 3 };
|
enum optionsNumbers { Back = 0, lora_picker = 1, device_role_picker = 2 };
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
bannerOptions.message = "LoRa Actions";
|
bannerOptions.message = "LoRa Actions";
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsCount = 4;
|
bannerOptions.optionsCount = 3;
|
||||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||||
if (selected == Back) {
|
if (selected == Back) {
|
||||||
// No action
|
// No action
|
||||||
} else if (selected == device_role_picker) {
|
|
||||||
menuHandler::menuQueue = menuHandler::device_role_picker;
|
|
||||||
} else if (selected == radio_preset_picker) {
|
|
||||||
menuHandler::menuQueue = menuHandler::radio_preset_picker;
|
|
||||||
} else if (selected == lora_picker) {
|
} else if (selected == lora_picker) {
|
||||||
menuHandler::menuQueue = menuHandler::lora_picker;
|
menuHandler::menuQueue = menuHandler::lora_picker;
|
||||||
|
} else if (selected == device_role_picker) {
|
||||||
|
menuHandler::menuQueue = menuHandler::device_role_picker;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
screen->showOverlayBanner(bannerOptions);
|
screen->showOverlayBanner(bannerOptions);
|
||||||
@@ -182,53 +177,6 @@ void menuHandler::DeviceRolePicker()
|
|||||||
screen->showOverlayBanner(bannerOptions);
|
screen->showOverlayBanner(bannerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
void menuHandler::RadioPresetPicker()
|
|
||||||
{
|
|
||||||
static const char *optionsArray[] = {"Back", "LongSlow", "LongModerate", "LongFast", "MediumSlow",
|
|
||||||
"MediumFast", "ShortSlow", "ShortFast", "ShortTurbo"};
|
|
||||||
enum optionsNumbers {
|
|
||||||
Back = 0,
|
|
||||||
radiopreset_LongSlow = 1,
|
|
||||||
radiopreset_LongModerate = 2,
|
|
||||||
radiopreset_LongFast = 3,
|
|
||||||
radiopreset_MediumSlow = 4,
|
|
||||||
radiopreset_MediumFast = 5,
|
|
||||||
radiopreset_ShortSlow = 6,
|
|
||||||
radiopreset_ShortFast = 7,
|
|
||||||
radiopreset_ShortTurbo = 8
|
|
||||||
};
|
|
||||||
BannerOverlayOptions bannerOptions;
|
|
||||||
bannerOptions.message = "Radio Preset";
|
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
|
||||||
bannerOptions.optionsCount = 9;
|
|
||||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
|
||||||
if (selected == Back) {
|
|
||||||
menuHandler::menuQueue = menuHandler::lora_Menu;
|
|
||||||
screen->runNow();
|
|
||||||
return;
|
|
||||||
} else if (selected == radiopreset_LongSlow) {
|
|
||||||
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW;
|
|
||||||
} else if (selected == radiopreset_LongModerate) {
|
|
||||||
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE;
|
|
||||||
} else if (selected == radiopreset_LongFast) {
|
|
||||||
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
|
|
||||||
} else if (selected == radiopreset_MediumSlow) {
|
|
||||||
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW;
|
|
||||||
} else if (selected == radiopreset_MediumFast) {
|
|
||||||
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST;
|
|
||||||
} else if (selected == radiopreset_ShortSlow) {
|
|
||||||
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW;
|
|
||||||
} else if (selected == radiopreset_ShortFast) {
|
|
||||||
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST;
|
|
||||||
} else if (selected == radiopreset_ShortTurbo) {
|
|
||||||
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO;
|
|
||||||
}
|
|
||||||
service->reloadConfig(SEGMENT_CONFIG);
|
|
||||||
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
|
|
||||||
};
|
|
||||||
screen->showOverlayBanner(bannerOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
void menuHandler::TwelveHourPicker()
|
void menuHandler::TwelveHourPicker()
|
||||||
{
|
{
|
||||||
static const char *optionsArray[] = {"Back", "12-hour", "24-hour"};
|
static const char *optionsArray[] = {"Back", "12-hour", "24-hour"};
|
||||||
@@ -1527,9 +1475,6 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
|||||||
case device_role_picker:
|
case device_role_picker:
|
||||||
DeviceRolePicker();
|
DeviceRolePicker();
|
||||||
break;
|
break;
|
||||||
case radio_preset_picker:
|
|
||||||
RadioPresetPicker();
|
|
||||||
break;
|
|
||||||
case no_timeout_lora_picker:
|
case no_timeout_lora_picker:
|
||||||
LoraRegionPicker(0);
|
LoraRegionPicker(0);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ class menuHandler
|
|||||||
lora_Menu,
|
lora_Menu,
|
||||||
lora_picker,
|
lora_picker,
|
||||||
device_role_picker,
|
device_role_picker,
|
||||||
radio_preset_picker,
|
|
||||||
no_timeout_lora_picker,
|
no_timeout_lora_picker,
|
||||||
TZ_picker,
|
TZ_picker,
|
||||||
twelve_hour_picker,
|
twelve_hour_picker,
|
||||||
@@ -51,7 +50,6 @@ class menuHandler
|
|||||||
static void LoraRegionPicker(uint32_t duration = 30000);
|
static void LoraRegionPicker(uint32_t duration = 30000);
|
||||||
static void loraMenu();
|
static void loraMenu();
|
||||||
static void DeviceRolePicker();
|
static void DeviceRolePicker();
|
||||||
static void RadioPresetPicker();
|
|
||||||
static void handleMenuSwitch(OLEDDisplay *display);
|
static void handleMenuSwitch(OLEDDisplay *display);
|
||||||
static void showConfirmationBanner(const char *message, std::function<void()> onConfirm);
|
static void showConfirmationBanner(const char *message, std::function<void()> onConfirm);
|
||||||
static void clockMenu();
|
static void clockMenu();
|
||||||
@@ -85,8 +83,8 @@ class menuHandler
|
|||||||
static void notificationsMenu();
|
static void notificationsMenu();
|
||||||
static void screenOptionsMenu();
|
static void screenOptionsMenu();
|
||||||
static void powerMenu();
|
static void powerMenu();
|
||||||
static void FrameToggles_menu();
|
|
||||||
static void textMessageMenu();
|
static void textMessageMenu();
|
||||||
|
static void FrameToggles_menu();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void saveUIConfig();
|
static void saveUIConfig();
|
||||||
|
|||||||
@@ -7,18 +7,10 @@
|
|||||||
#include "graphics/ScreenFonts.h"
|
#include "graphics/ScreenFonts.h"
|
||||||
#include "graphics/SharedUIDisplay.h"
|
#include "graphics/SharedUIDisplay.h"
|
||||||
#include "graphics/images.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 "main.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#if HAS_TRACKBALL
|
|
||||||
#include "input/TrackballInterruptImpl1.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
#include "esp_task_wdt.h"
|
#include "esp_task_wdt.h"
|
||||||
@@ -26,11 +18,6 @@
|
|||||||
|
|
||||||
using namespace meshtastic;
|
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
|
// External references to global variables from Screen.cpp
|
||||||
extern std::vector<std::string> functionSymbol;
|
extern std::vector<std::string> functionSymbol;
|
||||||
extern std::string functionSymbolString;
|
extern std::string functionSymbolString;
|
||||||
@@ -51,8 +38,6 @@ bool NotificationRenderer::pauseBanner = false;
|
|||||||
notificationTypeEnum NotificationRenderer::current_notification_type = notificationTypeEnum::none;
|
notificationTypeEnum NotificationRenderer::current_notification_type = notificationTypeEnum::none;
|
||||||
uint32_t NotificationRenderer::numDigits = 0;
|
uint32_t NotificationRenderer::numDigits = 0;
|
||||||
uint32_t NotificationRenderer::currentNumber = 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)
|
uint32_t pow_of_10(uint32_t n)
|
||||||
{
|
{
|
||||||
@@ -104,33 +89,14 @@ void NotificationRenderer::resetBanner()
|
|||||||
|
|
||||||
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
|
void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||||
{
|
{
|
||||||
// Handle text_input notifications first - they have their own timeout/banner logic
|
if (!isOverlayBannerShowing() && alertBannerMessage[0] != '\0')
|
||||||
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) {
|
|
||||||
resetBanner();
|
resetBanner();
|
||||||
}
|
if (!isOverlayBannerShowing() || pauseBanner)
|
||||||
|
|
||||||
// Exit if no banner is showing or banner is paused
|
|
||||||
if (!isOverlayBannerShowing() || pauseBanner) {
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
switch (current_notification_type) {
|
switch (current_notification_type) {
|
||||||
case notificationTypeEnum::none:
|
case notificationTypeEnum::none:
|
||||||
// Do nothing - no notification to display
|
// Do nothing - no notification to display
|
||||||
break;
|
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::text_banner:
|
||||||
case notificationTypeEnum::selection_picker:
|
case notificationTypeEnum::selection_picker:
|
||||||
drawAlertBannerOverlay(display, state);
|
drawAlertBannerOverlay(display, state);
|
||||||
@@ -303,9 +269,12 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
|
|||||||
if (nodeDB->getMeshNodeByIndex(i + 1)->has_user) {
|
if (nodeDB->getMeshNodeByIndex(i + 1)->has_user) {
|
||||||
std::string sanitized = sanitizeString(nodeDB->getMeshNodeByIndex(i + 1)->user.long_name);
|
std::string sanitized = sanitizeString(nodeDB->getMeshNodeByIndex(i + 1)->user.long_name);
|
||||||
strncpy(temp_name, sanitized.c_str(), sizeof(temp_name) - 1);
|
strncpy(temp_name, sanitized.c_str(), sizeof(temp_name) - 1);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
snprintf(temp_name, sizeof(temp_name), "(%04X)", (uint16_t)(nodeDB->getMeshNodeByIndex(i + 1)->num & 0xFFFF));
|
snprintf(temp_name, sizeof(temp_name), "(%04X)", (uint16_t)(nodeDB->getMeshNodeByIndex(i + 1)->num & 0xFFFF));
|
||||||
}
|
}
|
||||||
|
// make temp buffer for name
|
||||||
|
// fi
|
||||||
if (i == curSelected) {
|
if (i == curSelected) {
|
||||||
selectedNodenum = nodeDB->getMeshNodeByIndex(i + 1)->num;
|
selectedNodenum = nodeDB->getMeshNodeByIndex(i + 1)->num;
|
||||||
if (isHighResolution) {
|
if (isHighResolution) {
|
||||||
@@ -319,8 +288,7 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
|
|||||||
}
|
}
|
||||||
scratchLineBuffer[scratchLineNum][39] = '\0';
|
scratchLineBuffer[scratchLineNum][39] = '\0';
|
||||||
} else {
|
} else {
|
||||||
strncpy(scratchLineBuffer[scratchLineNum], temp_name, 39);
|
strncpy(scratchLineBuffer[scratchLineNum], temp_name, 36);
|
||||||
scratchLineBuffer[scratchLineNum][39] = '\0';
|
|
||||||
}
|
}
|
||||||
linePointers[linesShown] = scratchLineBuffer[scratchLineNum++];
|
linePointers[linesShown] = scratchLineBuffer[scratchLineNum++];
|
||||||
}
|
}
|
||||||
@@ -741,99 +709,6 @@ void NotificationRenderer::drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUi
|
|||||||
"Please be patient and do not power off.");
|
"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()
|
bool NotificationRenderer::isOverlayBannerShowing()
|
||||||
{
|
{
|
||||||
return strlen(alertBannerMessage) > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil);
|
return strlen(alertBannerMessage) > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil);
|
||||||
|
|||||||
@@ -3,9 +3,6 @@
|
|||||||
#include "OLEDDisplay.h"
|
#include "OLEDDisplay.h"
|
||||||
#include "OLEDDisplayUi.h"
|
#include "OLEDDisplayUi.h"
|
||||||
#include "graphics/Screen.h"
|
#include "graphics/Screen.h"
|
||||||
#include "graphics/VirtualKeyboard.h"
|
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
|
||||||
#define MAX_LINES 5
|
#define MAX_LINES 5
|
||||||
|
|
||||||
namespace graphics
|
namespace graphics
|
||||||
@@ -25,8 +22,6 @@ class NotificationRenderer
|
|||||||
static std::function<void(int)> alertBannerCallback;
|
static std::function<void(int)> alertBannerCallback;
|
||||||
static uint32_t numDigits;
|
static uint32_t numDigits;
|
||||||
static uint32_t currentNumber;
|
static uint32_t currentNumber;
|
||||||
static VirtualKeyboard *virtualKeyboard;
|
|
||||||
static std::function<void(const std::string &)> textInputCallback;
|
|
||||||
|
|
||||||
static bool pauseBanner;
|
static bool pauseBanner;
|
||||||
|
|
||||||
@@ -35,7 +30,6 @@ class NotificationRenderer
|
|||||||
static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state);
|
static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||||
static void drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiState *state);
|
static void drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||||
static void drawNodePicker(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],
|
static void drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[MAX_LINES + 1],
|
||||||
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth = 0);
|
uint16_t totalLines, uint8_t firstOptionToShow, uint16_t maxWidth = 0);
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,7 @@
|
|||||||
|
|
||||||
// External variables
|
// External variables
|
||||||
extern graphics::Screen *screen;
|
extern graphics::Screen *screen;
|
||||||
#if defined(M5STACK_UNITC6L)
|
|
||||||
static uint32_t lastSwitchTime = 0;
|
static uint32_t lastSwitchTime = 0;
|
||||||
#endif
|
|
||||||
namespace graphics
|
namespace graphics
|
||||||
{
|
{
|
||||||
NodeNum UIRenderer::currentFavoriteNodeNum = 0;
|
NodeNum UIRenderer::currentFavoriteNodeNum = 0;
|
||||||
@@ -128,10 +126,8 @@ void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y,
|
|||||||
strcpy(displayLine, "No GPS present");
|
strcpy(displayLine, "No GPS present");
|
||||||
display->drawString(x, y, displayLine);
|
display->drawString(x, y, displayLine);
|
||||||
} else if (!gps->getHasLock() && !config.position.fixed_position) {
|
} else if (!gps->getHasLock() && !config.position.fixed_position) {
|
||||||
if (strcmp(mode, "line1") == 0) {
|
strcpy(displayLine, "No GPS Lock");
|
||||||
strcpy(displayLine, "No GPS Lock");
|
display->drawString(x, y, displayLine);
|
||||||
display->drawString(x, y, displayLine);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude()));
|
geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude()));
|
||||||
@@ -289,9 +285,9 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
|||||||
meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex];
|
meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex];
|
||||||
if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite)
|
if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite)
|
||||||
return;
|
return;
|
||||||
|
uint32_t now = millis();
|
||||||
display->clear();
|
display->clear();
|
||||||
#if defined(M5STACK_UNITC6L)
|
#if defined(M5STACK_UNITC6L)
|
||||||
uint32_t now = millis();
|
|
||||||
if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒
|
if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒
|
||||||
{
|
{
|
||||||
display->display();
|
display->display();
|
||||||
@@ -736,6 +732,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
int textWidth = 0;
|
int textWidth = 0;
|
||||||
int nameX = 0;
|
int nameX = 0;
|
||||||
int yOffset = (isHighResolution) ? 0 : 5;
|
int yOffset = (isHighResolution) ? 0 : 5;
|
||||||
|
const char *longName = nullptr;
|
||||||
std::string longNameStr;
|
std::string longNameStr;
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
@@ -1187,7 +1184,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif // HAS_GPS
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USERPREFS_OEM_TEXT
|
#ifdef USERPREFS_OEM_TEXT
|
||||||
@@ -1280,13 +1277,14 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
const int totalWidth = (pageEnd - pageStart) * iconSize + (pageEnd - pageStart - 1) * spacing;
|
const int totalWidth = (pageEnd - pageStart) * iconSize + (pageEnd - pageStart - 1) * spacing;
|
||||||
const int xStart = (SCREEN_WIDTH - totalWidth) / 2;
|
const int xStart = (SCREEN_WIDTH - totalWidth) / 2;
|
||||||
|
|
||||||
|
// Only show bar briefly after switching frames
|
||||||
|
static uint32_t navBarLastShown = 0;
|
||||||
|
static bool cosmeticRefreshDone = false;
|
||||||
|
|
||||||
bool navBarVisible = millis() - lastFrameChangeTime <= ICON_DISPLAY_DURATION_MS;
|
bool navBarVisible = millis() - lastFrameChangeTime <= ICON_DISPLAY_DURATION_MS;
|
||||||
int y = navBarVisible ? (SCREEN_HEIGHT - iconSize - 1) : SCREEN_HEIGHT;
|
int y = navBarVisible ? (SCREEN_HEIGHT - iconSize - 1) : SCREEN_HEIGHT;
|
||||||
|
|
||||||
#if defined(USE_EINK)
|
#if defined(USE_EINK)
|
||||||
// Only show bar briefly after switching frames
|
|
||||||
static uint32_t navBarLastShown = 0;
|
|
||||||
static bool cosmeticRefreshDone = false;
|
|
||||||
static bool navBarPrevVisible = false;
|
static bool navBarPrevVisible = false;
|
||||||
|
|
||||||
if (navBarVisible && !navBarPrevVisible) {
|
if (navBarVisible && !navBarPrevVisible) {
|
||||||
|
|||||||
@@ -287,10 +287,12 @@ const uint8_t digital_icon_clock[] PROGMEM = {0b00111100, 0b01000010, 0b10000101
|
|||||||
#define analog_icon_clock_height 8
|
#define analog_icon_clock_height 8
|
||||||
const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000,
|
const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000,
|
||||||
0b00100100, 0b01000010, 0b01000010, 0b11111111};
|
0b00100100, 0b01000010, 0b01000010, 0b11111111};
|
||||||
|
#ifdef M5STACK_UNITC6L
|
||||||
|
#include "img/icon_small.xbm"
|
||||||
|
#else
|
||||||
#define chirpy_width 38
|
#define chirpy_width 38
|
||||||
#define chirpy_height 50
|
#define chirpy_height 50
|
||||||
const uint8_t chirpy[] = {
|
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,
|
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, 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,
|
0x00, 0x00, 0xe0, 0x81, 0xff, 0xff, 0x7f, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xff, 0xff, 0xff, 0xe0, 0xc1, 0xcf, 0x7f,
|
||||||
@@ -306,7 +308,7 @@ const uint8_t chirpy[] = {
|
|||||||
|
|
||||||
#define chirpy_width_hirez 76
|
#define chirpy_width_hirez 76
|
||||||
#define chirpy_height_hirez 100
|
#define chirpy_height_hirez 100
|
||||||
const uint8_t chirpy_hirez[] = {
|
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,
|
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, 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, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
|
||||||
@@ -358,11 +360,8 @@ const uint8_t chirpy_hirez[] = {
|
|||||||
|
|
||||||
#define chirpy_small_image_width 8
|
#define chirpy_small_image_width 8
|
||||||
#define chirpy_small_image_height 8
|
#define chirpy_small_image_height 8
|
||||||
const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
|
static unsigned char small_chirpy[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
|
||||||
|
|
||||||
#ifdef M5STACK_UNITC6L
|
|
||||||
#include "img/icon_small.xbm"
|
|
||||||
#else
|
|
||||||
#include "img/icon.xbm"
|
#include "img/icon.xbm"
|
||||||
#endif
|
#endif
|
||||||
static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");
|
static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning");
|
||||||
@@ -27,4 +27,90 @@ static uint8_t icon_bits[] = {
|
|||||||
0x00, 0x1c, 0x00, 0x00, 0x70, 0x00, 0x10, 0x00,
|
0x00, 0x1c, 0x00, 0x00, 0x70, 0x00, 0x10, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00 };
|
0x00, 0x00, 0x00, 0x00 };
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Chirpy image definitions for M5STACK_UNITC6L compatibility
|
||||||
|
#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, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc,
|
||||||
|
0x03, 0xe0, 0x1f, 0xff, 0xff, 0xff, 0xf8, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03,
|
||||||
|
0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0,
|
||||||
|
0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f,
|
||||||
|
0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe,
|
||||||
|
0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff,
|
||||||
|
0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f,
|
||||||
|
0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0,
|
||||||
|
0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f,
|
||||||
|
0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00,
|
||||||
|
0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc,
|
||||||
|
0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03,
|
||||||
|
0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0,
|
||||||
|
0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f,
|
||||||
|
0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe,
|
||||||
|
0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff,
|
||||||
|
0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f,
|
||||||
|
0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0,
|
||||||
|
0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f,
|
||||||
|
0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00,
|
||||||
|
0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc,
|
||||||
|
0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03,
|
||||||
|
0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0,
|
||||||
|
0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x0f, 0xfe, 0xff, 0x7f, 0xf0, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0x1f,
|
||||||
|
0xff, 0xff, 0xff, 0xf8, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x00, 0xfc, 0x03, 0xe0, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xff, 0x7f, 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, 0x86, 0x61, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x86, 0x61, 0x00,
|
||||||
|
0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x86, 0x61, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x80, 0x87, 0xe1, 0x01, 0x00,
|
||||||
|
0x00, 0xfc, 0x03, 0x00, 0x00, 0x80, 0x87, 0xe1, 0x01, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x83, 0xc1, 0x03, 0x00, 0x00,
|
||||||
|
0xfc, 0x03, 0x00, 0x00, 0xc0, 0x83, 0xc1, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xe0, 0x01, 0x80, 0x07, 0x00, 0x00, 0xfc,
|
||||||
|
0x03, 0x00, 0x00, 0xe0, 0x01, 0x80, 0x07, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x00, 0xfc, 0x03,
|
||||||
|
0x00, 0x00, 0xf0, 0x00, 0x00, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x78, 0x00, 0x00, 0x1e, 0x00, 0x00, 0xfc, 0x03, 0x00,
|
||||||
|
0x00, 0x78, 0x00, 0x00, 0x1e, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
|
||||||
|
0x3c, 0x00, 0x00, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x78, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x1e,
|
||||||
|
0x00, 0x00, 0x78, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0x00, 0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0x00,
|
||||||
|
0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x80, 0x07, 0x00, 0x00, 0xe0, 0x01, 0x00, 0xfc, 0x03, 0x00, 0x80, 0x07, 0x00, 0x00,
|
||||||
|
0xe0, 0x01, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x00, 0x00, 0xc0,
|
||||||
|
0x03, 0x00, 0xfc, 0x03, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0x07, 0x00, 0xfc, 0x03, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x80, 0x07,
|
||||||
|
0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00,
|
||||||
|
0xfc, 0x03, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0xfc, 0x03, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0xfc,
|
||||||
|
0x03, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0xfc, 0x03, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0xfc, 0x03,
|
||||||
|
0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0xfc, 0x03, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0xfc, 0x03, 0x00,
|
||||||
|
0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0xfc, 0x03, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0xfc, 0x03, 0x80, 0x07,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0xfc, 0x03, 0x80, 0x07, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x01, 0xfc, 0x03, 0xc0, 0x03, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0xc0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0xe0, 0x01, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x80, 0x07, 0xfc, 0x03, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x07, 0xfc, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x0f, 0xfc, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 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};
|
||||||
@@ -76,9 +76,6 @@ class ButtonThread : public Observable<const InputEvent *>, public concurrency::
|
|||||||
return digitalRead(buttonPin); // Most buttons are active low by default
|
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
|
// Disconnect and reconnect interrupts for light sleep
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
int beforeLightSleep(void *unused);
|
int beforeLightSleep(void *unused);
|
||||||
|
|||||||
@@ -4,9 +4,6 @@
|
|||||||
enum input_broker_event {
|
enum input_broker_event {
|
||||||
INPUT_BROKER_NONE = 0,
|
INPUT_BROKER_NONE = 0,
|
||||||
INPUT_BROKER_SELECT = 10,
|
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_UP = 17,
|
||||||
INPUT_BROKER_DOWN = 18,
|
INPUT_BROKER_DOWN = 18,
|
||||||
INPUT_BROKER_LEFT = 19,
|
INPUT_BROKER_LEFT = 19,
|
||||||
|
|||||||
@@ -8,17 +8,15 @@ RotaryEncoderInterruptBase::RotaryEncoderInterruptBase(const char *name) : concu
|
|||||||
|
|
||||||
void RotaryEncoderInterruptBase::init(
|
void RotaryEncoderInterruptBase::init(
|
||||||
uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw,
|
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) :
|
// std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)> onIntPress) :
|
||||||
void (*onIntA)(), void (*onIntB)(), void (*onIntPress)())
|
void (*onIntA)(), void (*onIntB)(), void (*onIntPress)())
|
||||||
{
|
{
|
||||||
this->_pinA = pinA;
|
this->_pinA = pinA;
|
||||||
this->_pinB = pinB;
|
this->_pinB = pinB;
|
||||||
this->_pinPress = pinPress;
|
|
||||||
this->_eventCw = eventCw;
|
this->_eventCw = eventCw;
|
||||||
this->_eventCcw = eventCcw;
|
this->_eventCcw = eventCcw;
|
||||||
this->_eventPressed = eventPressed;
|
this->_eventPressed = eventPressed;
|
||||||
this->_eventPressedLong = eventPressedLong;
|
|
||||||
|
|
||||||
bool isRAK = false;
|
bool isRAK = false;
|
||||||
#ifdef RAK_4631
|
#ifdef RAK_4631
|
||||||
@@ -27,7 +25,7 @@ void RotaryEncoderInterruptBase::init(
|
|||||||
|
|
||||||
if (!isRAK || pinPress != 0) {
|
if (!isRAK || pinPress != 0) {
|
||||||
pinMode(pinPress, INPUT_PULLUP);
|
pinMode(pinPress, INPUT_PULLUP);
|
||||||
attachInterrupt(pinPress, onIntPress, CHANGE);
|
attachInterrupt(pinPress, onIntPress, RISING);
|
||||||
}
|
}
|
||||||
if (!isRAK || this->_pinA != 0) {
|
if (!isRAK || this->_pinA != 0) {
|
||||||
pinMode(this->_pinA, INPUT_PULLUP);
|
pinMode(this->_pinA, INPUT_PULLUP);
|
||||||
@@ -48,37 +46,10 @@ int32_t RotaryEncoderInterruptBase::runOnce()
|
|||||||
InputEvent e = {};
|
InputEvent e = {};
|
||||||
e.inputEvent = INPUT_BROKER_NONE;
|
e.inputEvent = INPUT_BROKER_NONE;
|
||||||
e.source = this->_originName;
|
e.source = this->_originName;
|
||||||
unsigned long now = millis();
|
|
||||||
|
|
||||||
// Handle press long/short detection
|
|
||||||
if (this->action == ROTARY_ACTION_PRESSED) {
|
if (this->action == ROTARY_ACTION_PRESSED) {
|
||||||
bool buttonPressed = !digitalRead(_pinPress);
|
LOG_DEBUG("Rotary event Press");
|
||||||
if (!pressDetected && buttonPressed) {
|
e.inputEvent = this->_eventPressed;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (this->action == ROTARY_ACTION_CW) {
|
} else if (this->action == ROTARY_ACTION_CW) {
|
||||||
LOG_DEBUG("Rotary event CW");
|
LOG_DEBUG("Rotary event CW");
|
||||||
e.inputEvent = this->_eventCw;
|
e.inputEvent = this->_eventCw;
|
||||||
@@ -91,9 +62,7 @@ int32_t RotaryEncoderInterruptBase::runOnce()
|
|||||||
this->notifyObservers(&e);
|
this->notifyObservers(&e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pressDetected) {
|
this->action = ROTARY_ACTION_NONE;
|
||||||
this->action = ROTARY_ACTION_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return INT32_MAX;
|
return INT32_MAX;
|
||||||
}
|
}
|
||||||
@@ -101,7 +70,7 @@ int32_t RotaryEncoderInterruptBase::runOnce()
|
|||||||
void RotaryEncoderInterruptBase::intPressHandler()
|
void RotaryEncoderInterruptBase::intPressHandler()
|
||||||
{
|
{
|
||||||
this->action = ROTARY_ACTION_PRESSED;
|
this->action = ROTARY_ACTION_PRESSED;
|
||||||
setIntervalFromNow(20); // start checking for long/short
|
setIntervalFromNow(20); // TODO: this modifies a non-volatile variable!
|
||||||
}
|
}
|
||||||
|
|
||||||
void RotaryEncoderInterruptBase::intAHandler()
|
void RotaryEncoderInterruptBase::intAHandler()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class RotaryEncoderInterruptBase : public Observable<const InputEvent *>, public
|
|||||||
public:
|
public:
|
||||||
explicit RotaryEncoderInterruptBase(const char *name);
|
explicit RotaryEncoderInterruptBase(const char *name);
|
||||||
void init(uint8_t pinA, uint8_t pinB, uint8_t pinPress, input_broker_event eventCw, input_broker_event eventCcw,
|
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);
|
// std::function<void(void)> onIntA, std::function<void(void)> onIntB, std::function<void(void)> onIntPress);
|
||||||
void (*onIntA)(), void (*onIntB)(), void (*onIntPress)());
|
void (*onIntA)(), void (*onIntB)(), void (*onIntPress)());
|
||||||
void intPressHandler();
|
void intPressHandler();
|
||||||
@@ -33,22 +33,10 @@ class RotaryEncoderInterruptBase : public Observable<const InputEvent *>, public
|
|||||||
volatile RotaryEncoderInterruptBaseActionType action = ROTARY_ACTION_NONE;
|
volatile RotaryEncoderInterruptBaseActionType action = ROTARY_ACTION_NONE;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// pins and events
|
|
||||||
uint8_t _pinA = 0;
|
uint8_t _pinA = 0;
|
||||||
uint8_t _pinB = 0;
|
uint8_t _pinB = 0;
|
||||||
uint8_t _pinPress = 0;
|
|
||||||
input_broker_event _eventCw = INPUT_BROKER_NONE;
|
input_broker_event _eventCw = INPUT_BROKER_NONE;
|
||||||
input_broker_event _eventCcw = INPUT_BROKER_NONE;
|
input_broker_event _eventCcw = INPUT_BROKER_NONE;
|
||||||
input_broker_event _eventPressed = INPUT_BROKER_NONE;
|
input_broker_event _eventPressed = INPUT_BROKER_NONE;
|
||||||
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
|
|
||||||
const char *_originName;
|
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
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "RotaryEncoderInterruptImpl1.h"
|
#include "RotaryEncoderInterruptImpl1.h"
|
||||||
#include "InputBroker.h"
|
#include "InputBroker.h"
|
||||||
extern bool osk_found;
|
|
||||||
|
|
||||||
RotaryEncoderInterruptImpl1 *rotaryEncoderInterruptImpl1;
|
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 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 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 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
|
// 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::handleIntA, RotaryEncoderInterruptImpl1::handleIntB,
|
||||||
RotaryEncoderInterruptImpl1::handleIntPressed);
|
RotaryEncoderInterruptImpl1::handleIntPressed);
|
||||||
inputBroker->registerSource(this);
|
inputBroker->registerSource(this);
|
||||||
osk_found = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
#include "TrackballInterruptBase.h"
|
#include "TrackballInterruptBase.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
extern bool osk_found;
|
|
||||||
|
|
||||||
TrackballInterruptBase::TrackballInterruptBase(const char *name) : concurrency::OSThread(name), _originName(name) {}
|
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,
|
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 eventDown, input_broker_event eventUp, input_broker_event eventLeft,
|
||||||
input_broker_event eventRight, input_broker_event eventPressed,
|
input_broker_event eventRight, input_broker_event eventPressed, void (*onIntDown)(),
|
||||||
input_broker_event eventPressedLong, void (*onIntDown)(), void (*onIntUp)(),
|
void (*onIntUp)(), void (*onIntLeft)(), void (*onIntRight)(), void (*onIntPress)())
|
||||||
void (*onIntLeft)(), void (*onIntRight)(), void (*onIntPress)())
|
|
||||||
{
|
{
|
||||||
this->_pinDown = pinDown;
|
this->_pinDown = pinDown;
|
||||||
this->_pinUp = pinUp;
|
this->_pinUp = pinUp;
|
||||||
@@ -20,7 +18,6 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
|
|||||||
this->_eventLeft = eventLeft;
|
this->_eventLeft = eventLeft;
|
||||||
this->_eventRight = eventRight;
|
this->_eventRight = eventRight;
|
||||||
this->_eventPressed = eventPressed;
|
this->_eventPressed = eventPressed;
|
||||||
this->_eventPressedLong = eventPressedLong;
|
|
||||||
|
|
||||||
if (pinPress != 255) {
|
if (pinPress != 255) {
|
||||||
pinMode(pinPress, INPUT_PULLUP);
|
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);
|
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,
|
LOG_DEBUG("Trackball GPIO initialized (%d, %d, %d, %d, %d)", this->_pinUp, this->_pinDown, this->_pinLeft, this->_pinRight,
|
||||||
this->_pinLeft, this->_pinRight, pinPress);
|
pinPress);
|
||||||
osk_found = true;
|
|
||||||
this->setInterval(100);
|
this->setInterval(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,47 +50,10 @@ int32_t TrackballInterruptBase::runOnce()
|
|||||||
{
|
{
|
||||||
InputEvent e = {};
|
InputEvent e = {};
|
||||||
e.inputEvent = INPUT_BROKER_NONE;
|
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 defined(T_DECK) // T-deck gets a super-simple debounce on trackball
|
||||||
if (this->action == TB_ACTION_PRESSED && !pressDetected) {
|
if (this->action == TB_ACTION_PRESSED) {
|
||||||
// Start long press detection
|
// LOG_DEBUG("Trackball event Press");
|
||||||
pressDetected = true;
|
e.inputEvent = this->_eventPressed;
|
||||||
pressStartTime = millis();
|
|
||||||
// Don't send event yet, wait to see if it's a long press
|
|
||||||
} else if (this->action == TB_ACTION_UP && lastEvent == TB_ACTION_UP) {
|
} else if (this->action == TB_ACTION_UP && lastEvent == TB_ACTION_UP) {
|
||||||
// LOG_DEBUG("Trackball event UP");
|
// LOG_DEBUG("Trackball event UP");
|
||||||
e.inputEvent = this->_eventUp;
|
e.inputEvent = this->_eventUp;
|
||||||
@@ -108,11 +68,9 @@ int32_t TrackballInterruptBase::runOnce()
|
|||||||
e.inputEvent = this->_eventRight;
|
e.inputEvent = this->_eventRight;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (this->action == TB_ACTION_PRESSED && !digitalRead(_pinPress) && !pressDetected) {
|
if (this->action == TB_ACTION_PRESSED && !digitalRead(_pinPress)) {
|
||||||
// Start long press detection
|
// LOG_DEBUG("Trackball event Press");
|
||||||
pressDetected = true;
|
e.inputEvent = this->_eventPressed;
|
||||||
pressStartTime = millis();
|
|
||||||
// Don't send event yet, wait to see if it's a long press
|
|
||||||
} else if (this->action == TB_ACTION_UP && !digitalRead(_pinUp)) {
|
} else if (this->action == TB_ACTION_UP && !digitalRead(_pinUp)) {
|
||||||
// LOG_DEBUG("Trackball event UP");
|
// LOG_DEBUG("Trackball event UP");
|
||||||
e.inputEvent = this->_eventUp;
|
e.inputEvent = this->_eventUp;
|
||||||
@@ -133,16 +91,10 @@ int32_t TrackballInterruptBase::runOnce()
|
|||||||
e.kbchar = 0x00;
|
e.kbchar = 0x00;
|
||||||
this->notifyObservers(&e);
|
this->notifyObservers(&e);
|
||||||
}
|
}
|
||||||
|
lastEvent = action;
|
||||||
|
this->action = TB_ACTION_NONE;
|
||||||
|
|
||||||
// Only update lastEvent for non-press actions or completed press actions
|
return 100;
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrackballInterruptBase::intPressHandler()
|
void TrackballInterruptBase::intPressHandler()
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
|
|||||||
explicit TrackballInterruptBase(const char *name);
|
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,
|
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 eventUp, input_broker_event eventLeft, input_broker_event eventRight,
|
||||||
input_broker_event eventPressed, input_broker_event eventPressedLong, void (*onIntDown)(), void (*onIntUp)(),
|
input_broker_event eventPressed, void (*onIntDown)(), void (*onIntUp)(), void (*onIntLeft)(), void (*onIntRight)(),
|
||||||
void (*onIntLeft)(), void (*onIntRight)(), void (*onIntPress)());
|
void (*onIntPress)());
|
||||||
void intPressHandler();
|
void intPressHandler();
|
||||||
void intDownHandler();
|
void intDownHandler();
|
||||||
void intUpHandler();
|
void intUpHandler();
|
||||||
@@ -33,7 +33,6 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
|
|||||||
enum TrackballInterruptBaseActionType {
|
enum TrackballInterruptBaseActionType {
|
||||||
TB_ACTION_NONE,
|
TB_ACTION_NONE,
|
||||||
TB_ACTION_PRESSED,
|
TB_ACTION_PRESSED,
|
||||||
TB_ACTION_PRESSED_LONG,
|
|
||||||
TB_ACTION_UP,
|
TB_ACTION_UP,
|
||||||
TB_ACTION_DOWN,
|
TB_ACTION_DOWN,
|
||||||
TB_ACTION_LEFT,
|
TB_ACTION_LEFT,
|
||||||
@@ -47,20 +46,12 @@ class TrackballInterruptBase : public Observable<const InputEvent *>, public con
|
|||||||
|
|
||||||
volatile TrackballInterruptBaseActionType action = TB_ACTION_NONE;
|
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:
|
private:
|
||||||
input_broker_event _eventDown = INPUT_BROKER_NONE;
|
input_broker_event _eventDown = INPUT_BROKER_NONE;
|
||||||
input_broker_event _eventUp = INPUT_BROKER_NONE;
|
input_broker_event _eventUp = INPUT_BROKER_NONE;
|
||||||
input_broker_event _eventLeft = INPUT_BROKER_NONE;
|
input_broker_event _eventLeft = INPUT_BROKER_NONE;
|
||||||
input_broker_event _eventRight = INPUT_BROKER_NONE;
|
input_broker_event _eventRight = INPUT_BROKER_NONE;
|
||||||
input_broker_event _eventPressed = INPUT_BROKER_NONE;
|
input_broker_event _eventPressed = INPUT_BROKER_NONE;
|
||||||
input_broker_event _eventPressedLong = INPUT_BROKER_NONE;
|
|
||||||
const char *_originName;
|
const char *_originName;
|
||||||
TrackballInterruptBaseActionType lastEvent = TB_ACTION_NONE;
|
TrackballInterruptBaseActionType lastEvent = TB_ACTION_NONE;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 eventLeft = INPUT_BROKER_LEFT;
|
||||||
input_broker_event eventRight = INPUT_BROKER_RIGHT;
|
input_broker_event eventRight = INPUT_BROKER_RIGHT;
|
||||||
input_broker_event eventPressed = INPUT_BROKER_SELECT;
|
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,
|
TrackballInterruptBase::init(pinDown, pinUp, pinLeft, pinRight, pinPress, eventDown, eventUp, eventLeft, eventRight,
|
||||||
eventPressed, eventPressedLong, TrackballInterruptImpl1::handleIntDown,
|
eventPressed, TrackballInterruptImpl1::handleIntDown, TrackballInterruptImpl1::handleIntUp,
|
||||||
TrackballInterruptImpl1::handleIntUp, TrackballInterruptImpl1::handleIntLeft,
|
TrackballInterruptImpl1::handleIntLeft, TrackballInterruptImpl1::handleIntRight,
|
||||||
TrackballInterruptImpl1::handleIntRight, TrackballInterruptImpl1::handleIntPressed);
|
TrackballInterruptImpl1::handleIntPressed);
|
||||||
inputBroker->registerSource(this);
|
inputBroker->registerSource(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
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 eventUp, input_broker_event eventPressed, void (*onIntDown)(),
|
||||||
input_broker_event eventUpLong, input_broker_event eventDownLong, void (*onIntDown)(),
|
|
||||||
void (*onIntUp)(), void (*onIntPress)(), unsigned long updownDebounceMs)
|
void (*onIntUp)(), void (*onIntPress)(), unsigned long updownDebounceMs)
|
||||||
{
|
{
|
||||||
this->_pinDown = pinDown;
|
this->_pinDown = pinDown;
|
||||||
this->_pinUp = pinUp;
|
this->_pinUp = pinUp;
|
||||||
this->_pinPress = pinPress;
|
|
||||||
this->_eventDown = eventDown;
|
this->_eventDown = eventDown;
|
||||||
this->_eventUp = eventUp;
|
this->_eventUp = eventUp;
|
||||||
this->_eventPressed = eventPressed;
|
this->_eventPressed = eventPressed;
|
||||||
this->_eventPressedLong = eventPressedLong;
|
|
||||||
this->_eventUpLong = eventUpLong;
|
|
||||||
this->_eventDownLong = eventDownLong;
|
|
||||||
|
|
||||||
// Store debounce configuration passed by caller
|
|
||||||
this->updownDebounceMs = updownDebounceMs;
|
|
||||||
bool isRAK = false;
|
bool isRAK = false;
|
||||||
#ifdef RAK_4631
|
#ifdef RAK_4631
|
||||||
isRAK = true;
|
isRAK = true;
|
||||||
@@ -30,20 +22,20 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress,
|
|||||||
|
|
||||||
if (!isRAK || pinPress != 0) {
|
if (!isRAK || pinPress != 0) {
|
||||||
pinMode(pinPress, INPUT_PULLUP);
|
pinMode(pinPress, INPUT_PULLUP);
|
||||||
attachInterrupt(pinPress, onIntPress, FALLING);
|
attachInterrupt(pinPress, onIntPress, RISING);
|
||||||
}
|
}
|
||||||
if (!isRAK || this->_pinDown != 0) {
|
if (!isRAK || this->_pinDown != 0) {
|
||||||
pinMode(this->_pinDown, INPUT_PULLUP);
|
pinMode(this->_pinDown, INPUT_PULLUP);
|
||||||
attachInterrupt(this->_pinDown, onIntDown, FALLING);
|
attachInterrupt(this->_pinDown, onIntDown, RISING);
|
||||||
}
|
}
|
||||||
if (!isRAK || this->_pinUp != 0) {
|
if (!isRAK || this->_pinUp != 0) {
|
||||||
pinMode(this->_pinUp, INPUT_PULLUP);
|
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);
|
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()
|
int32_t UpDownInterruptBase::runOnce()
|
||||||
@@ -51,88 +43,23 @@ int32_t UpDownInterruptBase::runOnce()
|
|||||||
InputEvent e = {};
|
InputEvent e = {};
|
||||||
e.inputEvent = INPUT_BROKER_NONE;
|
e.inputEvent = INPUT_BROKER_NONE;
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
|
if (this->action == UPDOWN_ACTION_PRESSED) {
|
||||||
// Read all button states once at the beginning
|
if (now - lastPressKeyTime >= pressDebounceMs) {
|
||||||
bool pressButtonPressed = !digitalRead(_pinPress);
|
lastPressKeyTime = now;
|
||||||
bool upButtonPressed = !digitalRead(_pinUp);
|
LOG_DEBUG("GPIO event Press");
|
||||||
bool downButtonPressed = !digitalRead(_pinDown);
|
e.inputEvent = this->_eventPressed;
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
} else if (this->action == UPDOWN_ACTION_UP) {
|
||||||
|
if (now - lastUpKeyTime >= updownDebounceMs) {
|
||||||
// Handle long press detection for up button
|
lastUpKeyTime = now;
|
||||||
if (upDetected && upStartTime > 0) {
|
LOG_DEBUG("GPIO event Up");
|
||||||
uint32_t upDuration = now - upStartTime;
|
e.inputEvent = this->_eventUp;
|
||||||
|
|
||||||
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_DOWN) {
|
||||||
|
if (now - lastDownKeyTime >= updownDebounceMs) {
|
||||||
// Handle long press detection for down button
|
lastDownKeyTime = now;
|
||||||
if (downDetected && downStartTime > 0) {
|
LOG_DEBUG("GPIO event Down");
|
||||||
uint32_t downDuration = now - downStartTime;
|
e.inputEvent = this->_eventDown;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,11 +69,8 @@ int32_t UpDownInterruptBase::runOnce()
|
|||||||
this->notifyObservers(&e);
|
this->notifyObservers(&e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pressDetected && !upDetected && !downDetected) {
|
this->action = UPDOWN_ACTION_NONE;
|
||||||
this->action = UPDOWN_ACTION_NONE;
|
return 100;
|
||||||
}
|
|
||||||
|
|
||||||
return 20; // This will control how the input frequency
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpDownInterruptBase::intPressHandler()
|
void UpDownInterruptBase::intPressHandler()
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ class UpDownInterruptBase : public Observable<const InputEvent *>, public concur
|
|||||||
public:
|
public:
|
||||||
explicit UpDownInterruptBase(const char *name);
|
explicit UpDownInterruptBase(const char *name);
|
||||||
void init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress, input_broker_event eventDown, input_broker_event eventUp,
|
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 eventPressed, void (*onIntDown)(), void (*onIntUp)(), void (*onIntPress)(),
|
||||||
input_broker_event eventDownLong, void (*onIntDown)(), void (*onIntUp)(), void (*onIntPress)(),
|
|
||||||
unsigned long updownDebounceMs = 50);
|
unsigned long updownDebounceMs = 50);
|
||||||
void intPressHandler();
|
void intPressHandler();
|
||||||
void intDownHandler();
|
void intDownHandler();
|
||||||
@@ -18,41 +17,16 @@ class UpDownInterruptBase : public Observable<const InputEvent *>, public concur
|
|||||||
int32_t runOnce() override;
|
int32_t runOnce() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
enum UpDownInterruptBaseActionType {
|
enum UpDownInterruptBaseActionType { UPDOWN_ACTION_NONE, UPDOWN_ACTION_PRESSED, UPDOWN_ACTION_UP, UPDOWN_ACTION_DOWN };
|
||||||
UPDOWN_ACTION_NONE,
|
|
||||||
UPDOWN_ACTION_PRESSED,
|
|
||||||
UPDOWN_ACTION_PRESSED_LONG,
|
|
||||||
UPDOWN_ACTION_UP,
|
|
||||||
UPDOWN_ACTION_UP_LONG,
|
|
||||||
UPDOWN_ACTION_DOWN,
|
|
||||||
UPDOWN_ACTION_DOWN_LONG
|
|
||||||
};
|
|
||||||
|
|
||||||
volatile UpDownInterruptBaseActionType action = UPDOWN_ACTION_NONE;
|
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:
|
private:
|
||||||
uint8_t _pinDown = 0;
|
uint8_t _pinDown = 0;
|
||||||
uint8_t _pinUp = 0;
|
uint8_t _pinUp = 0;
|
||||||
uint8_t _pinPress = 0;
|
|
||||||
input_broker_event _eventDown = INPUT_BROKER_NONE;
|
input_broker_event _eventDown = INPUT_BROKER_NONE;
|
||||||
input_broker_event _eventUp = INPUT_BROKER_NONE;
|
input_broker_event _eventUp = INPUT_BROKER_NONE;
|
||||||
input_broker_event _eventPressed = 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;
|
const char *_originName;
|
||||||
|
|
||||||
unsigned long lastUpKeyTime = 0;
|
unsigned long lastUpKeyTime = 0;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "UpDownInterruptImpl1.h"
|
#include "UpDownInterruptImpl1.h"
|
||||||
#include "InputBroker.h"
|
#include "InputBroker.h"
|
||||||
extern bool osk_found;
|
|
||||||
|
|
||||||
UpDownInterruptImpl1 *upDownInterruptImpl1;
|
UpDownInterruptImpl1 *upDownInterruptImpl1;
|
||||||
|
|
||||||
@@ -18,18 +17,13 @@ bool UpDownInterruptImpl1::init()
|
|||||||
uint8_t pinDown = moduleConfig.canned_message.inputbroker_pin_b;
|
uint8_t pinDown = moduleConfig.canned_message.inputbroker_pin_b;
|
||||||
uint8_t pinPress = moduleConfig.canned_message.inputbroker_pin_press;
|
uint8_t pinPress = moduleConfig.canned_message.inputbroker_pin_press;
|
||||||
|
|
||||||
input_broker_event eventDown = INPUT_BROKER_USER_PRESS; // acts like RIGHT/DOWN
|
input_broker_event eventDown = INPUT_BROKER_DOWN;
|
||||||
input_broker_event eventUp = INPUT_BROKER_ALT_PRESS; // acts like LEFT/UP
|
input_broker_event eventUp = INPUT_BROKER_UP;
|
||||||
input_broker_event eventPressed = INPUT_BROKER_SELECT;
|
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,
|
UpDownInterruptBase::init(pinDown, pinUp, pinPress, eventDown, eventUp, eventPressed, UpDownInterruptImpl1::handleIntDown,
|
||||||
eventDownLong, UpDownInterruptImpl1::handleIntDown, UpDownInterruptImpl1::handleIntUp,
|
UpDownInterruptImpl1::handleIntUp, UpDownInterruptImpl1::handleIntPressed);
|
||||||
UpDownInterruptImpl1::handleIntPressed);
|
|
||||||
inputBroker->registerSource(this);
|
inputBroker->registerSource(this);
|
||||||
osk_found = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -192,8 +192,6 @@ ScanI2C::DeviceAddress cardkb_found = ScanI2C::ADDRESS_NONE;
|
|||||||
uint8_t kb_model;
|
uint8_t kb_model;
|
||||||
// global bool to record that a kb is present
|
// global bool to record that a kb is present
|
||||||
bool kb_found = false;
|
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)
|
// The I2C address of the RTC Module (if found)
|
||||||
ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE;
|
ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE;
|
||||||
@@ -1441,10 +1439,6 @@ void setup()
|
|||||||
#endif
|
#endif
|
||||||
#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
|
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER
|
||||||
// Start web server thread.
|
// Start web server thread.
|
||||||
webServerThread = new WebServerThread();
|
webServerThread = new WebServerThread();
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ extern ScanI2C::DeviceAddress screen_found;
|
|||||||
extern ScanI2C::DeviceAddress cardkb_found;
|
extern ScanI2C::DeviceAddress cardkb_found;
|
||||||
extern uint8_t kb_model;
|
extern uint8_t kb_model;
|
||||||
extern bool kb_found;
|
extern bool kb_found;
|
||||||
extern bool osk_found;
|
|
||||||
extern ScanI2C::DeviceAddress rtc_found;
|
extern ScanI2C::DeviceAddress rtc_found;
|
||||||
extern ScanI2C::DeviceAddress accelerometer_found;
|
extern ScanI2C::DeviceAddress accelerometer_found;
|
||||||
extern ScanI2C::FoundDevice rgb_found;
|
extern ScanI2C::FoundDevice rgb_found;
|
||||||
|
|||||||
@@ -428,8 +428,8 @@ bool Channels::setDefaultPresetCryptoForHash(ChannelHash channelHash)
|
|||||||
// Iterate all known presets
|
// Iterate all known presets
|
||||||
for (int preset = _meshtastic_Config_LoRaConfig_ModemPreset_MIN; preset <= _meshtastic_Config_LoRaConfig_ModemPreset_MAX;
|
for (int preset = _meshtastic_Config_LoRaConfig_ModemPreset_MIN; preset <= _meshtastic_Config_LoRaConfig_ModemPreset_MAX;
|
||||||
++preset) {
|
++preset) {
|
||||||
const char *name = DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false,
|
const char *name =
|
||||||
config.lora.use_preset);
|
DisplayFormatters::getModemPresetDisplayName((meshtastic_Config_LoRaConfig_ModemPreset)preset, false, false);
|
||||||
if (!name)
|
if (!name)
|
||||||
continue;
|
continue;
|
||||||
if (strcmp(name, "Invalid") == 0)
|
if (strcmp(name, "Invalid") == 0)
|
||||||
|
|||||||
@@ -32,12 +32,9 @@ const RegionInfo regions[] = {
|
|||||||
RDEF(US, 902.0f, 928.0f, 100, 0, 30, true, false, false),
|
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://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
|
||||||
|
|
||||||
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
|
|
||||||
*/
|
*/
|
||||||
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/
|
https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/
|
||||||
|
|||||||
@@ -422,8 +422,7 @@ void RadioLibInterface::handleReceiveInterrupt()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (state != RADIOLIB_ERR_NONE) {
|
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,
|
LOG_ERROR("Ignore received packet due to error=%d", state);
|
||||||
radioBuffer.header.to, radioBuffer.header.from, radioBuffer.header.flags);
|
|
||||||
rxBad++;
|
rxBad++;
|
||||||
|
|
||||||
airTime->logAirtime(RX_ALL_LOG, xmitMsec);
|
airTime->logAirtime(RX_ALL_LOG, xmitMsec);
|
||||||
|
|||||||
@@ -97,34 +97,27 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
|
|||||||
void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
|
void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
|
||||||
{
|
{
|
||||||
if (isToUs(p)) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability)
|
if (isToUs(p)) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability)
|
||||||
if (!MeshModule::currentReply) {
|
if (p->want_ack) {
|
||||||
if (p->want_ack) {
|
if (MeshModule::currentReply) {
|
||||||
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
LOG_DEBUG("Another module replied to this message, no need for 2nd ack");
|
||||||
/* A response may be set to want_ack for retransmissions, but we don't need to ACK a response if it received
|
} else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||||
an implicit ACK already. If we received it directly or via NextHopRouter, only ACK with a hop limit of 0 to
|
// A response may be set to want_ack for retransmissions, but we don't need to ACK a response if it received an
|
||||||
make sure the other side stops retransmitting. */
|
// implicit ACK already. If we received it directly, only ACK with a hop limit of 0
|
||||||
if (!p->decoded.request_id && !p->decoded.reply_id) {
|
if (!p->decoded.request_id)
|
||||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
|
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
|
||||||
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
|
|
||||||
} else if ((p->hop_start > 0 && p->hop_start == p->hop_limit) || p->next_hop != NO_NEXT_HOP_PREFERENCE) {
|
|
||||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
|
|
||||||
}
|
|
||||||
} else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 &&
|
|
||||||
(nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) {
|
|
||||||
LOG_INFO("PKI packet from unknown node, send PKI_UNKNOWN_PUBKEY");
|
|
||||||
sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
|
|
||||||
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
|
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
|
||||||
} else {
|
else if (p->hop_start > 0 && p->hop_start == p->hop_limit)
|
||||||
// Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded
|
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
|
||||||
sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(),
|
} else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 &&
|
||||||
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
|
(nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) {
|
||||||
}
|
LOG_INFO("PKI packet from unknown node, send PKI_UNKNOWN_PUBKEY");
|
||||||
} else if (p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum()) && p->hop_limit > 0) {
|
sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
|
||||||
// No wantAck, but we need to ACK with hop limit of 0 if we were the next hop to stop their retransmissions
|
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
|
||||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
|
} else {
|
||||||
|
// Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded
|
||||||
|
sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(),
|
||||||
|
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
LOG_DEBUG("Another module replied to this message, no need for 2nd ack");
|
|
||||||
}
|
}
|
||||||
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && c &&
|
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && c &&
|
||||||
c->error_reason == meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY) {
|
c->error_reason == meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY) {
|
||||||
|
|||||||
@@ -52,16 +52,6 @@ template <typename T> bool SX126xInterface<T>::init()
|
|||||||
pinMode(SX126X_POWER_EN, OUTPUT);
|
pinMode(SX126X_POWER_EN, OUTPUT);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HELTEC_V4
|
|
||||||
pinMode(LORA_PA_POWER, OUTPUT);
|
|
||||||
digitalWrite(LORA_PA_POWER, HIGH);
|
|
||||||
|
|
||||||
pinMode(LORA_PA_EN, OUTPUT);
|
|
||||||
digitalWrite(LORA_PA_EN, LOW);
|
|
||||||
pinMode(LORA_PA_TX_EN, OUTPUT);
|
|
||||||
digitalWrite(LORA_PA_TX_EN, LOW);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
|
tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000;
|
||||||
if (portduino_config.lora_sx126x_ant_sw_pin.pin != RADIOLIB_NC) {
|
if (portduino_config.lora_sx126x_ant_sw_pin.pin != RADIOLIB_NC) {
|
||||||
@@ -73,7 +63,7 @@ template <typename T> bool SX126xInterface<T>::init()
|
|||||||
LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage");
|
LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage");
|
||||||
else
|
else
|
||||||
LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", tcxoVoltage);
|
LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", tcxoVoltage);
|
||||||
setTransmitEnable(false);
|
|
||||||
// FIXME: May want to set depending on a definition, currently all SX126x variant files use the DC-DC regulator option
|
// FIXME: May want to set depending on a definition, currently all SX126x variant files use the DC-DC regulator option
|
||||||
bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC?
|
bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC?
|
||||||
|
|
||||||
@@ -269,7 +259,6 @@ template <typename T> void SX126xInterface<T>::addReceiveMetadata(meshtastic_Mes
|
|||||||
*/
|
*/
|
||||||
template <typename T> void SX126xInterface<T>::configHardwareForSend()
|
template <typename T> void SX126xInterface<T>::configHardwareForSend()
|
||||||
{
|
{
|
||||||
setTransmitEnable(true);
|
|
||||||
RadioLibInterface::configHardwareForSend();
|
RadioLibInterface::configHardwareForSend();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,7 +271,6 @@ template <typename T> void SX126xInterface<T>::startReceive()
|
|||||||
sleep();
|
sleep();
|
||||||
#else
|
#else
|
||||||
|
|
||||||
setTransmitEnable(false);
|
|
||||||
setStandby();
|
setStandby();
|
||||||
|
|
||||||
// We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly.
|
// We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly.
|
||||||
@@ -310,7 +298,7 @@ template <typename T> bool SX126xInterface<T>::isChannelActive()
|
|||||||
.irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
|
.irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS,
|
||||||
.irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK}};
|
.irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK}};
|
||||||
int16_t result;
|
int16_t result;
|
||||||
setTransmitEnable(false);
|
|
||||||
setStandby();
|
setStandby();
|
||||||
result = lora.scanChannel(cfg);
|
result = lora.scanChannel(cfg);
|
||||||
if (result == RADIOLIB_LORA_DETECTED)
|
if (result == RADIOLIB_LORA_DETECTED)
|
||||||
@@ -349,26 +337,6 @@ template <typename T> bool SX126xInterface<T>::sleep()
|
|||||||
digitalWrite(SX126X_POWER_EN, LOW);
|
digitalWrite(SX126X_POWER_EN, LOW);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HELTEC_V4
|
|
||||||
/*
|
|
||||||
* Do not switch the power on and off frequently.
|
|
||||||
* After turning off LORA_PA_EN, the power consumption has dropped to the uA level.
|
|
||||||
* // digitalWrite(LORA_PA_POWER, LOW);
|
|
||||||
*/
|
|
||||||
digitalWrite(LORA_PA_EN, LOW);
|
|
||||||
digitalWrite(LORA_PA_TX_EN, LOW);
|
|
||||||
#endif
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Some boards require GPIO control of tx vs rx paths */
|
|
||||||
template <typename T> void SX126xInterface<T>::setTransmitEnable(bool txon)
|
|
||||||
{
|
|
||||||
#ifdef HELTEC_V4
|
|
||||||
digitalWrite(LORA_PA_POWER, HIGH);
|
|
||||||
digitalWrite(LORA_PA_EN, HIGH);
|
|
||||||
digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -71,9 +71,5 @@ template <class T> class SX126xInterface : public RadioLibInterface
|
|||||||
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
|
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
|
||||||
|
|
||||||
virtual void setStandby() override;
|
virtual void setStandby() override;
|
||||||
|
|
||||||
private:
|
|
||||||
/** Some boards require GPIO control of tx vs rx paths */
|
|
||||||
void setTransmitEnable(bool txon);
|
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
@@ -50,15 +50,15 @@ class StreamAPI : public PhoneAPI
|
|||||||
* phone.
|
* phone.
|
||||||
*/
|
*/
|
||||||
virtual int32_t runOncePart();
|
virtual int32_t runOncePart();
|
||||||
virtual int32_t runOncePart(char *buf, uint16_t bufLen);
|
virtual int32_t runOncePart(char *buf,uint16_t bufLen);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
/**
|
||||||
* Read any rx chars from the link and call handleToRadio
|
* Read any rx chars from the link and call handleToRadio
|
||||||
*/
|
*/
|
||||||
int32_t readStream();
|
int32_t readStream();
|
||||||
int32_t readStream(char *buf, uint16_t bufLen);
|
int32_t readStream(char *buf,uint16_t bufLen);
|
||||||
int32_t handleRecStream(char *buf, uint16_t bufLen);
|
int32_t handleRecStream(char *buf,uint16_t bufLen);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* call getFromRadio() and deliver encapsulated packets to the Stream
|
* call getFromRadio() and deliver encapsulated packets to the Stream
|
||||||
|
|||||||
@@ -55,12 +55,12 @@ HTTPClient httpClient;
|
|||||||
|
|
||||||
// We need to specify some content-type mapping, so the resources get delivered with the
|
// We need to specify some content-type mapping, so the resources get delivered with the
|
||||||
// right content type and are displayed correctly in the browser
|
// right content type and are displayed correctly in the browser
|
||||||
char const *contentTypes[][2] = {{".txt", "text/plain"}, {".html", "text/html"},
|
char contentTypes[][2][32] = {{".txt", "text/plain"}, {".html", "text/html"},
|
||||||
{".js", "text/javascript"}, {".png", "image/png"},
|
{".js", "text/javascript"}, {".png", "image/png"},
|
||||||
{".jpg", "image/jpg"}, {".gz", "application/gzip"},
|
{".jpg", "image/jpg"}, {".gz", "application/gzip"},
|
||||||
{".gif", "image/gif"}, {".json", "application/json"},
|
{".gif", "image/gif"}, {".json", "application/json"},
|
||||||
{".css", "text/css"}, {".ico", "image/vnd.microsoft.icon"},
|
{".css", "text/css"}, {".ico", "image/vnd.microsoft.icon"},
|
||||||
{".svg", "image/svg+xml"}, {"", ""}};
|
{".svg", "image/svg+xml"}, {"", ""}};
|
||||||
|
|
||||||
// const char *certificate = NULL; // change this as needed, leave as is for no TLS check (yolo security)
|
// const char *certificate = NULL; // change this as needed, leave as is for no TLS check (yolo security)
|
||||||
|
|
||||||
|
|||||||
@@ -13,16 +13,12 @@
|
|||||||
#include "detect/ScanI2C.h"
|
#include "detect/ScanI2C.h"
|
||||||
#include "graphics/Screen.h"
|
#include "graphics/Screen.h"
|
||||||
#include "graphics/SharedUIDisplay.h"
|
#include "graphics/SharedUIDisplay.h"
|
||||||
#include "graphics/draw/NotificationRenderer.h"
|
|
||||||
#include "graphics/emotes.h"
|
#include "graphics/emotes.h"
|
||||||
#include "graphics/images.h"
|
#include "graphics/images.h"
|
||||||
#include "main.h" // for cardkb_found
|
#include "main.h" // for cardkb_found
|
||||||
#include "mesh/generated/meshtastic/cannedmessages.pb.h"
|
#include "mesh/generated/meshtastic/cannedmessages.pb.h"
|
||||||
#include "modules/AdminModule.h"
|
#include "modules/AdminModule.h"
|
||||||
#include "modules/ExternalNotificationModule.h" // for buzzer control
|
#include "modules/ExternalNotificationModule.h" // for buzzer control
|
||||||
#if HAS_TRACKBALL
|
|
||||||
#include "input/TrackballInterruptImpl1.h"
|
|
||||||
#endif
|
|
||||||
#if !MESHTASTIC_EXCLUDE_GPS
|
#if !MESHTASTIC_EXCLUDE_GPS
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -42,7 +38,6 @@
|
|||||||
|
|
||||||
extern ScanI2C::DeviceAddress cardkb_found;
|
extern ScanI2C::DeviceAddress cardkb_found;
|
||||||
extern bool graphics::isMuted;
|
extern bool graphics::isMuted;
|
||||||
extern bool osk_found;
|
|
||||||
|
|
||||||
static const char *cannedMessagesConfigFile = "/prefs/cannedConf.proto";
|
static const char *cannedMessagesConfigFile = "/prefs/cannedConf.proto";
|
||||||
static NodeNum lastDest = NODENUM_BROADCAST;
|
static NodeNum lastDest = NODENUM_BROADCAST;
|
||||||
@@ -156,13 +151,10 @@ int CannedMessageModule::splitConfiguredMessages()
|
|||||||
int tempCount = 0;
|
int tempCount = 0;
|
||||||
// Insert at position 0 (top)
|
// Insert at position 0 (top)
|
||||||
tempMessages[tempCount++] = "[Select Destination]";
|
tempMessages[tempCount++] = "[Select Destination]";
|
||||||
|
|
||||||
#if defined(USE_VIRTUAL_KEYBOARD)
|
#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 --]";
|
tempMessages[tempCount++] = "[-- Free Text --]";
|
||||||
#else
|
|
||||||
if (osk_found && screen) {
|
|
||||||
tempMessages[tempCount++] = "[-- Free Text --]";
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// First message always starts at buffer start
|
// First message always starts at buffer start
|
||||||
@@ -349,8 +341,6 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event)
|
|||||||
case CANNED_MESSAGE_RUN_STATE_FREETEXT:
|
case CANNED_MESSAGE_RUN_STATE_FREETEXT:
|
||||||
return handleFreeTextInput(event); // All allowed input for this state
|
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)
|
// If sending, block all input except global/system (handled above)
|
||||||
case CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE:
|
case CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE:
|
||||||
return 1;
|
return 1;
|
||||||
@@ -637,56 +627,6 @@ bool CannedMessageModule::handleMessageSelectorInput(const InputEvent *event, bo
|
|||||||
notifyObservers(&e);
|
notifyObservers(&e);
|
||||||
return true;
|
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
|
#endif
|
||||||
|
|
||||||
// Normal canned message selection
|
// Normal canned message selection
|
||||||
@@ -1003,54 +943,12 @@ int32_t CannedMessageModule::runOnce()
|
|||||||
|
|
||||||
// Normal module disable/idle handling
|
// Normal module disable/idle handling
|
||||||
if ((this->runState == CANNED_MESSAGE_RUN_STATE_DISABLED) || (this->runState == CANNED_MESSAGE_RUN_STATE_INACTIVE)) {
|
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 = "";
|
temporaryMessage = "";
|
||||||
return INT32_MAX;
|
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;
|
UIFrameEvent e;
|
||||||
if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE && this->payload != 0 &&
|
if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) ||
|
||||||
this->payload != CANNED_MESSAGE_RUN_STATE_FREETEXT) ||
|
|
||||||
(this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) ||
|
(this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) ||
|
||||||
(this->runState == CANNED_MESSAGE_RUN_STATE_MESSAGE_SELECTION)) {
|
(this->runState == CANNED_MESSAGE_RUN_STATE_MESSAGE_SELECTION)) {
|
||||||
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
|
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
|
||||||
@@ -1060,18 +958,6 @@ int32_t CannedMessageModule::runOnce()
|
|||||||
this->freetext = "";
|
this->freetext = "";
|
||||||
this->cursor = 0;
|
this->cursor = 0;
|
||||||
this->notifyObservers(&e);
|
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)) &&
|
} else if (((this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT)) &&
|
||||||
!Throttle::isWithinTimespanMs(this->lastTouchMillis, INACTIVATE_AFTER_MS)) {
|
!Throttle::isWithinTimespanMs(this->lastTouchMillis, INACTIVATE_AFTER_MS)) {
|
||||||
// Reset module on inactivity
|
// Reset module on inactivity
|
||||||
@@ -1080,23 +966,9 @@ int32_t CannedMessageModule::runOnce()
|
|||||||
this->freetext = "";
|
this->freetext = "";
|
||||||
this->cursor = 0;
|
this->cursor = 0;
|
||||||
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
|
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);
|
this->notifyObservers(&e);
|
||||||
} else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) {
|
} else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) {
|
||||||
if (this->payload == 0) {
|
if (this->payload == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
|
||||||
// [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->freetext.length() > 0) {
|
if (this->freetext.length() > 0) {
|
||||||
sendText(this->dest, this->channel, this->freetext.c_str(), true);
|
sendText(this->dest, this->channel, this->freetext.c_str(), true);
|
||||||
this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE;
|
this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE;
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ void setupModules()
|
|||||||
#if HAS_TELEMETRY
|
#if HAS_TELEMETRY
|
||||||
new DeviceTelemetryModule();
|
new DeviceTelemetryModule();
|
||||||
#endif
|
#endif
|
||||||
#if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||||
if (moduleConfig.has_telemetry &&
|
if (moduleConfig.has_telemetry &&
|
||||||
(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
|
(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
|
||||||
new EnvironmentTelemetryModule();
|
new EnvironmentTelemetryModule();
|
||||||
|
|||||||
@@ -45,9 +45,6 @@
|
|||||||
#ifndef HAS_CUSTOM_CRYPTO_ENGINE
|
#ifndef HAS_CUSTOM_CRYPTO_ENGINE
|
||||||
#define HAS_CUSTOM_CRYPTO_ENGINE 1
|
#define HAS_CUSTOM_CRYPTO_ENGINE 1
|
||||||
#endif
|
#endif
|
||||||
#ifndef HAS_32768HZ
|
|
||||||
#define HAS_32768HZ 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(HAS_AXP192) || defined(HAS_AXP2101)
|
#if defined(HAS_AXP192) || defined(HAS_AXP2101)
|
||||||
#define HAS_PMU
|
#define HAS_PMU
|
||||||
@@ -197,8 +194,6 @@
|
|||||||
#define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO
|
#define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO
|
||||||
#elif defined(T_LORA_PAGER)
|
#elif defined(T_LORA_PAGER)
|
||||||
#define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER
|
#define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER
|
||||||
#elif defined(HELTEC_V4)
|
|
||||||
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_V4
|
|
||||||
#elif defined(M5STACK_UNITC6L)
|
#elif defined(M5STACK_UNITC6L)
|
||||||
#define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L
|
#define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L
|
||||||
#endif
|
#endif
|
||||||
@@ -224,13 +219,3 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define SERIAL0_RX_GPIO 3 // Always GPIO3 on ESP32 // FIXME: may be different on ESP32-S3, etc.
|
#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
|
|
||||||
@@ -64,7 +64,7 @@ void getMacAddr(uint8_t *dmac)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
#if HAS_32768HZ
|
#ifdef HAS_32768HZ
|
||||||
#define CALIBRATE_ONE(cali_clk) calibrate_one(cali_clk, #cali_clk)
|
#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)
|
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);
|
uint32_t cal_32k = CALIBRATE_ONE(RTC_CAL_32K_XTAL);
|
||||||
|
|
||||||
if (cal_32k == 0) {
|
if (cal_32k == 0) {
|
||||||
LOG_DEBUG("32k XTAL OSC has not started up");
|
LOG_DEBUG("32K XTAL OSC has not started up");
|
||||||
} else {
|
} else {
|
||||||
rtc_clk_slow_freq_set(RTC_SLOW_FREQ_32K_XTAL);
|
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_RTC_MUX);
|
||||||
CALIBRATE_ONE(RTC_CAL_32K_XTAL);
|
CALIBRATE_ONE(RTC_CAL_32K_XTAL);
|
||||||
}
|
}
|
||||||
CALIBRATE_ONE(RTC_CAL_RTC_MUX);
|
CALIBRATE_ONE(RTC_CAL_RTC_MUX);
|
||||||
CALIBRATE_ONE(RTC_CAL_32K_XTAL);
|
CALIBRATE_ONE(RTC_CAL_32K_XTAL);
|
||||||
if (rtc_clk_slow_freq_get() != RTC_SLOW_FREQ_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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,7 +182,7 @@ void esp32Setup()
|
|||||||
res = esp_task_wdt_add(NULL);
|
res = esp_task_wdt_add(NULL);
|
||||||
assert(res == ESP_OK);
|
assert(res == ESP_OK);
|
||||||
|
|
||||||
#if HAS_32768HZ
|
#ifdef HAS_32768HZ
|
||||||
enableSlowCLK();
|
enableSlowCLK();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -545,12 +545,6 @@ void enableLoraInterrupt()
|
|||||||
gpio_pullup_en((gpio_num_t)LORA_CS);
|
gpio_pullup_en((gpio_num_t)LORA_CS);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HELTEC_V4
|
|
||||||
gpio_pullup_en((gpio_num_t)LORA_PA_POWER);
|
|
||||||
gpio_pullup_en((gpio_num_t)LORA_PA_EN);
|
|
||||||
gpio_pulldown_en((gpio_num_t)LORA_PA_TX_EN);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by gpio interrupt", LORA_DIO1);
|
LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by gpio interrupt", LORA_DIO1);
|
||||||
gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL);
|
gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL);
|
||||||
|
|
||||||
|
|||||||
@@ -40,5 +40,3 @@
|
|||||||
|
|
||||||
#define SX126X_DIO2_AS_RF_SWITCH
|
#define SX126X_DIO2_AS_RF_SWITCH
|
||||||
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
||||||
|
|
||||||
#define HAS_32768HZ 1
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
#ifndef Pins_Arduino_h
|
|
||||||
#define Pins_Arduino_h
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#define USB_VID 0x303a
|
|
||||||
#define USB_PID 0x1001
|
|
||||||
|
|
||||||
static const uint8_t LED_BUILTIN = 35;
|
|
||||||
#define BUILTIN_LED LED_BUILTIN // backward compatibility
|
|
||||||
#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN
|
|
||||||
|
|
||||||
static const uint8_t TX = 43;
|
|
||||||
static const uint8_t RX = 44;
|
|
||||||
|
|
||||||
static const uint8_t SDA = 3;
|
|
||||||
static const uint8_t SCL = 4;
|
|
||||||
|
|
||||||
static const uint8_t SS = 8;
|
|
||||||
static const uint8_t MOSI = 10;
|
|
||||||
static const uint8_t MISO = 11;
|
|
||||||
static const uint8_t SCK = 9;
|
|
||||||
|
|
||||||
static const uint8_t A0 = 1;
|
|
||||||
static const uint8_t A1 = 2;
|
|
||||||
static const uint8_t A2 = 3;
|
|
||||||
static const uint8_t A3 = 4;
|
|
||||||
static const uint8_t A4 = 5;
|
|
||||||
static const uint8_t A5 = 6;
|
|
||||||
static const uint8_t A6 = 7;
|
|
||||||
static const uint8_t A7 = 8;
|
|
||||||
static const uint8_t A8 = 9;
|
|
||||||
static const uint8_t A9 = 10;
|
|
||||||
static const uint8_t A10 = 11;
|
|
||||||
static const uint8_t A11 = 12;
|
|
||||||
static const uint8_t A12 = 13;
|
|
||||||
static const uint8_t A13 = 14;
|
|
||||||
static const uint8_t A14 = 15;
|
|
||||||
static const uint8_t A15 = 16;
|
|
||||||
static const uint8_t A16 = 17;
|
|
||||||
static const uint8_t A17 = 18;
|
|
||||||
static const uint8_t A18 = 19;
|
|
||||||
static const uint8_t A19 = 20;
|
|
||||||
|
|
||||||
static const uint8_t T1 = 1;
|
|
||||||
static const uint8_t T2 = 2;
|
|
||||||
static const uint8_t T3 = 3;
|
|
||||||
static const uint8_t T4 = 4;
|
|
||||||
static const uint8_t T5 = 5;
|
|
||||||
static const uint8_t T6 = 6;
|
|
||||||
static const uint8_t T7 = 7;
|
|
||||||
static const uint8_t T8 = 8;
|
|
||||||
static const uint8_t T9 = 9;
|
|
||||||
static const uint8_t T10 = 10;
|
|
||||||
static const uint8_t T11 = 11;
|
|
||||||
static const uint8_t T12 = 12;
|
|
||||||
static const uint8_t T13 = 13;
|
|
||||||
static const uint8_t T14 = 14;
|
|
||||||
|
|
||||||
static const uint8_t Vext = 36;
|
|
||||||
static const uint8_t LED = 35;
|
|
||||||
static const uint8_t RST_OLED = 21;
|
|
||||||
static const uint8_t SCL_OLED = 18;
|
|
||||||
static const uint8_t SDA_OLED = 17;
|
|
||||||
|
|
||||||
static const uint8_t RST_LoRa = 12;
|
|
||||||
static const uint8_t BUSY_LoRa = 13;
|
|
||||||
static const uint8_t DIO0 = 14;
|
|
||||||
|
|
||||||
#endif /* Pins_Arduino_h */
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
[env:heltec-v4]
|
|
||||||
extends = esp32s3_base
|
|
||||||
board = heltec_v4
|
|
||||||
board_check = true
|
|
||||||
build_flags =
|
|
||||||
${esp32s3_base.build_flags}
|
|
||||||
-D HELTEC_V4
|
|
||||||
-I variants/esp32s3/heltec_v4
|
|
||||||
-D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
|
|
||||||
-D SX126X_MAX_POWER=11
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
#define LED_PIN 35
|
|
||||||
|
|
||||||
#define USE_SSD1306 // Heltec_v4 has an SSD1315 display (compatible with SSD1306 driver)
|
|
||||||
|
|
||||||
#define RESET_OLED 21
|
|
||||||
#define I2C_SDA 17 // I2C pins for this board
|
|
||||||
#define I2C_SCL 18
|
|
||||||
|
|
||||||
#define VEXT_ENABLE 36 // active low, powers the oled display and the lora antenna boost
|
|
||||||
#define BUTTON_PIN 0
|
|
||||||
|
|
||||||
#define ADC_CTRL 37
|
|
||||||
#define ADC_CTRL_ENABLED HIGH
|
|
||||||
#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage
|
|
||||||
#define ADC_CHANNEL ADC1_GPIO1_CHANNEL
|
|
||||||
#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider
|
|
||||||
#define ADC_MULTIPLIER 4.9 * 1.045
|
|
||||||
|
|
||||||
#define USE_SX1262
|
|
||||||
|
|
||||||
#define LORA_DIO0 -1 // a No connect on the SX1262 module
|
|
||||||
#define LORA_RESET 12
|
|
||||||
#define LORA_DIO1 14 // SX1262 IRQ
|
|
||||||
#define LORA_DIO2 13 // SX1262 BUSY
|
|
||||||
#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TCXO is enabled
|
|
||||||
|
|
||||||
#define LORA_SCK 9
|
|
||||||
#define LORA_MISO 11
|
|
||||||
#define LORA_MOSI 10
|
|
||||||
#define LORA_CS 8
|
|
||||||
|
|
||||||
#define SX126X_CS LORA_CS
|
|
||||||
#define SX126X_DIO1 LORA_DIO1
|
|
||||||
#define SX126X_BUSY LORA_DIO2
|
|
||||||
#define SX126X_RESET LORA_RESET
|
|
||||||
|
|
||||||
#define SX126X_DIO2_AS_RF_SWITCH
|
|
||||||
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
|
||||||
|
|
||||||
#define LORA_PA_POWER 7 // power en
|
|
||||||
#define LORA_PA_EN 2
|
|
||||||
#define LORA_PA_TX_EN 46 // enable tx
|
|
||||||
|
|
||||||
/*
|
|
||||||
* GPS pins
|
|
||||||
*/
|
|
||||||
#define GPS_L76K
|
|
||||||
#define PIN_GPS_RESET (42) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K
|
|
||||||
#define GPS_RESET_MODE LOW
|
|
||||||
#define PIN_GPS_EN (34)
|
|
||||||
#define GPS_EN_ACTIVE LOW
|
|
||||||
#define PERIPHERAL_WARMUP_MS 1000 // Make sure I2C QuickLink has stable power before continuing
|
|
||||||
#define PIN_GPS_STANDBY (40) // An output to wake GPS, low means allow sleep, high means force wake
|
|
||||||
#define PIN_GPS_PPS (41)
|
|
||||||
// Seems to be missing on this new board
|
|
||||||
#define GPS_TX_PIN (38) // This is for bits going TOWARDS the CPU
|
|
||||||
#define GPS_RX_PIN (39) // This is for bits going TOWARDS the GPS
|
|
||||||
#define GPS_THREAD_INTERVAL 50
|
|
||||||
@@ -26,7 +26,6 @@ extends = env:picomputer-s3
|
|||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
${env:picomputer-s3.build_flags}
|
${env:picomputer-s3.build_flags}
|
||||||
-D MESHTASTIC_EXCLUDE_WEBSERVER=1
|
|
||||||
-D INPUTDRIVER_MATRIX_TYPE=1
|
-D INPUTDRIVER_MATRIX_TYPE=1
|
||||||
-D USE_PIN_BUZZER=PIN_BUZZER
|
-D USE_PIN_BUZZER=PIN_BUZZER
|
||||||
-D USE_SX127x
|
-D USE_SX127x
|
||||||
|
|||||||
@@ -62,6 +62,6 @@
|
|||||||
// #define PCF8563_RTC 0x51 //Putting definitions in variant. h does not compile correctly
|
// #define PCF8563_RTC 0x51 //Putting definitions in variant. h does not compile correctly
|
||||||
|
|
||||||
// has 32768 Hz crystal
|
// has 32768 Hz crystal
|
||||||
#define HAS_32768HZ 1
|
#define HAS_32768HZ
|
||||||
|
|
||||||
#define USE_SH1106
|
#define USE_SH1106
|
||||||
@@ -4,7 +4,15 @@ extends = nrf52840_base
|
|||||||
board = meshtiny
|
board = meshtiny
|
||||||
board_level = extra
|
board_level = extra
|
||||||
build_flags = ${nrf52840_base.build_flags} -Ivariants/nrf52840/meshtiny -D MESHTINY
|
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
|
-D MESHTASTIC_EXCLUDE_GPS=1
|
||||||
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/meshtiny>
|
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/meshtiny>
|
||||||
lib_deps =
|
lib_deps =
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
|
|
||||||
#define MESHTINY
|
#define MESHTINY
|
||||||
|
|
||||||
|
// #define RAK4630
|
||||||
|
|
||||||
/** Master clock frequency */
|
/** Master clock frequency */
|
||||||
#define VARIANT_MCK (64000000ul)
|
#define VARIANT_MCK (64000000ul)
|
||||||
|
|
||||||
@@ -74,10 +76,11 @@ extern "C" {
|
|||||||
* Buttons
|
* Buttons
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define CANCEL_BUTTON_PIN 9
|
#define PIN_BUTTON1 9
|
||||||
#define BUTTON_NEED_PULLUP
|
#define BUTTON_NEED_PULLUP
|
||||||
#define CANCEL_BUTTON_ACTIVE_LOW true
|
#define PIN_BUTTON2 12
|
||||||
#define CANCEL_BUTTON_ACTIVE_PULLUP false
|
#define PIN_BUTTON3 24
|
||||||
|
#define PIN_BUTTON4 25
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Analog pins
|
* Analog pins
|
||||||
|
|||||||
@@ -16,11 +16,3 @@ lib_deps =
|
|||||||
debug_tool = jlink
|
debug_tool = jlink
|
||||||
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
|
; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm)
|
||||||
;upload_protocol = jlink
|
;upload_protocol = jlink
|
||||||
|
|
||||||
; Seeed Xiao BLE but with GPS undefined, and therefore i2c active
|
|
||||||
[env:seeed_xiao_nrf52840_kit_i2c]
|
|
||||||
extends = env:seeed_xiao_nrf52840_kit
|
|
||||||
board_level = extra
|
|
||||||
build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags}
|
|
||||||
-DSEEED_XIAO_NRF52840_KIT
|
|
||||||
build_unflags = -DGPS_L76K
|
|
||||||
|
|||||||
Reference in New Issue
Block a user