Compare commits

..
Author SHA1 Message Date
Json-181andAustin Lane 7e03cae9b3 Swap Mesh-Ham for Mesh-Sun for Hamcation2026 Event Firmware (#9535)
* Swap Sun-Ham for Mesh-Sun

---------

Co-authored-by: Austin Lane <vidplace7@gmail.com>
2026-02-04 21:18:28 -05:00
Austin Lane 04b3f8cef4 Disable webserver for OG ESP32
We need that dram!
2026-02-04 20:14:31 -05:00
fcfd8a4560 Add HamCation Event Firmware Splash Screen Logos (#9092)
* HamCation 2026 userprefs

* Fix minimumTimeThreshold type for event_mode

* Add HamCation Firmware Logo

Add .png logo and .svg files for records.

* Sun Ham XBM

* Updated Branding with Official HamCation Logo.

Got official HamCation logo .svgs, thanks to team at HamCation. Have now updated branding to reflect.

* Update XBM to *MeshSun*

* corrected missing bit

* image scale

---------

Co-authored-by: Austin Lane <vidplace7@gmail.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-02-04 17:48:55 -06:00
Austin Lane 33e3dba47a Sun Ham XBM 2026-02-04 15:43:47 -05:00
Austin Lane f2069d5b74 HamCation 2026 userprefs 2026-02-04 15:43:47 -05:00
390 changed files with 2572 additions and 8953 deletions
-1
View File
@@ -1 +0,0 @@
use nix
-213
View File
@@ -1,213 +0,0 @@
name: Issue Triage (Models)
on:
issues:
types: [opened]
permissions:
issues: write
models: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.issue.number }}
cancel-in-progress: true
jobs:
triage:
if: ${{ github.repository == 'meshtastic/firmware' && github.event.issue.user.type != 'Bot' }}
runs-on: ubuntu-latest
steps:
# ─────────────────────────────────────────────────────────────────────────
# Step 1: Quality check (spam/AI-slop detection) - runs first, exits early if spam
# ─────────────────────────────────────────────────────────────────────────
- name: Detect spam or low-quality content
uses: actions/ai-inference@v2
id: quality
continue-on-error: true
with:
max-tokens: 20
prompt: |
Is this GitHub issue spam, AI-generated slop, or low quality?
Title: ${{ github.event.issue.title }}
Body: ${{ github.event.issue.body }}
Respond with exactly one of: spam, ai-generated, needs-review, ok
system-prompt: You detect spam and low-quality contributions. Be conservative - only flag obvious spam or AI slop.
model: openai/gpt-4o-mini
- name: Apply quality label if needed
if: steps.quality.outputs.response != '' && steps.quality.outputs.response != 'ok'
uses: actions/github-script@v8
env:
QUALITY_LABEL: ${{ steps.quality.outputs.response }}
with:
script: |
const label = (process.env.QUALITY_LABEL || '').trim().toLowerCase();
const labelMeta = {
'spam': { color: 'd73a4a', description: 'Possible spam' },
'ai-generated': { color: 'fbca04', description: 'Possible AI-generated low-quality content' },
'needs-review': { color: 'f9d0c4', description: 'Needs human review' },
};
const meta = labelMeta[label];
if (!meta) return;
// Ensure label exists
try {
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
} catch (e) {
if (e.status !== 404) throw e;
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
}
// Apply label
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.issue.number, labels: [label] });
// Set output to skip remaining steps
core.setOutput('is_spam', 'true');
# ─────────────────────────────────────────────────────────────────────────
# Step 2: Duplicate detection - only if not spam
# ─────────────────────────────────────────────────────────────────────────
- name: Detect duplicate issues
if: steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == ''
uses: pelikhan/action-genai-issue-dedup@bdb3b5d9451c1090ffcdf123d7447a5e7c7a2528 # v0.0.19
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# ─────────────────────────────────────────────────────────────────────────
# Step 3: Completeness check + auto-labeling (combined into one AI call)
# ─────────────────────────────────────────────────────────────────────────
- name: Determine if completeness check should be skipped
if: steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == ''
uses: actions/github-script@v8
id: check-skip
with:
script: |
const title = (context.payload.issue.title || '').toLowerCase();
const labels = (context.payload.issue.labels || []).map(label => label.name);
const hasFeatureRequest = title.includes('feature request');
const hasEnhancement = labels.includes('enhancement');
const shouldSkip = hasFeatureRequest && hasEnhancement;
core.setOutput('should_skip', shouldSkip ? 'true' : 'false');
- name: Analyze issue completeness and determine labels
if: (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') && steps.check-skip.outputs.should_skip != 'true'
uses: actions/ai-inference@v2
id: analysis
continue-on-error: true
with:
prompt: |
Analyze this GitHub issue for completeness and determine if it needs labels.
IMPORTANT: Distinguish between:
- Device/firmware bugs (crashes, reboots, lockups, radio/GPS/display/power issues) - these need device logs
- Build/release/packaging issues (missing files, CI failures, download problems) - these do NOT need device logs
- Documentation or website issues - these do NOT need device logs
If this is a device/firmware bug, request device logs and explain how to get them:
Web Flasher logs:
- Go to https://flasher.meshtastic.org
- Connect the device via USB and click Connect
- Open the device console/log output, reproduce the problem, then copy/download and attach/paste the logs
Meshtastic CLI logs:
- Run: meshtastic --port <serial-port> --noproto
- Reproduce the problem, then copy/paste the terminal output
Also request key context if missing: device model/variant, firmware version, region, steps to reproduce, expected vs actual.
Respond ONLY with valid JSON (no markdown, no code fences):
{"complete": true, "comment": "", "label": "none"}
OR
{"complete": false, "comment": "Your helpful comment", "label": "needs-logs"}
Use "needs-logs" ONLY if this is a device/firmware bug AND no logs are attached.
Use "needs-info" if basic info like firmware version or steps to reproduce are missing.
Use "none" if the issue is complete, is a feature request, or is a build/CI/packaging issue.
Title: ${{ github.event.issue.title }}
Body: ${{ github.event.issue.body }}
system-prompt: You are a helpful assistant that triages GitHub issues. Be conservative with labels. Only request device logs for actual device/firmware bugs, not for build/release/CI issues.
model: openai/gpt-4o-mini
- name: Process analysis result
if: (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') && steps.check-skip.outputs.should_skip != 'true' && steps.analysis.outputs.response != ''
uses: actions/github-script@v8
id: process
env:
AI_RESPONSE: ${{ steps.analysis.outputs.response }}
with:
script: |
let raw = (process.env.AI_RESPONSE || '').trim();
// Strip markdown code fences if present
raw = raw.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, '').trim();
let complete = true;
let comment = '';
let label = 'none';
try {
const parsed = JSON.parse(raw);
complete = !!parsed.complete;
comment = (parsed.comment ?? '').toString().trim();
label = (parsed.label ?? 'none').toString().trim().toLowerCase();
} catch {
// If JSON parse fails, log warning and don't comment (avoid posting raw JSON)
console.log('Failed to parse AI response as JSON:', raw);
complete = true;
comment = '';
label = 'none';
}
// Validate label
const allowedLabels = new Set(['needs-logs', 'needs-info', 'none']);
if (!allowedLabels.has(label)) label = 'none';
// Only comment if we have a valid parsed comment (not raw JSON)
const shouldComment = !complete && comment.length > 0 && !comment.startsWith('{');
core.setOutput('should_comment', shouldComment ? 'true' : 'false');
core.setOutput('comment_body', comment);
core.setOutput('label', label);
- name: Apply triage label
if: steps.process.outputs.label != '' && steps.process.outputs.label != 'none'
uses: actions/github-script@v8
env:
LABEL_NAME: ${{ steps.process.outputs.label }}
with:
script: |
const label = process.env.LABEL_NAME;
const labelMeta = {
'needs-logs': { color: 'cfd3d7', description: 'Device logs requested for triage' },
'needs-info': { color: 'f9d0c4', description: 'More information requested for triage' },
};
const meta = labelMeta[label];
if (!meta) return;
// Ensure label exists
try {
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
} catch (e) {
if (e.status !== 404) throw e;
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
}
// Apply label
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.issue.number, labels: [label] });
- name: Comment on issue
if: steps.process.outputs.should_comment == 'true'
uses: actions/github-script@v8
env:
COMMENT_BODY: ${{ steps.process.outputs.comment_body }}
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: process.env.COMMENT_BODY
});
-139
View File
@@ -1,139 +0,0 @@
name: PR Triage (Models)
on:
pull_request_target:
types: [opened]
permissions:
pull-requests: write
issues: write
models: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
triage:
if: ${{ github.repository == 'meshtastic/firmware' && github.event.pull_request.user.type != 'Bot' }}
runs-on: ubuntu-latest
steps:
# ─────────────────────────────────────────────────────────────────────────
# Step 1: Check if PR already has automation/type labels (skip if so)
# ─────────────────────────────────────────────────────────────────────────
- name: Check existing labels
uses: actions/github-script@v8
id: check-labels
with:
script: |
const skipLabels = new Set(['automation']);
const typeLabels = new Set(['bugfix', 'hardware-support', 'enhancement', 'dependencies', 'submodules', 'github_actions', 'trunk', 'cleanup']);
const prLabels = context.payload.pull_request.labels.map(l => l.name);
const shouldSkipAll = prLabels.some(l => skipLabels.has(l));
const hasTypeLabel = prLabels.some(l => typeLabels.has(l));
core.setOutput('skip_all', shouldSkipAll ? 'true' : 'false');
core.setOutput('has_type_label', hasTypeLabel ? 'true' : 'false');
# ─────────────────────────────────────────────────────────────────────────
# Step 2: Quality check (spam/AI-slop detection)
# ─────────────────────────────────────────────────────────────────────────
- name: Detect spam or low-quality content
if: steps.check-labels.outputs.skip_all != 'true'
uses: actions/ai-inference@v2
id: quality
continue-on-error: true
with:
max-tokens: 20
prompt: |
Is this GitHub pull request spam, AI-generated slop, or low quality?
Title: ${{ github.event.pull_request.title }}
Body: ${{ github.event.pull_request.body }}
Respond with exactly one of: spam, ai-generated, needs-review, ok
system-prompt: You detect spam and low-quality contributions. Be conservative - only flag obvious spam or AI slop.
model: openai/gpt-4o-mini
- name: Apply quality label if needed
if: steps.check-labels.outputs.skip_all != 'true' && steps.quality.outputs.response != '' && steps.quality.outputs.response != 'ok'
uses: actions/github-script@v8
id: quality-label
env:
QUALITY_LABEL: ${{ steps.quality.outputs.response }}
with:
script: |
const label = (process.env.QUALITY_LABEL || '').trim().toLowerCase();
const labelMeta = {
'spam': { color: 'd73a4a', description: 'Possible spam' },
'ai-generated': { color: 'fbca04', description: 'Possible AI-generated low-quality content' },
'needs-review': { color: 'f9d0c4', description: 'Needs human review' },
};
const meta = labelMeta[label];
if (!meta) return;
// Ensure label exists
try {
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
} catch (e) {
if (e.status !== 404) throw e;
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
}
// Apply label
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: [label] });
core.setOutput('is_spam', 'true');
# ─────────────────────────────────────────────────────────────────────────
# Step 3: Auto-label PR type (bugfix/hardware-support/enhancement)
# Only skip for spam/ai-generated; still classify needs-review PRs
# ─────────────────────────────────────────────────────────────────────────
- name: Classify PR for labeling
if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && steps.quality.outputs.response != 'spam' && steps.quality.outputs.response != 'ai-generated'
uses: actions/ai-inference@v2
id: classify
continue-on-error: true
with:
max-tokens: 30
prompt: |
Classify this pull request into exactly one category.
Return exactly one of: bugfix, hardware-support, enhancement
Use bugfix if it fixes a bug, crash, or incorrect behavior.
Use hardware-support if it adds or improves support for a specific hardware device/variant.
Use enhancement if it adds a new feature, improves performance, or refactors code.
Title: ${{ github.event.pull_request.title }}
Body: ${{ github.event.pull_request.body }}
system-prompt: You classify pull requests into categories. Be conservative and pick the most appropriate single label.
model: openai/gpt-4o-mini
- name: Apply type label
if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && steps.classify.outputs.response != ''
uses: actions/github-script@v8
env:
TYPE_LABEL: ${{ steps.classify.outputs.response }}
with:
script: |
const label = (process.env.TYPE_LABEL || '').trim().toLowerCase();
const labelMeta = {
'bugfix': { color: 'd73a4a', description: 'Bug fix' },
'hardware-support': { color: '0e8a16', description: 'Hardware support addition or improvement' },
'enhancement': { color: 'a2eeef', description: 'New feature or enhancement' },
};
const meta = labelMeta[label];
if (!meta) return;
// Ensure label exists
try {
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
} catch (e) {
if (e.status !== 404) throw e;
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
}
// Apply label
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: [label] });
-3
View File
@@ -50,6 +50,3 @@ idf_component.yml
CMakeLists.txt CMakeLists.txt
/sdkconfig.* /sdkconfig.*
.dummy/* .dummy/*
# PYTHONPATH used by the Nix shell
.python3
+7 -7
View File
@@ -8,18 +8,18 @@ plugins:
uri: https://github.com/trunk-io/plugins uri: https://github.com/trunk-io/plugins
lint: lint:
enabled: enabled:
- checkov@3.2.501 - checkov@3.2.497
- renovate@43.8.5 - renovate@42.84.2
- prettier@3.8.1 - prettier@3.8.0
- trufflehog@3.93.3 - trufflehog@3.92.5
- yamllint@1.38.0 - yamllint@1.38.0
- bandit@1.9.3 - bandit@1.9.3
- trivy@0.69.1 - trivy@0.68.2
- taplo@0.10.0 - taplo@0.10.0
- ruff@0.15.0 - ruff@0.14.13
- isort@7.0.0 - isort@7.0.0
- markdownlint@0.47.0 - markdownlint@0.47.0
- oxipng@10.1.0 - oxipng@10.0.0
- svgo@4.0.0 - svgo@4.0.0
- actionlint@1.7.10 - actionlint@1.7.10
- flake8@7.3.0 - flake8@7.3.0
@@ -1,23 +0,0 @@
Lora:
Module: sx1262
CS: 0
IRQ: 6
Reset: 1
Busy: 4
RXen: 2
DIO2_AS_RF_SWITCH: true
spidev: ch341
USB_PID: 0x5512
USB_VID: 0x1A86
DIO3_TCXO_VOLTAGE: true
# USB_Serialnum: 12345678
SX126X_MAX_POWER: 22
# Reduce output power to improve EMI
NUM_PA_POINTS: 22
TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7
# Note: This module integrates an additional PA to achieve higher output power.
# The 'power' parameter here does not represent the actual RF output.
# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm).
# Each array element corresponds to the additional gain when that input level is set,
# The effective RF output is: Pout ≈ Pset + TX_GAIN_LORA[index].
# Please refer to https://github.com/linser233/uMesh/blob/main/RF_Power.md for detailed information.
+15
View File
@@ -0,0 +1,15 @@
Lora:
Module: sx1262
CS: 0
IRQ: 6
Reset: 1
Busy: 4
RXen: 2
DIO2_AS_RF_SWITCH: true
spidev: ch341
USB_PID: 0x5512
USB_VID: 0x1A86
DIO3_TCXO_VOLTAGE: true
# USB_Serialnum: 12345678
SX126X_MAX_POWER: 30
# Reduce output power to improve EMI
@@ -1,23 +0,0 @@
Lora:
Module: sx1268
CS: 0
IRQ: 6
Reset: 1
Busy: 4
RXen: 2
DIO2_AS_RF_SWITCH: true
spidev: ch341
USB_PID: 0x5512
USB_VID: 0x1A86
DIO3_TCXO_VOLTAGE: true
# USB_Serialnum: 12345678
SX126X_MAX_POWER: 22
# Reduce output power to improve EMI
NUM_PA_POINTS: 22
TX_GAIN_LORA: 12, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 8, 8, 7
# Note: This module integrates an additional PA to achieve higher output power.
# The 'power' parameter here does not represent the actual RF output.
# TX_GAIN_LORA defines the gain offset applied at each SX1262 input power step (1–22 dBm).
# Each array element corresponds to the additional gain when that input level is set,
# The effective RF output is: Pout ≈ Pset + TX_GAIN_LORA[index].
# Please refer to https://github.com/linser233/uMesh/blob/main/RF_Power.md for detailed information.
+15
View File
@@ -0,0 +1,15 @@
Lora:
Module: sx1268
CS: 0
IRQ: 6
Reset: 1
Busy: 4
RXen: 2
DIO2_AS_RF_SWITCH: true
spidev: ch341
USB_PID: 0x5512
USB_VID: 0x1A86
DIO3_TCXO_VOLTAGE: true
# USB_Serialnum: 12345678
SX126X_MAX_POWER: 30
# Reduce output power to improve EMI
+10 -2
View File
@@ -156,8 +156,16 @@ IF %BPS_RESET% EQU 1 (
SET "PROGNAME=!FILENAME:.factory.bin=!" SET "PROGNAME=!FILENAME:.factory.bin=!"
CALL :LOG_MESSAGE DEBUG "Computed PROGNAME: !PROGNAME!" CALL :LOG_MESSAGE DEBUG "Computed PROGNAME: !PROGNAME!"
@REM Determine OTA filename based on MCU type (unified OTA format) IF "__!MCU!__" == "__esp32s3__" (
SET "OTA_FILENAME=mt-!MCU!-ota.bin" @REM We are working with ESP32-S3
SET "OTA_FILENAME=bleota-s3.bin"
) ELSE IF "__!MCU!__" == "__esp32c3__" (
@REM We are working with ESP32-C3
SET "OTA_FILENAME=bleota-c3.bin"
) ELSE (
@REM Everything else
SET "OTA_FILENAME=bleota.bin"
)
CALL :LOG_MESSAGE DEBUG "Set OTA_FILENAME to: !OTA_FILENAME!" CALL :LOG_MESSAGE DEBUG "Set OTA_FILENAME to: !OTA_FILENAME!"
@REM Set SPIFFS filename with "littlefs-" prefix. @REM Set SPIFFS filename with "littlefs-" prefix.
+14 -21
View File
@@ -32,19 +32,6 @@ if ! command -v jq >/dev/null 2>&1; then
exit 1 exit 1
fi fi
# esptool v5 supports commands with dashes and deprecates commands with
# underscores. Prior versions only support commands with underscores
if ${ESPTOOL_CMD} | grep --quiet write-flash
then
ESPTOOL_WRITE_FLASH=write-flash
ESPTOOL_ERASE_FLASH=erase-flash
ESPTOOL_READ_FLASH_STATUS=read-flash-status
else
ESPTOOL_WRITE_FLASH=write_flash
ESPTOOL_ERASE_FLASH=erase_flash
ESPTOOL_READ_FLASH_STATUS=read_flash_status
fi
set -e set -e
# Usage info # Usage info
@@ -96,8 +83,8 @@ while [ $# -gt 0 ]; do
done done
if [[ $BPS_RESET == true ]]; then if [[ $BPS_RESET == true ]]; then
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS} $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
exit 0 exit 0
fi fi
[ -z "$FILENAME" ] && [ -n "$1" ] && { [ -z "$FILENAME" ] && [ -n "$1" ] && {
@@ -131,8 +118,14 @@ if [[ -f "$FILENAME" && "$FILENAME" == *.factory.bin ]]; then
exit 1 exit 1
fi fi
# Determine OTA filename based on MCU type (unified OTA format) # Determine OTA filename based on MCU type
OTAFILE="mt-${MCU}-ota.bin" if [ "$MCU" == "esp32s3" ]; then
OTAFILE=bleota-s3.bin
elif [ "$MCU" == "esp32c3" ]; then
OTAFILE=bleota-c3.bin
else
OTAFILE=bleota.bin
fi
# Set SPIFFS filename with "littlefs-" prefix. # Set SPIFFS filename with "littlefs-" prefix.
SPIFFSFILE="littlefs-${PROGNAME/firmware-/}.bin" SPIFFSFILE="littlefs-${PROGNAME/firmware-/}.bin"
@@ -151,12 +144,12 @@ if [[ -f "$FILENAME" && "$FILENAME" == *.factory.bin ]]; then
fi fi
echo "Trying to flash ${FILENAME}, but first erasing and writing system information" echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
$ESPTOOL_CMD ${ESPTOOL_ERASE_FLASH} $ESPTOOL_CMD erase-flash
$ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $FIRMWARE_OFFSET "${FILENAME}" $ESPTOOL_CMD write-flash $FIRMWARE_OFFSET "${FILENAME}"
echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}" echo "Trying to flash ${OTAFILE} at offset ${OTA_OFFSET}"
$ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OTA_OFFSET "${OTAFILE}" $ESPTOOL_CMD write_flash $OTA_OFFSET "${OTAFILE}"
echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}" echo "Trying to flash ${SPIFFSFILE}, at offset ${OFFSET}"
$ESPTOOL_CMD ${ESPTOOL_WRITE_FLASH} $OFFSET "${SPIFFSFILE}" $ESPTOOL_CMD write_flash $OFFSET "${SPIFFSFILE}"
else else
show_help show_help
+2 -13
View File
@@ -20,17 +20,6 @@ else
exit 1 exit 1
fi fi
# esptool v5 supports commands with dashes and deprecates commands with
# underscores. Prior versions only support commands with underscores
if ${ESPTOOL_CMD} | grep --quiet write-flash
then
ESPTOOL_WRITE_FLASH=write-flash
ESPTOOL_READ_FLASH_STATUS=read-flash-status
else
ESPTOOL_WRITE_FLASH=write_flash
ESPTOOL_READ_FLASH_STATUS=read_flash_status
fi
# Usage info # Usage info
show_help() { show_help() {
cat << EOF cat << EOF
@@ -80,7 +69,7 @@ done
shift "$((OPTIND-1))" shift "$((OPTIND-1))"
if [ "$CHANGE_MODE" = true ]; then if [ "$CHANGE_MODE" = true ]; then
$ESPTOOL_CMD --baud $RESET_BAUD --after no_reset ${ESPTOOL_READ_FLASH_STATUS} $ESPTOOL_CMD --baud $RESET_BAUD --after no_reset read_flash_status
exit 0 exit 0
fi fi
@@ -91,7 +80,7 @@ fi
if [[ -f "$FILENAME" && "$FILENAME" != *.factory.bin ]]; then if [[ -f "$FILENAME" && "$FILENAME" != *.factory.bin ]]; then
echo "Trying to flash update ${FILENAME}" echo "Trying to flash update ${FILENAME}"
$ESPTOOL_CMD --baud $FLASH_BAUD ${ESPTOOL_WRITE_FLASH} $UPDATE_OFFSET "${FILENAME}" $ESPTOOL_CMD --baud $FLASH_BAUD write-flash $UPDATE_OFFSET "${FILENAME}"
else else
show_help show_help
echo "Invalid file: ${FILENAME}" echo "Invalid file: ${FILENAME}"
@@ -87,12 +87,6 @@
</screenshots> </screenshots>
<releases> <releases>
<release version="2.7.20" date="2026-02-11">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.20</url>
</release>
<release version="2.7.19" date="2026-01-22">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.19</url>
</release>
<release version="2.7.18" date="2026-01-02"> <release version="2.7.18" date="2026-01-02">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18</url> <url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18</url>
</release> </release>
+1 -2
View File
@@ -32,8 +32,7 @@
"connectivity": ["bluetooth"], "connectivity": ["bluetooth"],
"debug": { "debug": {
"jlink_device": "nRF52840_xxAA", "jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd", "svd_path": "nrf52840.svd"
"openocd_target": "nrf52840-mdk-rs"
}, },
"frameworks": ["arduino"], "frameworks": ["arduino"],
"name": "Minesemi ME25LS01", "name": "Minesemi ME25LS01",
+1 -2
View File
@@ -33,8 +33,7 @@
"connectivity": ["bluetooth"], "connectivity": ["bluetooth"],
"debug": { "debug": {
"jlink_device": "nRF52840_xxAA", "jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd", "svd_path": "nrf52840.svd"
"openocd_target": "nrf52840-mdk-rs"
}, },
"frameworks": ["arduino"], "frameworks": ["arduino"],
"name": "MeshLink", "name": "MeshLink",
-51
View File
@@ -1,51 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DMINIMESH_LITE -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"]
],
"usb_product": "Minimesh Lite",
"mcu": "nrf52840",
"variant": "dls_Minimesh_Lite",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "Minimesh Lite",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["nrfutil", "jlink", "nrfjprog", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://deeplabstudio.com",
"vendor": "Deeplab Studio"
}
+1 -2
View File
@@ -32,8 +32,7 @@
"connectivity": ["bluetooth"], "connectivity": ["bluetooth"],
"debug": { "debug": {
"jlink_device": "nRF52840_xxAA", "jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd", "svd_path": "nrf52840.svd"
"openocd_target": "nrf52840-mdk-rs"
}, },
"frameworks": ["arduino"], "frameworks": ["arduino"],
"name": "MINEWSEMI_MS24SF1_SX1262", "name": "MINEWSEMI_MS24SF1_SX1262",
+1 -2
View File
@@ -32,8 +32,7 @@
"connectivity": ["bluetooth"], "connectivity": ["bluetooth"],
"debug": { "debug": {
"jlink_device": "nRF52840_xxAA", "jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd", "svd_path": "nrf52840.svd"
"openocd_target": "nrf52840-mdk-rs"
}, },
"frameworks": ["arduino"], "frameworks": ["arduino"],
"name": "BQ nRF52840", "name": "BQ nRF52840",
+1 -2
View File
@@ -33,8 +33,7 @@
"connectivity": ["bluetooth"], "connectivity": ["bluetooth"],
"debug": { "debug": {
"jlink_device": "nRF52840_xxAA", "jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd", "svd_path": "nrf52840.svd"
"openocd_target": "nrf52840-mdk-rs"
}, },
"frameworks": ["arduino"], "frameworks": ["arduino"],
"name": "ProMicro compatible nRF52840", "name": "ProMicro compatible nRF52840",
+1 -2
View File
@@ -33,8 +33,7 @@
"connectivity": ["bluetooth"], "connectivity": ["bluetooth"],
"debug": { "debug": {
"jlink_device": "nRF52840_xxAA", "jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd", "svd_path": "nrf52840.svd"
"openocd_target": "nrf52840-mdk-rs"
}, },
"frameworks": ["arduino"], "frameworks": ["arduino"],
"name": "Seeed T1000-E", "name": "Seeed T1000-E",
+1 -2
View File
@@ -25,8 +25,7 @@
"connectivity": ["bluetooth"], "connectivity": ["bluetooth"],
"debug": { "debug": {
"jlink_device": "nRF52840_xxAA", "jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd", "svd_path": "nrf52840.svd"
"openocd_target": "nrf52840-mdk-rs"
}, },
"frameworks": ["arduino", "freertos"], "frameworks": ["arduino", "freertos"],
"name": "Seeed WIO WM1110", "name": "Seeed WIO WM1110",
+1 -2
View File
@@ -32,8 +32,7 @@
"connectivity": ["bluetooth"], "connectivity": ["bluetooth"],
"debug": { "debug": {
"jlink_device": "nRF52840_xxAA", "jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd", "svd_path": "nrf52840.svd"
"openocd_target": "nrf52840-mdk-rs"
}, },
"frameworks": ["arduino"], "frameworks": ["arduino"],
"name": "Seeed WIO WM1110", "name": "Seeed WIO WM1110",
+1 -2
View File
@@ -32,8 +32,7 @@
"connectivity": ["bluetooth"], "connectivity": ["bluetooth"],
"debug": { "debug": {
"jlink_device": "nRF52840_xxAA", "jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd", "svd_path": "nrf52840.svd"
"openocd_target": "nrf52840-mdk-rs"
}, },
"frameworks": ["arduino"], "frameworks": ["arduino"],
"name": "Seeed WIO WM1110", "name": "Seeed WIO WM1110",
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

-12
View File
@@ -1,15 +1,3 @@
meshtasticd (2.7.20.0) unstable; urgency=medium
* Version 2.7.20
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 11 Feb 2026 12:19:54 +0000
meshtasticd (2.7.19.0) unstable; urgency=medium
* Version 2.7.19
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Thu, 22 Jan 2026 22:17:40 +0000
meshtasticd (2.7.18.0) unstable; urgency=medium meshtasticd (2.7.18.0) unstable; urgency=medium
* Version 2.7.18 * Version 2.7.18
Generated
-44
View File
@@ -1,44 +0,0 @@
{
"nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1767039857,
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
"owner": "NixOS",
"repo": "flake-compat",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "flake-compat",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1766314097,
"narHash": "sha256-laJftWbghBehazn/zxVJ8NdENVgjccsWAdAqKXhErrM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "306ea70f9eb0fb4e040f8540e2deab32ed7e2055",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
-66
View File
@@ -1,66 +0,0 @@
{
description = "Nix flake to compile Meshtastic firmware";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
# Shim to make flake.nix work with stable Nix.
flake-compat = {
url = "github:NixOS/flake-compat";
flake = false;
};
};
outputs =
inputs:
let
lib = inputs.nixpkgs.lib;
forAllSystems =
fn:
lib.genAttrs lib.systems.flakeExposed (
system:
fn {
pkgs = import inputs.nixpkgs {
inherit system;
};
inherit system;
}
);
in
{
devShells = forAllSystems (
{ pkgs, ... }:
let
python3 = pkgs.python312.withPackages (
ps: with ps; [
google
]
);
in
{
default = pkgs.mkShell {
buildInputs = with pkgs; [
python3
platformio
];
shellHook = ''
# Set up PlatformIO to use a local core directory.
export PLATFORMIO_CORE_DIR=$PWD/.platformio
# Tell pip to put packages into $PIP_PREFIX instead of the usual
# location. This is especially necessary under NixOS to avoid having
# pip trying to write to the read-only Nix store. For more info,
# see https://wiki.nixos.org/wiki/Python
export PIP_PREFIX=$PWD/.python3
export PYTHONPATH="$PIP_PREFIX/${python3.sitePackages}"
export PATH="$PIP_PREFIX/bin:$PATH"
# Avoids reproducibility issues with some Python packages
# See https://nixos.org/manual/nixpkgs/stable/#python-setup.py-bdist_wheel-cannot-create-.whl
unset SOURCE_DATE_EPOCH
'';
};
}
);
};
}
+6 -8
View File
@@ -56,7 +56,6 @@ build_flags = -Wno-missing-field-initializers
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1 -DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
-DMESHTASTIC_EXCLUDE_POWERMON=1 -DMESHTASTIC_EXCLUDE_POWERMON=1
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage -D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
-DLED_BUILTIN=-1
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now #-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
#-D OLED_PL=1 #-D OLED_PL=1
#-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs #-D DEBUG_HEAP=1 ; uncomment to add free heap space / memory leak debugging logs
@@ -66,7 +65,7 @@ monitor_speed = 115200
monitor_filters = direct monitor_filters = direct
lib_deps = lib_deps =
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master # renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/21e484f409cde18d44012caef84c244eb5ca28f3.zip https://github.com/meshtastic/esp8266-oled-ssd1306/archive/b34c6817c25d6faabb3a8a162b5d14fb75395433.zip
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master # renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master # renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
@@ -120,7 +119,7 @@ lib_deps =
[device-ui_base] [device-ui_base]
lib_deps = lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master # renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
https://github.com/meshtastic/device-ui/archive/6c75195e9987b7a49563232234f2f868dd343cae.zip https://github.com/meshtastic/device-ui/archive/3480b731d28b10d73414cf0dd7975bff745de8cf.zip
; Common libs for environmental measurements in telemetry module ; Common libs for environmental measurements in telemetry module
[environmental_base] [environmental_base]
@@ -144,7 +143,7 @@ lib_deps =
# renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219 # renovate: datasource=custom.pio depName=Adafruit INA219 packageName=adafruit/library/Adafruit INA219
adafruit/Adafruit INA219@1.2.3 adafruit/Adafruit INA219@1.2.3
# renovate: datasource=custom.pio depName=Adafruit MPU6050 packageName=adafruit/library/Adafruit MPU6050 # renovate: datasource=custom.pio depName=Adafruit MPU6050 packageName=adafruit/library/Adafruit MPU6050
adafruit/Adafruit MPU6050@2.2.9 adafruit/Adafruit MPU6050@2.2.6
# renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH # renovate: datasource=custom.pio depName=Adafruit LIS3DH packageName=adafruit/library/Adafruit LIS3DH
adafruit/Adafruit LIS3DH@1.3.0 adafruit/Adafruit LIS3DH@1.3.0
# renovate: datasource=custom.pio depName=Adafruit AHTX0 packageName=adafruit/library/Adafruit AHTX0 # renovate: datasource=custom.pio depName=Adafruit AHTX0 packageName=adafruit/library/Adafruit AHTX0
@@ -210,10 +209,9 @@ lib_deps =
# renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master
https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip
# renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core
sensirion/Sensirion Core@0.7.3 sensirion/Sensirion Core@0.7.2
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
sensirion/Sensirion I2C SCD4x@1.1.0 sensirion/Sensirion I2C SCD4x@1.1.0
; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets) ; Same as environmental_extra but without BSEC (saves ~3.5KB DRAM for original ESP32 targets)
[environmental_extra_no_bsec] [environmental_extra_no_bsec]
lib_deps = lib_deps =
@@ -238,6 +236,6 @@ lib_deps =
# renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master # renovate: datasource=git-refs depName=meshtastic-DFRobot_LarkWeatherStation packageName=https://github.com/meshtastic/DFRobot_LarkWeatherStation gitBranch=master
https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip https://github.com/meshtastic/DFRobot_LarkWeatherStation/archive/4de3a9cadef0f6a5220a8a906cf9775b02b0040d.zip
# renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core # renovate: datasource=custom.pio depName=Sensirion Core packageName=sensirion/library/Sensirion Core
sensirion/Sensirion Core@0.7.3 sensirion/Sensirion Core@0.7.2
# renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x # renovate: datasource=custom.pio depName=Sensirion I2C SCD4x packageName=sensirion/library/Sensirion I2C SCD4x
sensirion/Sensirion I2C SCD4x@1.1.0 sensirion/Sensirion I2C SCD4x@1.1.0
-12
View File
@@ -1,12 +0,0 @@
(import (
let
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
nodeName = lock.nodes.root.inputs.flake-compat;
in
fetchTarball {
url =
lock.nodes.${nodeName}.locked.url
or "https://github.com/NixOS/flake-compat/archive/${lock.nodes.${nodeName}.locked.rev}.tar.gz";
sha256 = lock.nodes.${nodeName}.locked.narHash;
}
) { src = ./.; }).shellNix
+88 -119
View File
@@ -1,14 +1,11 @@
/** /**
* @file Power.cpp * @file Power.cpp
* @brief This file contains the implementation of the Power class, which is * @brief This file contains the implementation of the Power class, which is responsible for managing power-related functionality
* responsible for managing power-related functionality of the device. It * of the device. It includes battery level sensing, power management unit (PMU) control, and power state machine management. The
* includes battery level sensing, power management unit (PMU) control, and * Power class is used by the main device class to manage power-related functionality.
* power state machine management. The Power class is used by the main device
* class to manage power-related functionality.
* *
* The file also includes implementations of various battery level sensors, such * The file also includes implementations of various battery level sensors, such as the AnalogBatteryLevel class, which assumes
* as the AnalogBatteryLevel class, which assumes the battery voltage is * the battery voltage is attached via a voltage-divider to an analog input.
* attached via a voltage-divider to an analog input.
* *
* This file is part of the Meshtastic project. * This file is part of the Meshtastic project.
* For more information, see: https://meshtastic.org/ * For more information, see: https://meshtastic.org/
@@ -22,7 +19,6 @@
#include "configuration.h" #include "configuration.h"
#include "main.h" #include "main.h"
#include "meshUtils.h" #include "meshUtils.h"
#include "power/PowerHAL.h"
#include "sleep.h" #include "sleep.h"
#if defined(ARCH_PORTDUINO) #if defined(ARCH_PORTDUINO)
@@ -175,12 +171,22 @@ Power *power;
using namespace meshtastic; using namespace meshtastic;
// NRF52 has AREF_VOLTAGE defined in architecture.h but #ifndef AREF_VOLTAGE
// make sure it's included. If something is wrong with NRF52 #if defined(ARCH_NRF52)
// definition - compilation will fail on missing definition /*
#if !defined(AREF_VOLTAGE) && !defined(ARCH_NRF52) * Internal Reference is +/-0.6V, with an adjustable gain of 1/6, 1/5, 1/4,
* 1/3, 1/2 or 1, meaning 3.6, 3.0, 2.4, 1.8, 1.2 or 0.6V for the ADC levels.
*
* External Reference is VDD/4, with an adjustable gain of 1, 2 or 4, meaning
* VDD/4, VDD/2 or VDD for the ADC levels.
*
* Default settings are internal reference with 1/6 gain (GND..3.6V ADC range)
*/
#define AREF_VOLTAGE 3.6
#else
#define AREF_VOLTAGE 3.3 #define AREF_VOLTAGE 3.3
#endif #endif
#endif
/** /**
* If this board has a battery level sensor, set this to a valid implementation * If this board has a battery level sensor, set this to a valid implementation
@@ -227,8 +233,7 @@ static void battery_adcDisable()
#endif #endif
/** /**
* A simple battery level sensor that assumes the battery voltage is attached * A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input
* via a voltage-divider to an analog input
*/ */
class AnalogBatteryLevel : public HasBatteryLevel class AnalogBatteryLevel : public HasBatteryLevel
{ {
@@ -306,8 +311,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
#ifndef BATTERY_SENSE_SAMPLES #ifndef BATTERY_SENSE_SAMPLES
#define BATTERY_SENSE_SAMPLES \ #define BATTERY_SENSE_SAMPLES \
15 // Set the number of samples, it has an effect of increasing sensitivity in 15 // Set the number of samples, it has an effect of increasing sensitivity in complex electromagnetic environment.
// complex electromagnetic environment.
#endif #endif
#ifdef BATTERY_PIN #ifdef BATTERY_PIN
@@ -337,8 +341,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
battery_adcDisable(); battery_adcDisable();
if (!initial_read_done) { if (!initial_read_done) {
// Flush the smoothing filter with an ADC reading, if the reading is // Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
// plausibly correct
if (scaled > last_read_value) if (scaled > last_read_value)
last_read_value = scaled; last_read_value = scaled;
initial_read_done = true; initial_read_done = true;
@@ -347,8 +350,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF
} }
// LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", // LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t)
// BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t) (last_read_value)); // (last_read_value));
} }
return last_read_value; return last_read_value;
#endif // BATTERY_PIN #endif // BATTERY_PIN
@@ -417,8 +420,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
/** /**
* return true if there is a battery installed in this unit * return true if there is a battery installed in this unit
*/ */
// if we have a integrated device with a battery, we can assume that the // if we have a integrated device with a battery, we can assume that the battery is always connected
// battery is always connected
#ifdef BATTERY_IMMUTABLE #ifdef BATTERY_IMMUTABLE
virtual bool isBatteryConnect() override { return true; } virtual bool isBatteryConnect() override { return true; }
#elif defined(ADC_V) #elif defined(ADC_V)
@@ -439,10 +441,10 @@ class AnalogBatteryLevel : public HasBatteryLevel
virtual bool isBatteryConnect() override { return getBatteryPercent() != -1; } virtual bool isBatteryConnect() override { return getBatteryPercent() != -1; }
#endif #endif
/// If we see a battery voltage higher than physics allows - assume charger is /// If we see a battery voltage higher than physics allows - assume charger is pumping
/// pumping in power On some boards we don't have the power management chip /// in power
/// (like AXPxxxx) so we use EXT_PWR_DETECT GPIO pin to detect external power /// On some boards we don't have the power management chip (like AXPxxxx)
/// source /// so we use EXT_PWR_DETECT GPIO pin to detect external power source
virtual bool isVbusIn() override virtual bool isVbusIn() override
{ {
#ifdef EXT_PWR_DETECT #ifdef EXT_PWR_DETECT
@@ -459,12 +461,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
} }
// if it's not HIGH - check the battery // if it's not HIGH - check the battery
#endif #endif
#elif defined(MUZI_BASE)
// technically speaking this should work for all(?) NRF52 boards return NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk;
// but needs testing across multiple devices. NRF52 USB would not even work if
// VBUS was not properly connected and detected by the CPU
#elif defined(MUZI_BASE) || defined(PROMICRO_DIY_TCXO)
return powerHAL_isVBUSConnected();
#endif #endif
return getBattVoltage() > chargingVolt; return getBattVoltage() > chargingVolt;
} }
@@ -487,9 +485,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
#else #else
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION) #if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(DISABLE_INA_CHARGING_DETECTION)
if (hasINA()) { if (hasINA()) {
// get current flow from INA sensor - negative value means power flowing // get current flow from INA sensor - negative value means power flowing into the battery
// into the battery default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT // default assuming BATTERY+ <--> INA_VIN+ <--> SHUNT RESISTOR <--> INA_VIN- <--> LOAD
// RESISTOR <--> INA_VIN- <--> LOAD
LOG_DEBUG("Using INA on I2C addr 0x%x for charging detection", config.power.device_battery_ina_address); LOG_DEBUG("Using INA on I2C addr 0x%x for charging detection", config.power.device_battery_ina_address);
#if defined(INA_CHARGING_DETECTION_INVERT) #if defined(INA_CHARGING_DETECTION_INVERT)
return getINACurrent() > 0; return getINACurrent() > 0;
@@ -505,8 +502,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
} }
private: private:
/// If we see a battery voltage higher than physics allows - assume charger is /// If we see a battery voltage higher than physics allows - assume charger is pumping
/// pumping in power /// in power
/// For heltecs with no battery connected, the measured voltage is 2204, so /// For heltecs with no battery connected, the measured voltage is 2204, so
// need to be higher than that, in this case is 2500mV (3000-500) // need to be higher than that, in this case is 2500mV (3000-500)
@@ -515,8 +512,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS; const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS;
// Start value from minimum voltage for the filter to not start from 0 // Start value from minimum voltage for the filter to not start from 0
// that could trigger some events. // that could trigger some events.
// This value is over-written by the first ADC reading, it the voltage seems // This value is over-written by the first ADC reading, it the voltage seems reasonable.
// reasonable.
bool initial_read_done = false; bool initial_read_done = false;
float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS); float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS);
uint32_t last_read_time_ms = 0; uint32_t last_read_time_ms = 0;
@@ -658,8 +654,7 @@ bool Power::analogInit()
#ifdef CONFIG_IDF_TARGET_ESP32S3 #ifdef CONFIG_IDF_TARGET_ESP32S3
// ESP32S3 // ESP32S3
else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) { else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) {
LOG_INFO("ADC config based on Two Point values and fitting curve " LOG_INFO("ADC config based on Two Point values and fitting curve coefficients stored in eFuse");
"coefficients stored in eFuse");
} }
#endif #endif
else { else {
@@ -667,7 +662,13 @@ bool Power::analogInit()
} }
#endif // ARCH_ESP32 #endif // ARCH_ESP32
// NRF52 ADC init moved to powerHAL_init in nrf52 platform #ifdef ARCH_NRF52
#ifdef VBAT_AR_INTERNAL
analogReference(VBAT_AR_INTERNAL);
#else
analogReference(AR_INTERNAL); // 3.6V
#endif
#endif // ARCH_NRF52
#ifndef ARCH_ESP32 #ifndef ARCH_ESP32
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS); analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
@@ -722,16 +723,6 @@ bool Power::setup()
runASAP = true; runASAP = true;
}, },
CHANGE); CHANGE);
#endif
#ifdef EXT_CHRG_DETECT
attachInterrupt(
EXT_CHRG_DETECT,
[]() {
power->setIntervalFromNow(0);
runASAP = true;
BaseType_t higherWake = 0;
},
CHANGE);
#endif #endif
enabled = found; enabled = found;
low_voltage_counter = 0; low_voltage_counter = 0;
@@ -778,8 +769,7 @@ void Power::reboot()
HAL_NVIC_SystemReset(); HAL_NVIC_SystemReset();
#else #else
rebootAtMsec = -1; rebootAtMsec = -1;
LOG_WARN("FIXME implement reboot for this platform. Note that some settings " LOG_WARN("FIXME implement reboot for this platform. Note that some settings require a restart to be applied");
"require a restart to be applied");
#endif #endif
} }
@@ -789,12 +779,9 @@ void Power::shutdown()
#if HAS_SCREEN #if HAS_SCREEN
if (screen) { if (screen) {
#ifdef T_DECK_PRO #ifdef T_DECK_PRO
screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", screen->showSimpleBanner("Device is powered off.\nConnect USB to start!", 0); // T-Deck Pro has no power button
0); // T-Deck Pro has no power button
#elif defined(USE_EINK) #elif defined(USE_EINK)
screen->showSimpleBanner("Shutting Down...", screen->showSimpleBanner("Shutting Down...", 2250); // dismiss after 3 seconds to avoid the banner on the sleep screen
2250); // dismiss after 3 seconds to avoid the
// banner on the sleep screen
#else #else
screen->showSimpleBanner("Shutting Down...", 0); // stays on screen screen->showSimpleBanner("Shutting Down...", 0); // stays on screen
#endif #endif
@@ -816,9 +803,6 @@ void Power::shutdown()
#endif #endif
#ifdef PIN_LED3 #ifdef PIN_LED3
ledOff(PIN_LED3); ledOff(PIN_LED3);
#endif
#ifdef LED_NOTIFICATION
ledOff(LED_NOTIFICATION);
#endif #endif
doDeepSleep(DELAY_FOREVER, true, true); doDeepSleep(DELAY_FOREVER, true, true);
#elif defined(ARCH_PORTDUINO) #elif defined(ARCH_PORTDUINO)
@@ -836,8 +820,7 @@ void Power::readPowerStatus()
int32_t batteryVoltageMv = -1; // Assume unknown int32_t batteryVoltageMv = -1; // Assume unknown
int8_t batteryChargePercent = -1; int8_t batteryChargePercent = -1;
OptionalBool usbPowered = OptUnknown; OptionalBool usbPowered = OptUnknown;
OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM OptionalBool hasBattery = OptUnknown; // These must be static because NRF_APM code doesn't run every time
// code doesn't run every time
OptionalBool isChargingNow = OptUnknown; OptionalBool isChargingNow = OptUnknown;
if (batteryLevel) { if (batteryLevel) {
@@ -850,10 +833,9 @@ void Power::readPowerStatus()
if (batteryLevel->getBatteryPercent() >= 0) { if (batteryLevel->getBatteryPercent() >= 0) {
batteryChargePercent = batteryLevel->getBatteryPercent(); batteryChargePercent = batteryLevel->getBatteryPercent();
} else { } else {
// If the AXP192 returns a percentage less than 0, the feature is either // If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error
// not supported or there is an error In that case, we compute an // In that case, we compute an estimate of the charge percent based on open circuit voltage table defined
// estimate of the charge percent based on open circuit voltage table // in power.h
// defined in power.h
batteryChargePercent = clamp((int)(((batteryVoltageMv - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS)) * 1e2) / batteryChargePercent = clamp((int)(((batteryVoltageMv - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS)) * 1e2) /
((OCV[0] * NUM_CELLS) - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS))), ((OCV[0] * NUM_CELLS) - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS))),
0, 100); 0, 100);
@@ -861,12 +843,12 @@ void Power::readPowerStatus()
} }
} }
// FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way // FIXME: IMO we shouldn't be littering our code with all these ifdefs. Way better instead to make a Nrf52IsUsbPowered subclass
// better instead to make a Nrf52IsUsbPowered subclass (which shares a // (which shares a superclass with the BatteryLevel stuff)
// superclass with the BatteryLevel stuff) that just provides a few methods. But // that just provides a few methods. But in the interest of fixing this bug I'm going to follow current
// in the interest of fixing this bug I'm going to follow current practice. // practice.
#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates #ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates the power states. Takes 20 seconds or so to detect
// the power states. Takes 20 seconds or so to detect changes. // changes.
nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get(); nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get();
// LOG_DEBUG("NRF Power %d", nrf_usb_state); // LOG_DEBUG("NRF Power %d", nrf_usb_state);
@@ -940,9 +922,8 @@ void Power::readPowerStatus()
#endif #endif
// If we have a battery at all and it is less than 0%, force deep sleep if we // If we have a battery at all and it is less than 0%, force deep sleep if we have more than 10 low readings in
// have more than 10 low readings in a row. NOTE: min LiIon/LiPo voltage // a row. NOTE: min LiIon/LiPo voltage is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough.
// is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough.
// //
if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) { if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) {
@@ -964,8 +945,8 @@ int32_t Power::runOnce()
readPowerStatus(); readPowerStatus();
#ifdef HAS_PMU #ifdef HAS_PMU
// WE no longer use the IRQ line to wake the CPU (due to false wakes from // WE no longer use the IRQ line to wake the CPU (due to false wakes from sleep), but we do poll
// sleep), but we do poll the IRQ status by reading the registers over I2C // the IRQ status by reading the registers over I2C
if (PMU) { if (PMU) {
PMU->getIrqStatus(); PMU->getIrqStatus();
@@ -1007,8 +988,7 @@ int32_t Power::runOnce()
PMU->clearIrqStatus(); PMU->clearIrqStatus();
} }
#endif #endif
// Only read once every 20 seconds once the power status for the app has been // Only read once every 20 seconds once the power status for the app has been initialized
// initialized
return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME; return (statusHandler && statusHandler->isInitialized()) ? (1000 * 20) : RUN_SAME;
} }
@@ -1016,12 +996,10 @@ int32_t Power::runOnce()
* Init the power manager chip * Init the power manager chip
* *
* axp192 power * axp192 power
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the
comms to the axp192 because the OLED and the axp192 share the same i2c bus, axp192 share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this
instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> on!) LDO1 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of
ESP32 (keep this on!) LDO1 30mA -> charges GPS backup battery // charges the days), can not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can
not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
* *
*/ */
bool Power::axpChipInit() bool Power::axpChipInit()
@@ -1066,10 +1044,9 @@ bool Power::axpChipInit()
if (!PMU) { if (!PMU) {
/* /*
* In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will * In XPowersLib, if the XPowersAXPxxx object is released, Wire.end() will be called at the same time.
* be called at the same time. In order not to affect other devices, if the * In order not to affect other devices, if the initialization of the PMU fails, Wire needs to be re-initialized once,
* initialization of the PMU fails, Wire needs to be re-initialized once, if * if there are multiple devices sharing the bus.
* there are multiple devices sharing the bus.
* * */ * * */
#ifndef PMU_USE_WIRE1 #ifndef PMU_USE_WIRE1
w->begin(I2C_SDA, I2C_SCL); w->begin(I2C_SDA, I2C_SCL);
@@ -1086,8 +1063,8 @@ bool Power::axpChipInit()
PMU->enablePowerOutput(XPOWERS_LDO2); PMU->enablePowerOutput(XPOWERS_LDO2);
// oled module power channel, // oled module power channel,
// disable it will cause abnormal communication between boot and AXP power // disable it will cause abnormal communication between boot and AXP power supply,
// supply, do not turn it off // do not turn it off
PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300); PMU->setPowerChannelVoltage(XPOWERS_DCDC1, 3300);
// enable oled power // enable oled power
PMU->enablePowerOutput(XPOWERS_DCDC1); PMU->enablePowerOutput(XPOWERS_DCDC1);
@@ -1114,8 +1091,7 @@ bool Power::axpChipInit()
PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2); PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2);
} else if (PMU->getChipModel() == XPOWERS_AXP2101) { } else if (PMU->getChipModel() == XPOWERS_AXP2101) {
/*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it /*The alternative version of T-Beam 1.1 differs from T-Beam V1.1 in that it uses an AXP2101 power chip*/
* uses an AXP2101 power chip*/
if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) { if (HW_VENDOR == meshtastic_HardwareModel_TBEAM) {
// Unuse power channel // Unuse power channel
PMU->disablePowerOutput(XPOWERS_DCDC2); PMU->disablePowerOutput(XPOWERS_DCDC2);
@@ -1150,8 +1126,8 @@ bool Power::axpChipInit()
// t-beam s3 core // t-beam s3 core
/** /**
* gnss module power channel * gnss module power channel
* The default ALDO4 is off, you need to turn on the GNSS power first, * The default ALDO4 is off, you need to turn on the GNSS power first, otherwise it will be invalid during
* otherwise it will be invalid during initialization * initialization
*/ */
PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300); PMU->setPowerChannelVoltage(XPOWERS_ALDO4, 3300);
PMU->enablePowerOutput(XPOWERS_ALDO4); PMU->enablePowerOutput(XPOWERS_ALDO4);
@@ -1201,8 +1177,7 @@ bool Power::axpChipInit()
// disable all axp chip interrupt // disable all axp chip interrupt
PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ); PMU->disableIRQ(XPOWERS_AXP2101_ALL_IRQ);
// Set the constant current charging current of AXP2101, temporarily use // Set the constant current charging current of AXP2101, temporarily use 500mA by default
// 500mA by default
PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA); PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA);
// Set up the charging voltage // Set up the charging voltage
@@ -1268,12 +1243,11 @@ bool Power::axpChipInit()
PMU->getPowerChannelVoltage(XPOWERS_BLDO2)); PMU->getPowerChannelVoltage(XPOWERS_BLDO2));
} }
// We can safely ignore this approach for most (or all) boards because MCU // We can safely ignore this approach for most (or all) boards because MCU turned off
// turned off earlier than battery discharged to 2.6V. // earlier than battery discharged to 2.6V.
// //
// Unfortunately for now we can't use this killswitch for RAK4630-based boards // Unfortanly for now we can't use this killswitch for RAK4630-based boards because they have a bug with
// because they have a bug with battery voltage measurement. Probably it // battery voltage measurement. Probably it sometimes drops to low values.
// sometimes drops to low values.
#ifndef RAK4630 #ifndef RAK4630
// Set PMU shutdown voltage at 2.6V to maximize battery utilization // Set PMU shutdown voltage at 2.6V to maximize battery utilization
PMU->setSysPowerDownVoltage(2600); PMU->setSysPowerDownVoltage(2600);
@@ -1292,12 +1266,10 @@ bool Power::axpChipInit()
attachInterrupt( attachInterrupt(
PMU_IRQ, [] { pmu_irq = true; }, FALLING); PMU_IRQ, [] { pmu_irq = true; }, FALLING);
// we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ // we do not look for AXPXXX_CHARGING_FINISHED_IRQ & AXPXXX_CHARGING_IRQ because it occurs repeatedly while there is
// because it occurs repeatedly while there is no battery also it could cause // no battery also it could cause inadvertent waking from light sleep just because the battery filled
// inadvertent waking from light sleep just because the battery filled we // we don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while no battery installed
// don't look for AXPXXX_BATT_REMOVED_IRQ because it occurs repeatedly while // we don't look at AXPXXX_VBUS_REMOVED_IRQ because we don't have anything hooked to vbus
// no battery installed we don't look at AXPXXX_VBUS_REMOVED_IRQ because we
// don't have anything hooked to vbus
PMU->enableIRQ(pmuIrqMask); PMU->enableIRQ(pmuIrqMask);
PMU->clearIrqStatus(); PMU->clearIrqStatus();
@@ -1413,8 +1385,8 @@ class LipoCharger : public HasBatteryLevel
bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR); bool result = PPM->init(Wire, I2C_SDA, I2C_SCL, BQ25896_ADDR);
if (result) { if (result) {
LOG_INFO("PPM BQ25896 init succeeded"); LOG_INFO("PPM BQ25896 init succeeded");
// Set the minimum operating voltage. Below this voltage, the PPM will // Set the minimum operating voltage. Below this voltage, the PPM will protect
// protect PPM->setSysPowerDownVoltage(3100); // PPM->setSysPowerDownVoltage(3100);
// Set input current limit, default is 500mA // Set input current limit, default is 500mA
// PPM->setInputCurrentLimit(800); // PPM->setInputCurrentLimit(800);
@@ -1437,8 +1409,7 @@ class LipoCharger : public HasBatteryLevel
PPM->enableMeasure(); PPM->enableMeasure();
// Turn on charging function // Turn on charging function
// If there is no battery connected, do not turn on the charging // If there is no battery connected, do not turn on the charging function
// function
PPM->enableCharge(); PPM->enableCharge();
} else { } else {
LOG_WARN("PPM BQ25896 init failed"); LOG_WARN("PPM BQ25896 init failed");
@@ -1473,8 +1444,7 @@ class LipoCharger : public HasBatteryLevel
virtual int getBatteryPercent() override virtual int getBatteryPercent() override
{ {
return -1; return -1;
// return bq->getChargePercent(); // don't use BQ27220 for battery percent, // return bq->getChargePercent(); // don't use BQ27220 for battery percent, it is not calibrated
// it is not calibrated
} }
/** /**
@@ -1596,8 +1566,7 @@ bool Power::meshSolarInit()
#else #else
/** /**
* The meshSolar battery level sensor is unavailable - default to * The meshSolar battery level sensor is unavailable - default to AnalogBatteryLevel
* AnalogBatteryLevel
*/ */
bool Power::meshSolarInit() bool Power::meshSolarInit()
{ {
+6 -8
View File
@@ -241,7 +241,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define BQ27220_ADDR 0x55 // same address as TDECK_KB #define BQ27220_ADDR 0x55 // same address as TDECK_KB
#define BQ25896_ADDR 0x6B #define BQ25896_ADDR 0x6B
#define LTR553ALS_ADDR 0x23 #define LTR553ALS_ADDR 0x23
#define SEN5X_ADDR 0x69
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// ACCELEROMETER // ACCELEROMETER
@@ -391,6 +390,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#ifndef HAS_RADIO #ifndef HAS_RADIO
#define HAS_RADIO 0 #define HAS_RADIO 0
#endif #endif
#ifndef HAS_RTC
#define HAS_RTC 0
#endif
#ifndef HAS_CPU_SHUTDOWN #ifndef HAS_CPU_SHUTDOWN
#define HAS_CPU_SHUTDOWN 0 #define HAS_CPU_SHUTDOWN 0
#endif #endif
@@ -426,16 +428,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define HAS_RGB_LED #define HAS_RGB_LED
#endif #endif
#ifndef LED_STATE_OFF
#define LED_STATE_OFF 0
#endif
#ifndef LED_STATE_ON #ifndef LED_STATE_ON
#define LED_STATE_ON 1 #define LED_STATE_ON 1
#endif #endif
#ifndef LED_STATE_OFF
#define LED_STATE_OFF (LED_STATE_ON ^ 1)
#endif
#ifndef ledOff
#define ledOff(pin) pinMode(pin, INPUT)
#endif
// default mapping of pins // default mapping of pins
#if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN) #if defined(PIN_BUTTON2) && !defined(CANCEL_BUTTON_PIN)
+1 -1
View File
@@ -43,7 +43,7 @@ ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
ScanI2C::FoundDevice ScanI2C::firstAQI() const ScanI2C::FoundDevice ScanI2C::firstAQI() const
{ {
ScanI2C::DeviceType types[] = {PMSA003I, SEN5X, SCD4X}; ScanI2C::DeviceType types[] = {PMSA003I, SCD4X};
return firstOfOrNONE(2, types); return firstOfOrNONE(2, types);
} }
+1 -2
View File
@@ -88,8 +88,7 @@ class ScanI2C
BH1750, BH1750,
DA217, DA217,
CHSC6X, CHSC6X,
CST226SE, CST226SE
SEN5X
} DeviceType; } DeviceType;
// typedef uint8_t DeviceAddress; // typedef uint8_t DeviceAddress;
+8 -66
View File
@@ -8,7 +8,6 @@
#endif #endif
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) #if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
#include "meshUtils.h" // vformat #include "meshUtils.h" // vformat
#endif #endif
bool in_array(uint8_t *array, int size, uint8_t lookfor) bool in_array(uint8_t *array, int size, uint8_t lookfor)
@@ -115,45 +114,6 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation
return value; return value;
} }
/// for SEN5X detection
// Note, this code needs to be called before setting the I2C bus speed
// for the screen at high speed. The speed needs to be at 100kHz, otherwise
// detection will not work
String readSEN5xProductName(TwoWire *i2cBus, uint8_t address)
{
uint8_t cmd[] = {0xD0, 0x14};
uint8_t response[48] = {0};
i2cBus->beginTransmission(address);
i2cBus->write(cmd, 2);
if (i2cBus->endTransmission() != 0)
return "";
delay(20);
if (i2cBus->requestFrom(address, (uint8_t)48) != 48)
return "";
for (int i = 0; i < 48 && i2cBus->available(); ++i) {
response[i] = i2cBus->read();
}
char productName[33] = {0};
int j = 0;
for (int i = 0; i < 48 && j < 32; i += 3) {
if (response[i] >= 32 && response[i] <= 126)
productName[j++] = response[i];
else
break;
if (response[i + 1] >= 32 && response[i + 1] <= 126)
productName[j++] = response[i + 1];
else
break;
}
return String(productName);
}
#define SCAN_SIMPLE_CASE(ADDR, T, ...) \ #define SCAN_SIMPLE_CASE(ADDR, T, ...) \
case ADDR: \ case ADDR: \
logFoundDevice(__VA_ARGS__); \ logFoundDevice(__VA_ARGS__); \
@@ -608,9 +568,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
} }
break; break;
case ICM20948_ADDR: // same as BMX160_ADDR and SEN5X_ADDR case ICM20948_ADDR: // same as BMX160_ADDR
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR case ICM20948_ADDR_ALT: // same as MPU6050_ADDR
// ICM20948 Register check
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
#ifdef HAS_ICM20948 #ifdef HAS_ICM20948
type = ICM20948; type = ICM20948;
@@ -621,31 +580,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
type = ICM20948; type = ICM20948;
logFoundDevice("ICM20948", (uint8_t)addr.address); logFoundDevice("ICM20948", (uint8_t)addr.address);
break; break;
} else if (addr.address == BMX160_ADDR) {
type = BMX160;
logFoundDevice("BMX160", (uint8_t)addr.address);
break;
} else { } else {
String prod = ""; type = MPU6050;
prod = readSEN5xProductName(i2cBus, addr.address); logFoundDevice("MPU6050", (uint8_t)addr.address);
if (prod.startsWith("SEN55")) { break;
type = SEN5X;
logFoundDevice("Sensirion SEN55", addr.address);
break;
} else if (prod.startsWith("SEN54")) {
type = SEN5X;
logFoundDevice("Sensirion SEN54", addr.address);
break;
} else if (prod.startsWith("SEN50")) {
type = SEN5X;
logFoundDevice("Sensirion SEN50", addr.address);
break;
}
if (addr.address == BMX160_ADDR) {
type = BMX160;
logFoundDevice("BMX160", (uint8_t)addr.address);
break;
} else {
type = MPU6050;
logFoundDevice("MPU6050", (uint8_t)addr.address);
break;
}
} }
break; break;
-31
View File
@@ -1,31 +0,0 @@
#include "reClockI2C.h"
#include "ScanI2CTwoWire.h"
uint32_t reClockI2C(uint32_t desiredClock, TwoWire *i2cBus, bool force)
{
uint32_t currentClock = 0;
/* See https://github.com/arduino/Arduino/issues/11457
Currently, only ESP32 can getClock()
While all cores can setClock()
https://github.com/sandeepmistry/arduino-nRF5/blob/master/libraries/Wire/Wire.h#L50
https://github.com/earlephilhower/arduino-pico/blob/master/libraries/Wire/src/Wire.h#L60
https://github.com/stm32duino/Arduino_Core_STM32/blob/main/libraries/Wire/src/Wire.h#L103
For cases when I2C speed is different to the ones defined by sensors (see defines in sensor classes)
we need to reclock I2C and set it back to the previous desired speed.
Only for cases where we can know OR predefine the speed, we can do this.
*/
// TODO add getClock function or return a predefined clock speed per variant?
#ifdef CAN_RECLOCK_I2C
currentClock = i2cBus->getClock();
#endif
if ((currentClock != desiredClock) || force) {
LOG_DEBUG("Changing I2C clock to %u", desiredClock);
i2cBus->setClock(desiredClock);
}
return currentClock;
}
+37 -7
View File
@@ -1,11 +1,41 @@
#ifdef CAN_RECLOCK_I2C
#ifndef RECLOCK_I2C_
#define RECLOCK_I2C_
#include "ScanI2CTwoWire.h" #include "ScanI2CTwoWire.h"
#include <Wire.h>
#include <stdint.h>
uint32_t reClockI2C(uint32_t desiredClock, TwoWire *i2cBus, bool force); uint32_t reClockI2C(uint32_t desiredClock, TwoWire *i2cBus)
{
uint32_t currentClock;
/* See https://github.com/arduino/Arduino/issues/11457
Currently, only ESP32 can getClock()
While all cores can setClock()
https://github.com/sandeepmistry/arduino-nRF5/blob/master/libraries/Wire/Wire.h#L50
https://github.com/earlephilhower/arduino-pico/blob/master/libraries/Wire/src/Wire.h#L60
https://github.com/stm32duino/Arduino_Core_STM32/blob/main/libraries/Wire/src/Wire.h#L103
For cases when I2C speed is different to the ones defined by sensors (see defines in sensor classes)
we need to reclock I2C and set it back to the previous desired speed.
Only for cases where we can know OR predefine the speed, we can do this.
*/
#ifdef ARCH_ESP32
currentClock = i2cBus->getClock();
#elif defined(ARCH_NRF52)
// TODO add getClock function or return a predefined clock speed per variant?
return 0;
#elif defined(ARCH_RP2040)
// TODO add getClock function or return a predefined clock speed per variant
return 0;
#elif defined(ARCH_STM32WL)
// TODO add getClock function or return a predefined clock speed per variant
return 0;
#else
return 0;
#endif
if (currentClock != desiredClock) {
LOG_DEBUG("Changing I2C clock to %u", desiredClock);
i2cBus->setClock(desiredClock);
}
return currentClock;
}
#endif #endif
-6
View File
@@ -905,12 +905,6 @@ void GPS::writePinStandby(bool standby)
// Write and log // Write and log
pinMode(PIN_GPS_STANDBY, OUTPUT); pinMode(PIN_GPS_STANDBY, OUTPUT);
digitalWrite(PIN_GPS_STANDBY, val); digitalWrite(PIN_GPS_STANDBY, val);
// Enter backup mode on PA1010D; TODO: may be applicable to other MTK GPS too
if (IS_ONE_OF(gnssModel, GNSS_MODEL_MTK_PA1010D)) {
_serial_gps->write("$PMTK225,4*2F\r\n");
}
#ifdef GPS_DEBUG #ifdef GPS_DEBUG
LOG_DEBUG("Pin STANDBY %s", val == HIGH ? "HI" : "LOW"); LOG_DEBUG("Pin STANDBY %s", val == HIGH ? "HI" : "LOW");
#endif #endif
+10 -11
View File
@@ -72,13 +72,11 @@ RTCSetResult readFromRTC()
#elif defined(PCF8563_RTC) || defined(PCF85063_RTC) #elif defined(PCF8563_RTC) || defined(PCF85063_RTC)
#if defined(PCF8563_RTC) #if defined(PCF8563_RTC)
if (rtc_found.address == PCF8563_RTC) { if (rtc_found.address == PCF8563_RTC) {
SensorPCF8563 rtc;
#elif defined(PCF85063_RTC) #elif defined(PCF85063_RTC)
if (rtc_found.address == PCF85063_RTC) { if (rtc_found.address == PCF85063_RTC) {
SensorPCF85063 rtc;
#endif #endif
uint32_t now = millis(); uint32_t now = millis();
SensorRtcHelper rtc;
#if WIRE_INTERFACES_COUNT == 2 #if WIRE_INTERFACES_COUNT == 2
rtc.begin(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire); rtc.begin(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire);
@@ -242,12 +240,10 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
#elif defined(PCF8563_RTC) || defined(PCF85063_RTC) #elif defined(PCF8563_RTC) || defined(PCF85063_RTC)
#if defined(PCF8563_RTC) #if defined(PCF8563_RTC)
if (rtc_found.address == PCF8563_RTC) { if (rtc_found.address == PCF8563_RTC) {
SensorPCF8563 rtc;
#elif defined(PCF85063_RTC) #elif defined(PCF85063_RTC)
if (rtc_found.address == PCF85063_RTC) { if (rtc_found.address == PCF85063_RTC) {
SensorPCF85063 rtc;
#endif #endif
SensorRtcHelper rtc;
#if WIRE_INTERFACES_COUNT == 2 #if WIRE_INTERFACES_COUNT == 2
rtc.begin(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire); rtc.begin(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire);
@@ -280,7 +276,11 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
settimeofday(tv, NULL); settimeofday(tv, NULL);
#endif #endif
// nrf52 doesn't have a readable RTC (yet - software not written)
#if HAS_RTC
readFromRTC(); readFromRTC();
#endif
return RTCSetResultSuccess; return RTCSetResultSuccess;
} else { } else {
return RTCSetResultNotSet; // RTC was already set with a higher quality time return RTCSetResultNotSet; // RTC was already set with a higher quality time
@@ -397,7 +397,7 @@ uint32_t getValidTime(RTCQuality minQuality, bool local)
return (currentQuality >= minQuality) ? getTime(local) : 0; return (currentQuality >= minQuality) ? getTime(local) : 0;
} }
time_t gm_mktime(const struct tm *tm) time_t gm_mktime(struct tm *tm)
{ {
#if !MESHTASTIC_EXCLUDE_TZ #if !MESHTASTIC_EXCLUDE_TZ
time_t result = 0; time_t result = 0;
@@ -413,8 +413,8 @@ time_t gm_mktime(const struct tm *tm)
days_before_this_year -= 719162; // (1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400); days_before_this_year -= 719162; // (1969 * 365 + 1969 / 4 - 1969 / 100 + 1969 / 400);
// Now, within this tm->year, compute the days *before* this tm->month starts. // Now, within this tm->year, compute the days *before* this tm->month starts.
static const int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year int days_before_month[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; // non-leap year
int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11 int days_this_year_before_this_month = days_before_month[tm->tm_mon]; // tm->tm_mon is 0..11
// If this is a leap year, and we're past February, add a day: // If this is a leap year, and we're past February, add a day:
if (tm->tm_mon >= 2 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) { if (tm->tm_mon >= 2 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) {
@@ -435,7 +435,6 @@ time_t gm_mktime(const struct tm *tm)
return result; return result;
#else #else
struct tm tmCopy = *tm; return mktime(tm);
return mktime(&tmCopy);
#endif #endif
} }
+1 -1
View File
@@ -54,7 +54,7 @@ uint32_t getValidTime(RTCQuality minQuality, bool local = false);
RTCSetResult readFromRTC(); RTCSetResult readFromRTC();
time_t gm_mktime(const struct tm *tm); time_t gm_mktime(struct tm *tm);
#define SEC_PER_DAY 86400 #define SEC_PER_DAY 86400
#define SEC_PER_HOUR 3600 #define SEC_PER_HOUR 3600
-20
View File
@@ -1731,26 +1731,6 @@ int Screen::handleInputEvent(const InputEvent *event)
showFrame(FrameDirection::PREVIOUS); showFrame(FrameDirection::PREVIOUS);
} else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) { } else if (event->inputEvent == INPUT_BROKER_RIGHT || event->inputEvent == INPUT_BROKER_USER_PRESS) {
showFrame(FrameDirection::NEXT); showFrame(FrameDirection::NEXT);
} else if (event->inputEvent == INPUT_BROKER_FN_F1) {
this->ui->switchToFrame(0);
lastScreenTransition = millis();
setFastFramerate();
} else if (event->inputEvent == INPUT_BROKER_FN_F2) {
this->ui->switchToFrame(1);
lastScreenTransition = millis();
setFastFramerate();
} else if (event->inputEvent == INPUT_BROKER_FN_F3) {
this->ui->switchToFrame(2);
lastScreenTransition = millis();
setFastFramerate();
} else if (event->inputEvent == INPUT_BROKER_FN_F4) {
this->ui->switchToFrame(3);
lastScreenTransition = millis();
setFastFramerate();
} else if (event->inputEvent == INPUT_BROKER_FN_F5) {
this->ui->switchToFrame(4);
lastScreenTransition = millis();
setFastFramerate();
} else if (event->inputEvent == INPUT_BROKER_UP_LONG) { } else if (event->inputEvent == INPUT_BROKER_UP_LONG) {
// Long press up button for fast frame switching // Long press up button for fast frame switching
showPrevFrame(); showPrevFrame();
+178 -171
View File
@@ -58,7 +58,7 @@ BannerOverlayOptions createStaticBannerOptions(const char *message, const MenuOp
} // namespace } // namespace
menuHandler::screenMenus menuHandler::menuQueue = MenuNone; menuHandler::screenMenus menuHandler::menuQueue = menu_none;
uint32_t menuHandler::pickedNodeNum = 0; uint32_t menuHandler::pickedNodeNum = 0;
bool test_enabled = false; bool test_enabled = false;
uint8_t test_count = 0; uint8_t test_count = 0;
@@ -66,7 +66,7 @@ uint8_t test_count = 0;
void menuHandler::loraMenu() void menuHandler::loraMenu()
{ {
static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "Frequency Slot", "LoRa Region"}; static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "Frequency Slot", "LoRa Region"};
enum optionsNumbers { Back = 0, DeviceRolePicker = 1, RadioPresetPicker = 2, FrequencySlot = 3, LoraPicker = 4 }; enum optionsNumbers { Back = 0, device_role_picker = 1, radio_preset_picker = 2, frequency_slot = 3, lora_picker = 4 };
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "LoRa Actions"; bannerOptions.message = "LoRa Actions";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
@@ -74,14 +74,14 @@ void menuHandler::loraMenu()
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) { if (selected == Back) {
// No action // No action
} else if (selected == DeviceRolePicker) { } else if (selected == device_role_picker) {
menuHandler::menuQueue = menuHandler::DeviceRolePicker; menuHandler::menuQueue = menuHandler::device_role_picker;
} else if (selected == RadioPresetPicker) { } else if (selected == radio_preset_picker) {
menuHandler::menuQueue = menuHandler::RadioPresetPicker; menuHandler::menuQueue = menuHandler::radio_preset_picker;
} else if (selected == FrequencySlot) { } else if (selected == frequency_slot) {
menuHandler::menuQueue = menuHandler::FrequencySlot; menuHandler::menuQueue = menuHandler::frequency_slot;
} else if (selected == LoraPicker) { } else if (selected == lora_picker) {
menuHandler::menuQueue = menuHandler::LoraPicker; menuHandler::menuQueue = menuHandler::lora_picker;
} }
}; };
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
@@ -102,7 +102,7 @@ void menuHandler::OnboardMessage()
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 2; bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
menuHandler::menuQueue = menuHandler::NoTimeoutLoraPicker; menuHandler::menuQueue = menuHandler::no_timeout_lora_picker;
screen->runNow(); screen->runNow();
}; };
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
@@ -216,7 +216,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
void menuHandler::deviceRolePicker() void menuHandler::DeviceRolePicker()
{ {
static const char *optionsArray[] = {"Back", "Client", "Client Mute", "Lost and Found", "Tracker"}; static const char *optionsArray[] = {"Back", "Client", "Client Mute", "Lost and Found", "Tracker"};
enum optionsNumbers { enum optionsNumbers {
@@ -232,7 +232,7 @@ void menuHandler::deviceRolePicker()
bannerOptions.optionsCount = 5; bannerOptions.optionsCount = 5;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) { if (selected == Back) {
menuHandler::menuQueue = menuHandler::LoraMenu; menuHandler::menuQueue = menuHandler::lora_Menu;
screen->runNow(); screen->runNow();
return; return;
} else if (selected == devicerole_client) { } else if (selected == devicerole_client) {
@@ -266,8 +266,52 @@ void menuHandler::FrequencySlotPicker()
// Calculate number of channels (copied from RadioInterface::applyModemConfig()) // Calculate number of channels (copied from RadioInterface::applyModemConfig())
meshtastic_Config_LoRaConfig &loraConfig = config.lora; meshtastic_Config_LoRaConfig &loraConfig = config.lora;
double bw = loraConfig.use_preset ? modemPresetToBwKHz(loraConfig.modem_preset, myRegion->wideLora) double bw = loraConfig.bandwidth;
: bwCodeToKHz(loraConfig.bandwidth); if (loraConfig.use_preset) {
switch (loraConfig.modem_preset) {
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
bw = (myRegion->wideLora) ? 1625.0 : 500;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
bw = (myRegion->wideLora) ? 812.5 : 250;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
bw = (myRegion->wideLora) ? 812.5 : 250;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
bw = (myRegion->wideLora) ? 812.5 : 250;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
bw = (myRegion->wideLora) ? 812.5 : 250;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO:
bw = (myRegion->wideLora) ? 1625.0 : 500;
break;
default:
bw = (myRegion->wideLora) ? 812.5 : 250;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
bw = (myRegion->wideLora) ? 406.25 : 125;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
bw = (myRegion->wideLora) ? 406.25 : 125;
break;
}
} else {
bw = loraConfig.bandwidth;
if (bw == 31) // This parameter is not an integer
bw = 31.25;
if (bw == 62) // Fix for 62.5Khz bandwidth
bw = 62.5;
if (bw == 200)
bw = 203.125;
if (bw == 400)
bw = 406.25;
if (bw == 800)
bw = 812.5;
if (bw == 1600)
bw = 1625.0;
}
uint32_t numChannels = 0; uint32_t numChannels = 0;
if (myRegion) { if (myRegion) {
@@ -300,7 +344,7 @@ void menuHandler::FrequencySlotPicker()
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) { if (selected == Back) {
menuHandler::menuQueue = menuHandler::LoraMenu; menuHandler::menuQueue = menuHandler::lora_Menu;
screen->runNow(); screen->runNow();
return; return;
} }
@@ -313,7 +357,7 @@ void menuHandler::FrequencySlotPicker()
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
void menuHandler::radioPresetPicker() void menuHandler::RadioPresetPicker()
{ {
static const RadioPresetOption presetOptions[] = { static const RadioPresetOption presetOptions[] = {
{"Back", OptionsAction::Back}, {"Back", OptionsAction::Back},
@@ -333,7 +377,7 @@ void menuHandler::radioPresetPicker()
auto bannerOptions = auto bannerOptions =
createStaticBannerOptions("Radio Preset", presetOptions, presetLabels, [](const RadioPresetOption &option, int) -> void { createStaticBannerOptions("Radio Preset", presetOptions, presetLabels, [](const RadioPresetOption &option, int) -> void {
if (option.action == OptionsAction::Back) { if (option.action == OptionsAction::Back) {
menuHandler::menuQueue = menuHandler::LoraMenu; menuHandler::menuQueue = menuHandler::lora_Menu;
screen->runNow(); screen->runNow();
return; return;
} }
@@ -352,7 +396,7 @@ void menuHandler::radioPresetPicker()
screen->showOverlayBanner(bannerOptions); 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"};
enum optionsNumbers { Back = 0, twelve = 1, twentyfour = 2 }; enum optionsNumbers { Back = 0, twelve = 1, twentyfour = 2 };
@@ -362,7 +406,7 @@ void menuHandler::twelveHourPicker()
bannerOptions.optionsCount = 3; bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) { if (selected == Back) {
menuHandler::menuQueue = menuHandler::ClockMenu; menuHandler::menuQueue = menuHandler::clock_menu;
screen->runNow(); screen->runNow();
} else if (selected == twelve) { } else if (selected == twelve) {
config.display.use_12h_clock = true; config.display.use_12h_clock = true;
@@ -390,7 +434,7 @@ void menuHandler::showConfirmationBanner(const char *message, std::function<void
screen->showOverlayBanner(confirmBanner); screen->showOverlayBanner(confirmBanner);
} }
void menuHandler::clockFacePicker() void menuHandler::ClockFacePicker()
{ {
static const ClockFaceOption clockFaceOptions[] = { static const ClockFaceOption clockFaceOptions[] = {
{"Back", OptionsAction::Back}, {"Back", OptionsAction::Back},
@@ -404,7 +448,7 @@ void menuHandler::clockFacePicker()
auto bannerOptions = createStaticBannerOptions("Which Face?", clockFaceOptions, clockFaceLabels, auto bannerOptions = createStaticBannerOptions("Which Face?", clockFaceOptions, clockFaceLabels,
[](const ClockFaceOption &option, int) -> void { [](const ClockFaceOption &option, int) -> void {
if (option.action == OptionsAction::Back) { if (option.action == OptionsAction::Back) {
menuHandler::menuQueue = menuHandler::ClockMenu; menuHandler::menuQueue = menuHandler::clock_menu;
screen->runNow(); screen->runNow();
return; return;
} }
@@ -456,7 +500,7 @@ void menuHandler::TZPicker()
auto bannerOptions = createStaticBannerOptions( auto bannerOptions = createStaticBannerOptions(
"Pick Timezone", timezoneOptions, timezoneLabels, [](const TimezoneOption &option, int) -> void { "Pick Timezone", timezoneOptions, timezoneLabels, [](const TimezoneOption &option, int) -> void {
if (option.action == OptionsAction::Back) { if (option.action == OptionsAction::Back) {
menuHandler::menuQueue = menuHandler::ClockMenu; menuHandler::menuQueue = menuHandler::clock_menu;
screen->runNow(); screen->runNow();
return; return;
} }
@@ -503,13 +547,13 @@ void menuHandler::clockMenu()
bannerOptions.optionsCount = 4; bannerOptions.optionsCount = 4;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Clock) { if (selected == Clock) {
menuHandler::menuQueue = menuHandler::ClockFacePicker; menuHandler::menuQueue = menuHandler::clock_face_picker;
screen->runNow(); screen->runNow();
} else if (selected == Time) { } else if (selected == Time) {
menuHandler::menuQueue = menuHandler::TwelveHourPicker; menuHandler::menuQueue = menuHandler::twelve_hour_picker;
screen->runNow(); screen->runNow();
} else if (selected == Timezone) { } else if (selected == Timezone) {
menuHandler::menuQueue = menuHandler::TzPicker; menuHandler::menuQueue = menuHandler::TZ_picker;
screen->runNow(); screen->runNow();
} }
}; };
@@ -572,12 +616,12 @@ void menuHandler::messageResponseMenu()
LOG_DEBUG("[ReplyCtx] mode=%d ch=%d peer=0x%08x", (int)mode, ch, (unsigned int)peer); LOG_DEBUG("[ReplyCtx] mode=%d ch=%d peer=0x%08x", (int)mode, ch, (unsigned int)peer);
if (selected == ViewMode) { if (selected == ViewMode) {
menuHandler::menuQueue = menuHandler::MessageViewModeMenu; menuHandler::menuQueue = menuHandler::message_viewmode_menu;
screen->runNow(); screen->runNow();
// Reply submenu // Reply submenu
} else if (selected == ReplyMenu) { } else if (selected == ReplyMenu) {
menuHandler::menuQueue = menuHandler::ReplyMenu; menuHandler::menuQueue = menuHandler::reply_menu;
screen->runNow(); screen->runNow();
} else if (selected == MuteChannel) { } else if (selected == MuteChannel) {
@@ -589,7 +633,7 @@ void menuHandler::messageResponseMenu()
} }
} else if (selected == DeleteMenu) { } else if (selected == DeleteMenu) {
menuHandler::menuQueue = menuHandler::DeleteMessagesMenu; menuHandler::menuQueue = menuHandler::delete_messages_menu;
screen->runNow(); screen->runNow();
#ifdef HAS_I2S #ifdef HAS_I2S
@@ -649,7 +693,7 @@ void menuHandler::replyMenu()
uint32_t peer = graphics::MessageRenderer::getThreadPeer(); uint32_t peer = graphics::MessageRenderer::getThreadPeer();
if (selected == Back) { if (selected == Back) {
menuHandler::menuQueue = menuHandler::MessageResponseMenu; menuHandler::menuQueue = menuHandler::message_response_menu;
screen->runNow(); screen->runNow();
return; return;
} }
@@ -737,7 +781,7 @@ void menuHandler::deleteMessagesMenu()
uint32_t peer = graphics::MessageRenderer::getThreadPeer(); uint32_t peer = graphics::MessageRenderer::getThreadPeer();
if (selected == Back) { if (selected == Back) {
menuHandler::menuQueue = menuHandler::MessageResponseMenu; menuHandler::menuQueue = menuHandler::message_response_menu;
screen->runNow(); screen->runNow();
return; return;
} }
@@ -901,7 +945,7 @@ void menuHandler::messageViewModeMenu()
bannerOptions.bannerCallback = [=](int selected) -> void { bannerOptions.bannerCallback = [=](int selected) -> void {
LOG_DEBUG("messageViewModeMenu: selected=%d", selected); LOG_DEBUG("messageViewModeMenu: selected=%d", selected);
if (selected == -1) { if (selected == -1) {
menuHandler::menuQueue = menuHandler::MessageResponseMenu; menuHandler::menuQueue = menuHandler::message_response_menu;
screen->runNow(); screen->runNow();
} else if (selected == -2) { } else if (selected == -2) {
graphics::MessageRenderer::setThreadMode(graphics::MessageRenderer::ThreadMode::ALL); graphics::MessageRenderer::setThreadMode(graphics::MessageRenderer::ThreadMode::ALL);
@@ -1083,23 +1127,23 @@ void menuHandler::systemBaseMenu()
bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Notifications) { if (selected == Notifications) {
menuHandler::menuQueue = menuHandler::BuzzerModeMenuPicker; menuHandler::menuQueue = menuHandler::buzzermodemenupicker;
screen->runNow(); screen->runNow();
} else if (selected == ScreenOptions) { } else if (selected == ScreenOptions) {
menuHandler::menuQueue = menuHandler::ScreenOptionsMenu; menuHandler::menuQueue = menuHandler::screen_options_menu;
screen->runNow(); screen->runNow();
} else if (selected == PowerMenu) { } else if (selected == PowerMenu) {
menuHandler::menuQueue = menuHandler::PowerMenu; menuHandler::menuQueue = menuHandler::power_menu;
screen->runNow(); screen->runNow();
} else if (selected == Test) { } else if (selected == Test) {
menuHandler::menuQueue = menuHandler::TestMenu; menuHandler::menuQueue = menuHandler::test_menu;
screen->runNow(); screen->runNow();
} else if (selected == Bluetooth) { } else if (selected == Bluetooth) {
menuQueue = BluetoothToggleMenu; menuQueue = bluetooth_toggle_menu;
screen->runNow(); screen->runNow();
#if HAS_WIFI && !defined(ARCH_PORTDUINO) #if HAS_WIFI && !defined(ARCH_PORTDUINO)
} else if (selected == WiFiToggle) { } else if (selected == WiFiToggle) {
menuQueue = WifiToggleMenu; menuQueue = wifi_toggle_menu;
screen->runNow(); screen->runNow();
#endif #endif
} else if (selected == Back && !test_enabled) { } else if (selected == Back && !test_enabled) {
@@ -1177,7 +1221,7 @@ void menuHandler::favoriteBaseMenu()
evt.action = UIFrameEvent::Action::SWITCH_TO_TEXTMESSAGE; evt.action = UIFrameEvent::Action::SWITCH_TO_TEXTMESSAGE;
screen->handleUIFrameEvent(&evt); screen->handleUIFrameEvent(&evt);
} else if (selected == Remove) { } else if (selected == Remove) {
menuHandler::menuQueue = menuHandler::RemoveFavorite; menuHandler::menuQueue = menuHandler::remove_favorite;
screen->runNow(); screen->runNow();
} else if (selected == TraceRoute) { } else if (selected == TraceRoute) {
if (traceRouteModule) { if (traceRouteModule) {
@@ -1238,15 +1282,15 @@ void menuHandler::positionBaseMenu()
auto action = static_cast<PositionAction>(option.value); auto action = static_cast<PositionAction>(option.value);
switch (action) { switch (action) {
case PositionAction::GpsToggle: case PositionAction::GpsToggle:
menuQueue = GpsToggleMenu; menuQueue = gps_toggle_menu;
screen->runNow(); screen->runNow();
break; break;
case PositionAction::GpsFormat: case PositionAction::GpsFormat:
menuQueue = GpsFormatMenu; menuQueue = gps_format_menu;
screen->runNow(); screen->runNow();
break; break;
case PositionAction::CompassMenu: case PositionAction::CompassMenu:
menuQueue = CompassPointNorthMenu; menuQueue = compass_point_north_menu;
screen->runNow(); screen->runNow();
break; break;
case PositionAction::CompassCalibrate: case PositionAction::CompassCalibrate:
@@ -1255,15 +1299,15 @@ void menuHandler::positionBaseMenu()
} }
break; break;
case PositionAction::GPSSmartPosition: case PositionAction::GPSSmartPosition:
menuQueue = GpsSmartPositionMenu; menuQueue = gps_smart_position_menu;
screen->runNow(); screen->runNow();
break; break;
case PositionAction::GPSUpdateInterval: case PositionAction::GPSUpdateInterval:
menuQueue = GpsUpdateIntervalMenu; menuQueue = gps_update_interval_menu;
screen->runNow(); screen->runNow();
break; break;
case PositionAction::GPSPositionBroadcast: case PositionAction::GPSPositionBroadcast:
menuQueue = GpsPositionBroadcastMenu; menuQueue = gps_position_broadcast_menu;
screen->runNow(); screen->runNow();
break; break;
} }
@@ -1303,13 +1347,13 @@ void menuHandler::nodeListMenu()
bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == NodePicker) { if (selected == NodePicker) {
menuQueue = NodePickerMenu; menuQueue = NodePicker_menu;
screen->runNow(); screen->runNow();
} else if (selected == Reset) { } else if (selected == Reset) {
menuQueue = ResetNodeDbMenu; menuQueue = reset_node_db_menu;
screen->runNow(); screen->runNow();
} else if (selected == NodeNameLength) { } else if (selected == NodeNameLength) {
menuHandler::menuQueue = menuHandler::NodeNameLengthMenu; menuHandler::menuQueue = menuHandler::node_name_length_menu;
screen->runNow(); screen->runNow();
} }
}; };
@@ -1330,12 +1374,12 @@ void menuHandler::NodePicker()
menuHandler::pickedNodeNum = nodenum; menuHandler::pickedNodeNum = nodenum;
// Keep UI favorite context in sync (used elsewhere for some node-based actions) // Keep UI favorite context in sync (used elsewhere for some node-based actions)
graphics::UIRenderer::currentFavoriteNodeNum = nodenum; graphics::UIRenderer::currentFavoriteNodeNum = nodenum;
menuQueue = ManageNodeMenu; menuQueue = Manage_Node_menu;
screen->runNow(); screen->runNow();
}); });
} }
void menuHandler::manageNodeMenu() void menuHandler::ManageNodeMenu()
{ {
// If we don't have a node selected yet, go fast exit // If we don't have a node selected yet, go fast exit
auto node = nodeDB->getMeshNode(menuHandler::pickedNodeNum); auto node = nodeDB->getMeshNode(menuHandler::pickedNodeNum);
@@ -1391,7 +1435,7 @@ void menuHandler::manageNodeMenu()
bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) { if (selected == Back) {
menuQueue = NodeBaseMenu; menuQueue = node_base_menu;
screen->runNow(); screen->runNow();
return; return;
} }
@@ -1483,7 +1527,7 @@ void menuHandler::nodeNameLengthMenu()
auto bannerOptions = createStaticBannerOptions("Node Name Length", nodeNameOptions, nodeNameLabels, auto bannerOptions = createStaticBannerOptions("Node Name Length", nodeNameOptions, nodeNameLabels,
[](const NodeNameOption &option, int) -> void { [](const NodeNameOption &option, int) -> void {
if (option.action == OptionsAction::Back) { if (option.action == OptionsAction::Back) {
menuQueue = NodeBaseMenu; menuQueue = node_base_menu;
screen->runNow(); screen->runNow();
return; return;
} }
@@ -1498,7 +1542,6 @@ void menuHandler::nodeNameLengthMenu()
config.display.use_long_node_name = option.value; config.display.use_long_node_name = option.value;
saveUIConfig(); saveUIConfig();
service->reloadConfig(SEGMENT_CONFIG);
LOG_INFO("Setting names to %s", option.value ? "long" : "short"); LOG_INFO("Setting names to %s", option.value ? "long" : "short");
}); });
@@ -1529,7 +1572,7 @@ void menuHandler::resetNodeDBMenu()
nodeDB->resetNodes(1); nodeDB->resetNodes(1);
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
} else if (selected == 0) { } else if (selected == 0) {
menuQueue = NodeBaseMenu; menuQueue = node_base_menu;
screen->runNow(); screen->runNow();
} }
}; };
@@ -1551,7 +1594,7 @@ void menuHandler::compassNorthMenu()
auto bannerOptions = createStaticBannerOptions("North Directions?", compassOptions, compassLabels, auto bannerOptions = createStaticBannerOptions("North Directions?", compassOptions, compassLabels,
[](const CompassOption &option, int) -> void { [](const CompassOption &option, int) -> void {
if (option.action == OptionsAction::Back) { if (option.action == OptionsAction::Back) {
menuQueue = PositionBaseMenu; menuQueue = position_base_menu;
screen->runNow(); screen->runNow();
return; return;
} }
@@ -1596,7 +1639,7 @@ void menuHandler::GPSToggleMenu()
auto bannerOptions = auto bannerOptions =
createStaticBannerOptions("Toggle GPS", gpsToggleOptions, toggleLabels, [](const GPSToggleOption &option, int) -> void { createStaticBannerOptions("Toggle GPS", gpsToggleOptions, toggleLabels, [](const GPSToggleOption &option, int) -> void {
if (option.action == OptionsAction::Back) { if (option.action == OptionsAction::Back) {
menuQueue = PositionBaseMenu; menuQueue = position_base_menu;
screen->runNow(); screen->runNow();
return; return;
} }
@@ -1661,7 +1704,7 @@ void menuHandler::GPSFormatMenu()
auto onSelection = [](const GPSFormatOption &option, int) -> void { auto onSelection = [](const GPSFormatOption &option, int) -> void {
if (option.action == OptionsAction::Back) { if (option.action == OptionsAction::Back) {
menuQueue = PositionBaseMenu; menuQueue = position_base_menu;
screen->runNow(); screen->runNow();
return; return;
} }
@@ -1716,7 +1759,7 @@ void menuHandler::GPSSmartPositionMenu()
bannerOptions.optionsCount = 3; bannerOptions.optionsCount = 3;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 0) { if (selected == 0) {
menuQueue = PositionBaseMenu; menuQueue = position_base_menu;
screen->runNow(); screen->runNow();
} else if (selected == 1) { } else if (selected == 1) {
config.position.position_broadcast_smart_enabled = true; config.position.position_broadcast_smart_enabled = true;
@@ -1745,7 +1788,7 @@ void menuHandler::GPSUpdateIntervalMenu()
bannerOptions.optionsCount = 16; bannerOptions.optionsCount = 16;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 0) { if (selected == 0) {
menuQueue = PositionBaseMenu; menuQueue = position_base_menu;
screen->runNow(); screen->runNow();
} else if (selected == 1) { } else if (selected == 1) {
config.position.gps_update_interval = 8; config.position.gps_update_interval = 8;
@@ -1833,7 +1876,7 @@ void menuHandler::GPSPositionBroadcastMenu()
bannerOptions.optionsCount = 17; bannerOptions.optionsCount = 17;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == 0) { if (selected == 0) {
menuQueue = PositionBaseMenu; menuQueue = position_base_menu;
screen->runNow(); screen->runNow();
} else if (selected == 1) { } else if (selected == 1) {
config.position.position_broadcast_secs = 60; config.position.position_broadcast_secs = 60;
@@ -1916,7 +1959,7 @@ void menuHandler::GPSPositionBroadcastMenu()
#endif #endif
void menuHandler::bluetoothToggleMenu() void menuHandler::BluetoothToggleMenu()
{ {
static const char *optionsArray[] = {"Back", "Enabled", "Disabled"}; static const char *optionsArray[] = {"Back", "Enabled", "Disabled"};
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
@@ -2044,7 +2087,7 @@ void menuHandler::TFTColorPickerMenu(OLEDDisplay *display)
auto bannerOptions = createStaticBannerOptions( auto bannerOptions = createStaticBannerOptions(
"Select Screen Color", colorOptions, colorLabels, [display](const ScreenColorOption &option, int) -> void { "Select Screen Color", colorOptions, colorLabels, [display](const ScreenColorOption &option, int) -> void {
if (option.action == OptionsAction::Back) { if (option.action == OptionsAction::Back) {
menuQueue = SystemBaseMenu; menuQueue = system_base_menu;
screen->runNow(); screen->runNow();
return; return;
} }
@@ -2139,7 +2182,7 @@ void menuHandler::rebootMenu()
messageStore.saveToFlash(); messageStore.saveToFlash();
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000; rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
} else { } else {
menuQueue = PowerMenu; menuQueue = power_menu;
screen->runNow(); screen->runNow();
} }
}; };
@@ -2161,7 +2204,7 @@ void menuHandler::shutdownMenu()
InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0}; InputEvent event = {.inputEvent = (input_broker_event)INPUT_BROKER_SHUTDOWN, .kbchar = 0, .touchX = 0, .touchY = 0};
inputBroker->injectInputEvent(&event); inputBroker->injectInputEvent(&event);
} else { } else {
menuQueue = PowerMenu; menuQueue = power_menu;
screen->runNow(); screen->runNow();
} }
}; };
@@ -2222,14 +2265,14 @@ void menuHandler::testMenu()
bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == NumberPicker) { if (selected == NumberPicker) {
menuQueue = NumberTest; menuQueue = number_test;
screen->runNow(); screen->runNow();
} else if (selected == ShowChirpy) { } else if (selected == ShowChirpy) {
screen->toggleFrameVisibility("chirpy"); screen->toggleFrameVisibility("chirpy");
screen->setFrames(Screen::FOCUS_SYSTEM); screen->setFrames(Screen::FOCUS_SYSTEM);
} else { } else {
menuQueue = SystemBaseMenu; menuQueue = system_base_menu;
screen->runNow(); screen->runNow();
} }
}; };
@@ -2253,7 +2296,7 @@ void menuHandler::wifiBaseMenu()
bannerOptions.optionsCount = 2; bannerOptions.optionsCount = 2;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Wifi_toggle) { if (selected == Wifi_toggle) {
menuQueue = WifiToggleMenu; menuQueue = wifi_toggle_menu;
screen->runNow(); screen->runNow();
} }
}; };
@@ -2302,9 +2345,9 @@ void menuHandler::screenOptionsMenu()
hasSupportBrightness = false; hasSupportBrightness = false;
#endif #endif
enum optionsNumbers { Back, Brightness, ScreenColor, FrameToggles, DisplayUnits, MessageBubbles }; enum optionsNumbers { Back, Brightness, ScreenColor, FrameToggles, DisplayUnits };
static const char *optionsArray[6] = {"Back"}; static const char *optionsArray[5] = {"Back"};
static int optionsEnumArray[6] = {Back}; static int optionsEnumArray[5] = {Back};
int options = 1; int options = 1;
// Only show brightness for B&W displays // Only show brightness for B&W displays
@@ -2326,9 +2369,6 @@ void menuHandler::screenOptionsMenu()
optionsArray[options] = "Display Units"; optionsArray[options] = "Display Units";
optionsEnumArray[options++] = DisplayUnits; optionsEnumArray[options++] = DisplayUnits;
optionsArray[options] = "Message Bubbles";
optionsEnumArray[options++] = MessageBubbles;
BannerOverlayOptions bannerOptions; BannerOverlayOptions bannerOptions;
bannerOptions.message = "Display Options"; bannerOptions.message = "Display Options";
bannerOptions.optionsArrayPtr = optionsArray; bannerOptions.optionsArrayPtr = optionsArray;
@@ -2336,10 +2376,10 @@ void menuHandler::screenOptionsMenu()
bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Brightness) { if (selected == Brightness) {
menuHandler::menuQueue = menuHandler::BrightnessPicker; menuHandler::menuQueue = menuHandler::brightness_picker;
screen->runNow(); screen->runNow();
} else if (selected == ScreenColor) { } else if (selected == ScreenColor) {
menuHandler::menuQueue = menuHandler::TftColorMenuPicker; menuHandler::menuQueue = menuHandler::tftcolormenupicker;
screen->runNow(); screen->runNow();
} else if (selected == FrameToggles) { } else if (selected == FrameToggles) {
menuHandler::menuQueue = menuHandler::FrameToggles; menuHandler::menuQueue = menuHandler::FrameToggles;
@@ -2347,11 +2387,8 @@ void menuHandler::screenOptionsMenu()
} else if (selected == DisplayUnits) { } else if (selected == DisplayUnits) {
menuHandler::menuQueue = menuHandler::DisplayUnits; menuHandler::menuQueue = menuHandler::DisplayUnits;
screen->runNow(); screen->runNow();
} else if (selected == MessageBubbles) {
menuHandler::menuQueue = menuHandler::MessageBubblesMenu;
screen->runNow();
} else { } else {
menuQueue = SystemBaseMenu; menuQueue = system_base_menu;
screen->runNow(); screen->runNow();
} }
}; };
@@ -2387,16 +2424,16 @@ void menuHandler::powerMenu()
bannerOptions.optionsEnumPtr = optionsEnumArray; bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.bannerCallback = [](int selected) -> void { bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Reboot) { if (selected == Reboot) {
menuHandler::menuQueue = menuHandler::RebootMenu; menuHandler::menuQueue = menuHandler::reboot_menu;
screen->runNow(); screen->runNow();
} else if (selected == Shutdown) { } else if (selected == Shutdown) {
menuHandler::menuQueue = menuHandler::ShutdownMenu; menuHandler::menuQueue = menuHandler::shutdown_menu;
screen->runNow(); screen->runNow();
} else if (selected == MUI) { } else if (selected == MUI) {
menuHandler::menuQueue = menuHandler::MuiPicker; menuHandler::menuQueue = menuHandler::mui_picker;
screen->runNow(); screen->runNow();
} else { } else {
menuQueue = SystemBaseMenu; menuQueue = system_base_menu;
screen->runNow(); screen->runNow();
} }
}; };
@@ -2434,7 +2471,7 @@ void menuHandler::keyVerificationFinalPrompt()
} }
} }
void menuHandler::frameTogglesMenu() void menuHandler::FrameToggles_menu()
{ {
enum optionsNumbers { enum optionsNumbers {
Finish, Finish,
@@ -2578,7 +2615,7 @@ void menuHandler::frameTogglesMenu()
screen->showOverlayBanner(bannerOptions); screen->showOverlayBanner(bannerOptions);
} }
void menuHandler::displayUnitsMenu() void menuHandler::DisplayUnits_menu()
{ {
enum optionsNumbers { Back, MetricUnits, ImperialUnits }; enum optionsNumbers { Back, MetricUnits, ImperialUnits };
@@ -2599,34 +2636,7 @@ void menuHandler::displayUnitsMenu()
config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL; config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL;
service->reloadConfig(SEGMENT_CONFIG); service->reloadConfig(SEGMENT_CONFIG);
} else { } else {
menuHandler::menuQueue = menuHandler::ScreenOptionsMenu; menuHandler::menuQueue = menuHandler::screen_options_menu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::messageBubblesMenu()
{
enum optionsNumbers { Back, ShowBubbles, HideBubbles };
static const char *optionsArray[] = {"Back", "Show Bubbles", "Hide Bubbles"};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Message Bubbles";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsCount = 3;
bannerOptions.InitialSelected = config.display.enable_message_bubbles ? 1 : 2;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == ShowBubbles) {
config.display.enable_message_bubbles = true;
service->reloadConfig(SEGMENT_CONFIG);
LOG_INFO("Message bubbles enabled");
} else if (selected == HideBubbles) {
config.display.enable_message_bubbles = false;
service->reloadConfig(SEGMENT_CONFIG);
LOG_INFO("Message bubbles disabled");
} else {
menuHandler::menuQueue = menuHandler::ScreenOptionsMenu;
screen->runNow(); screen->runNow();
} }
}; };
@@ -2635,156 +2645,153 @@ void menuHandler::messageBubblesMenu()
void menuHandler::handleMenuSwitch(OLEDDisplay *display) void menuHandler::handleMenuSwitch(OLEDDisplay *display)
{ {
if (menuQueue != MenuNone) if (menuQueue != menu_none)
test_count = 0; test_count = 0;
switch (menuQueue) { switch (menuQueue) {
case MenuNone: case menu_none:
break; break;
case LoraMenu: case lora_Menu:
loraMenu(); loraMenu();
break; break;
case LoraPicker: case lora_picker:
LoraRegionPicker(); LoraRegionPicker();
break; break;
case DeviceRolePicker: case device_role_picker:
deviceRolePicker(); DeviceRolePicker();
break; break;
case RadioPresetPicker: case radio_preset_picker:
radioPresetPicker(); RadioPresetPicker();
break; break;
case FrequencySlot: case frequency_slot:
FrequencySlotPicker(); FrequencySlotPicker();
break; break;
case NoTimeoutLoraPicker: case no_timeout_lora_picker:
LoraRegionPicker(0); LoraRegionPicker(0);
break; break;
case TzPicker: case TZ_picker:
TZPicker(); TZPicker();
break; break;
case TwelveHourPicker: case twelve_hour_picker:
twelveHourPicker(); TwelveHourPicker();
break; break;
case ClockFacePicker: case clock_face_picker:
clockFacePicker(); ClockFacePicker();
break; break;
case ClockMenu: case clock_menu:
clockMenu(); clockMenu();
break; break;
case SystemBaseMenu: case system_base_menu:
systemBaseMenu(); systemBaseMenu();
break; break;
case PositionBaseMenu: case position_base_menu:
positionBaseMenu(); positionBaseMenu();
break; break;
case NodeBaseMenu: case node_base_menu:
nodeListMenu(); nodeListMenu();
break; break;
#if !MESHTASTIC_EXCLUDE_GPS #if !MESHTASTIC_EXCLUDE_GPS
case GpsToggleMenu: case gps_toggle_menu:
GPSToggleMenu(); GPSToggleMenu();
break; break;
case GpsFormatMenu: case gps_format_menu:
GPSFormatMenu(); GPSFormatMenu();
break; break;
case GpsSmartPositionMenu: case gps_smart_position_menu:
GPSSmartPositionMenu(); GPSSmartPositionMenu();
break; break;
case GpsUpdateIntervalMenu: case gps_update_interval_menu:
GPSUpdateIntervalMenu(); GPSUpdateIntervalMenu();
break; break;
case GpsPositionBroadcastMenu: case gps_position_broadcast_menu:
GPSPositionBroadcastMenu(); GPSPositionBroadcastMenu();
break; break;
#endif #endif
case CompassPointNorthMenu: case compass_point_north_menu:
compassNorthMenu(); compassNorthMenu();
break; break;
case ResetNodeDbMenu: case reset_node_db_menu:
resetNodeDBMenu(); resetNodeDBMenu();
break; break;
case BuzzerModeMenuPicker: case buzzermodemenupicker:
BuzzerModeMenu(); BuzzerModeMenu();
break; break;
case MuiPicker: case mui_picker:
switchToMUIMenu(); switchToMUIMenu();
break; break;
case TftColorMenuPicker: case tftcolormenupicker:
TFTColorPickerMenu(display); TFTColorPickerMenu(display);
break; break;
case BrightnessPicker: case brightness_picker:
BrightnessPickerMenu(); BrightnessPickerMenu();
break; break;
case NodeNameLengthMenu: case node_name_length_menu:
nodeNameLengthMenu(); nodeNameLengthMenu();
break; break;
case RebootMenu: case reboot_menu:
rebootMenu(); rebootMenu();
break; break;
case ShutdownMenu: case shutdown_menu:
shutdownMenu(); shutdownMenu();
break; break;
case NodePickerMenu: case NodePicker_menu:
NodePicker(); NodePicker();
break; break;
case ManageNodeMenu: case Manage_Node_menu:
manageNodeMenu(); ManageNodeMenu();
break; break;
case RemoveFavorite: case remove_favorite:
removeFavoriteMenu(); removeFavoriteMenu();
break; break;
case TraceRouteMenu: case trace_route_menu:
traceRouteMenu(); traceRouteMenu();
break; break;
case TestMenu: case test_menu:
testMenu(); testMenu();
break; break;
case NumberTest: case number_test:
numberTest(); numberTest();
break; break;
case WifiToggleMenu: case wifi_toggle_menu:
wifiToggleMenu(); wifiToggleMenu();
break; break;
case KeyVerificationInit: case key_verification_init:
keyVerificationInitMenu(); keyVerificationInitMenu();
break; break;
case KeyVerificationFinalPrompt: case key_verification_final_prompt:
keyVerificationFinalPrompt(); keyVerificationFinalPrompt();
break; break;
case BluetoothToggleMenu: case bluetooth_toggle_menu:
bluetoothToggleMenu(); BluetoothToggleMenu();
break; break;
case ScreenOptionsMenu: case screen_options_menu:
screenOptionsMenu(); screenOptionsMenu();
break; break;
case PowerMenu: case power_menu:
powerMenu(); powerMenu();
break; break;
case FrameToggles: case FrameToggles:
frameTogglesMenu(); FrameToggles_menu();
break; break;
case DisplayUnits: case DisplayUnits:
displayUnitsMenu(); DisplayUnits_menu();
break; break;
case ThrottleMessage: case throttle_message:
screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000); screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
break; break;
case MessageResponseMenu: case message_response_menu:
messageResponseMenu(); messageResponseMenu();
break; break;
case ReplyMenu: case reply_menu:
replyMenu(); replyMenu();
break; break;
case DeleteMessagesMenu: case delete_messages_menu:
deleteMessagesMenu(); deleteMessagesMenu();
break; break;
case MessageViewModeMenu: case message_viewmode_menu:
messageViewModeMenu(); messageViewModeMenu();
break; break;
case MessageBubblesMenu:
messageBubblesMenu();
break;
} }
menuQueue = MenuNone; menuQueue = menu_none;
} }
void menuHandler::saveUIConfig() void menuHandler::saveUIConfig()
+55 -57
View File
@@ -8,54 +8,53 @@ class menuHandler
{ {
public: public:
enum screenMenus { enum screenMenus {
MenuNone, menu_none,
LoraMenu, lora_Menu,
LoraPicker, lora_picker,
DeviceRolePicker, device_role_picker,
RadioPresetPicker, radio_preset_picker,
FrequencySlot, frequency_slot,
NoTimeoutLoraPicker, no_timeout_lora_picker,
TzPicker, TZ_picker,
TwelveHourPicker, twelve_hour_picker,
ClockFacePicker, clock_face_picker,
ClockMenu, clock_menu,
PositionBaseMenu, position_base_menu,
NodeBaseMenu, node_base_menu,
GpsToggleMenu, gps_toggle_menu,
GpsFormatMenu, gps_format_menu,
GpsSmartPositionMenu, gps_smart_position_menu,
GpsUpdateIntervalMenu, gps_update_interval_menu,
GpsPositionBroadcastMenu, gps_position_broadcast_menu,
CompassPointNorthMenu, compass_point_north_menu,
ResetNodeDbMenu, reset_node_db_menu,
BuzzerModeMenuPicker, buzzermodemenupicker,
MuiPicker, mui_picker,
TftColorMenuPicker, tftcolormenupicker,
BrightnessPicker, brightness_picker,
RebootMenu, reboot_menu,
ShutdownMenu, shutdown_menu,
NodePickerMenu, NodePicker_menu,
ManageNodeMenu, Manage_Node_menu,
RemoveFavorite, remove_favorite,
TestMenu, test_menu,
NumberTest, number_test,
WifiToggleMenu, wifi_toggle_menu,
BluetoothToggleMenu, bluetooth_toggle_menu,
ScreenOptionsMenu, screen_options_menu,
PowerMenu, power_menu,
SystemBaseMenu, system_base_menu,
KeyVerificationInit, key_verification_init,
KeyVerificationFinalPrompt, key_verification_final_prompt,
TraceRouteMenu, trace_route_menu,
ThrottleMessage, throttle_message,
MessageResponseMenu, message_response_menu,
MessageViewModeMenu, message_viewmode_menu,
ReplyMenu, reply_menu,
DeleteMessagesMenu, delete_messages_menu,
NodeNameLengthMenu, node_name_length_menu,
FrameToggles, FrameToggles,
DisplayUnits, DisplayUnits
MessageBubblesMenu
}; };
static screenMenus menuQueue; static screenMenus menuQueue;
static uint32_t pickedNodeNum; // node selected by NodePicker for ManageNodeMenu static uint32_t pickedNodeNum; // node selected by NodePicker for ManageNodeMenu
@@ -63,15 +62,15 @@ class menuHandler
static void OnboardMessage(); static void OnboardMessage();
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 RadioPresetPicker();
static void FrequencySlotPicker(); static void FrequencySlotPicker();
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();
static void TZPicker(); static void TZPicker();
static void twelveHourPicker(); static void TwelveHourPicker();
static void clockFacePicker(); static void ClockFacePicker();
static void messageResponseMenu(); static void messageResponseMenu();
static void messageViewModeMenu(); static void messageViewModeMenu();
static void replyMenu(); static void replyMenu();
@@ -96,7 +95,7 @@ class menuHandler
static void rebootMenu(); static void rebootMenu();
static void shutdownMenu(); static void shutdownMenu();
static void NodePicker(); static void NodePicker();
static void manageNodeMenu(); static void ManageNodeMenu();
static void addFavoriteMenu(); static void addFavoriteMenu();
static void removeFavoriteMenu(); static void removeFavoriteMenu();
static void traceRouteMenu(); static void traceRouteMenu();
@@ -107,16 +106,15 @@ class menuHandler
static void screenOptionsMenu(); static void screenOptionsMenu();
static void powerMenu(); static void powerMenu();
static void nodeNameLengthMenu(); static void nodeNameLengthMenu();
static void frameTogglesMenu(); static void FrameToggles_menu();
static void displayUnitsMenu(); static void DisplayUnits_menu();
static void messageBubblesMenu();
static void textMessageMenu(); static void textMessageMenu();
private: private:
static void saveUIConfig(); static void saveUIConfig();
static void keyVerificationInitMenu(); static void keyVerificationInitMenu();
static void keyVerificationFinalPrompt(); static void keyVerificationFinalPrompt();
static void bluetoothToggleMenu(); static void BluetoothToggleMenu();
}; };
/* Generic Menu Options designations */ /* Generic Menu Options designations */
@@ -142,7 +140,7 @@ struct ScreenColor {
uint8_t b; uint8_t b;
bool useVariant; bool useVariant;
explicit ScreenColor(uint8_t rIn = 0, uint8_t gIn = 0, uint8_t bIn = 0, bool variantIn = false) ScreenColor(uint8_t rIn = 0, uint8_t gIn = 0, uint8_t bIn = 0, bool variantIn = false)
: r(rIn), g(gIn), b(bIn), useVariant(variantIn) : r(rIn), g(gIn), b(bIn), useVariant(variantIn)
{ {
} }
+44 -234
View File
@@ -6,6 +6,7 @@
#include "MessageStore.h" #include "MessageStore.h"
#include "NodeDB.h" #include "NodeDB.h"
#include "UIRenderer.h" #include "UIRenderer.h"
#include "configuration.h"
#include "gps/RTC.h" #include "gps/RTC.h"
#include "graphics/Screen.h" #include "graphics/Screen.h"
#include "graphics/ScreenFonts.h" #include "graphics/ScreenFonts.h"
@@ -19,6 +20,7 @@
// External declarations // External declarations
extern bool hasUnreadMessage; extern bool hasUnreadMessage;
extern meshtastic_DeviceState devicestate;
extern graphics::Screen *screen; extern graphics::Screen *screen;
using graphics::Emote; using graphics::Emote;
@@ -47,7 +49,7 @@ static inline size_t utf8CharLen(uint8_t c)
} }
// Remove variation selectors (FE0F) and skin tone modifiers from emoji so they match your labels // Remove variation selectors (FE0F) and skin tone modifiers from emoji so they match your labels
static std::string normalizeEmoji(const std::string &s) std::string normalizeEmoji(const std::string &s)
{ {
std::string out; std::string out;
for (size_t i = 0; i < s.size();) { for (size_t i = 0; i < s.size();) {
@@ -80,7 +82,6 @@ uint32_t pauseStart = 0;
bool waitingToReset = false; bool waitingToReset = false;
bool scrollStarted = false; bool scrollStarted = false;
static bool didReset = false; static bool didReset = false;
static constexpr int MESSAGE_BLOCK_GAP = 6;
void scrollUp() void scrollUp()
{ {
@@ -110,6 +111,22 @@ void scrollDown()
void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, const Emote *emotes, int emoteCount) void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string &line, const Emote *emotes, int emoteCount)
{ {
std::string renderLine;
for (size_t i = 0; i < line.size();) {
uint8_t c = (uint8_t)line[i];
size_t len = utf8CharLen(c);
if (c == 0xEF && i + 2 < line.size() && (uint8_t)line[i + 1] == 0xB8 && (uint8_t)line[i + 2] == 0x8F) {
i += 3;
continue;
}
if (c == 0xF0 && i + 3 < line.size() && (uint8_t)line[i + 1] == 0x9F && (uint8_t)line[i + 2] == 0x8F &&
((uint8_t)line[i + 3] >= 0xBB && (uint8_t)line[i + 3] <= 0xBF)) {
i += 4;
continue;
}
renderLine.append(line, i, len);
i += len;
}
int cursorX = x; int cursorX = x;
const int fontHeight = FONT_HEIGHT_SMALL; const int fontHeight = FONT_HEIGHT_SMALL;
@@ -186,7 +203,8 @@ void drawStringWithEmotes(OLEDDisplay *display, int x, int y, const std::string
// Render the emote (if found) // Render the emote (if found)
if (matchedEmote && i == nextEmotePos) { if (matchedEmote && i == nextEmotePos) {
int iconY = y + (lineHeight - matchedEmote->height) / 2; // Vertically center emote relative to font baseline (not just midline)
int iconY = fontY + (fontHeight - matchedEmote->height) / 2;
display->drawXbm(cursorX, iconY, matchedEmote->width, matchedEmote->height, matchedEmote->bitmap); display->drawXbm(cursorX, iconY, matchedEmote->width, matchedEmote->height, matchedEmote->bitmap);
cursorX += matchedEmote->width + 1; cursorX += matchedEmote->width + 1;
i += emojiLen; i += emojiLen;
@@ -405,63 +423,6 @@ static inline int getRenderedLineWidth(OLEDDisplay *display, const std::string &
return totalWidth; return totalWidth;
} }
struct MessageBlock {
size_t start;
size_t end;
bool mine;
};
static int getDrawnLinePixelBottom(int lineTopY, const std::string &line, bool isHeaderLine)
{
if (isHeaderLine) {
return lineTopY + (FONT_HEIGHT_SMALL - 1);
}
int tallest = FONT_HEIGHT_SMALL;
for (int e = 0; e < numEmotes; ++e) {
if (line.find(emotes[e].label) != std::string::npos) {
if (emotes[e].height > tallest)
tallest = emotes[e].height;
}
}
const int lineHeight = std::max(FONT_HEIGHT_SMALL, tallest);
const int iconTop = lineTopY + (lineHeight - tallest) / 2;
return iconTop + tallest - 1;
}
static std::vector<MessageBlock> buildMessageBlocks(const std::vector<bool> &isHeaderVec, const std::vector<bool> &isMineVec)
{
std::vector<MessageBlock> blocks;
if (isHeaderVec.empty())
return blocks;
size_t start = 0;
bool mine = isMineVec[0];
for (size_t i = 1; i < isHeaderVec.size(); ++i) {
if (isHeaderVec[i]) {
MessageBlock b;
b.start = start;
b.end = i - 1;
b.mine = mine;
blocks.push_back(b);
start = i;
mine = isMineVec[i];
}
}
MessageBlock last;
last.start = start;
last.end = isHeaderVec.size() - 1;
last.mine = mine;
blocks.push_back(last);
return blocks;
}
static void drawMessageScrollbar(OLEDDisplay *display, int visibleHeight, int totalHeight, int scrollOffset, int startY) static void drawMessageScrollbar(OLEDDisplay *display, int visibleHeight, int totalHeight, int scrollOffset, int startY)
{ {
if (totalHeight <= visibleHeight) if (totalHeight <= visibleHeight)
@@ -521,18 +482,9 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
constexpr int LEFT_MARGIN = 2; constexpr int LEFT_MARGIN = 2;
constexpr int RIGHT_MARGIN = 2; constexpr int RIGHT_MARGIN = 2;
constexpr int SCROLLBAR_WIDTH = 3; constexpr int SCROLLBAR_WIDTH = 3;
constexpr int BUBBLE_PAD_X = 3;
constexpr int BUBBLE_PAD_Y = 4;
constexpr int BUBBLE_RADIUS = 4;
constexpr int BUBBLE_MIN_W = 24;
constexpr int BUBBLE_TEXT_INDENT = 2;
// Check if bubbles are enabled const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN;
const bool showBubbles = config.display.enable_message_bubbles;
const int textIndent = showBubbles ? (BUBBLE_PAD_X + BUBBLE_TEXT_INDENT) : LEFT_MARGIN;
// Derived widths
const int leftTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - (showBubbles ? (BUBBLE_PAD_X * 2) : 0);
const int rightTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - SCROLLBAR_WIDTH; const int rightTextWidth = SCREEN_WIDTH - LEFT_MARGIN - RIGHT_MARGIN - SCROLLBAR_WIDTH;
// Title string depending on mode // Title string depending on mode
@@ -595,28 +547,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
char chanType[32] = ""; char chanType[32] = "";
if (currentMode == ThreadMode::ALL) { if (currentMode == ThreadMode::ALL) {
if (m.dest == NODENUM_BROADCAST) { if (m.dest == NODENUM_BROADCAST) {
const char *name = channels.getName(m.channelIndex); snprintf(chanType, sizeof(chanType), "#%s", channels.getName(m.channelIndex));
if (currentResolution == ScreenResolution::Low || currentResolution == ScreenResolution::UltraLow) {
if (strcmp(name, "ShortTurbo") == 0)
name = "ShortT";
else if (strcmp(name, "ShortSlow") == 0)
name = "ShortS";
else if (strcmp(name, "ShortFast") == 0)
name = "ShortF";
else if (strcmp(name, "MediumSlow") == 0)
name = "MedS";
else if (strcmp(name, "MediumFast") == 0)
name = "MedF";
else if (strcmp(name, "LongSlow") == 0)
name = "LongS";
else if (strcmp(name, "LongFast") == 0)
name = "LongF";
else if (strcmp(name, "LongTurbo") == 0)
name = "LongT";
else if (strcmp(name, "LongMod") == 0)
name = "LongM";
}
snprintf(chanType, sizeof(chanType), "#%s", name);
} else { } else {
snprintf(chanType, sizeof(chanType), "(DM)"); snprintf(chanType, sizeof(chanType), "(DM)");
} }
@@ -683,8 +614,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
} }
// Shrink Sender name if needed // Shrink Sender name if needed
int availWidth = (mine ? rightTextWidth : leftTextWidth) - display->getStringWidth(timeBuf) - int availWidth = SCREEN_WIDTH - display->getStringWidth(timeBuf) - display->getStringWidth(chanType) -
display->getStringWidth(chanType) - display->getStringWidth(" @..."); display->getStringWidth(" @...") - 10;
if (availWidth < 0) if (availWidth < 0)
availWidth = 0; availWidth = 0;
@@ -736,8 +667,6 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
cachedLines = allLines; cachedLines = allLines;
cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader); cachedHeights = calculateLineHeights(cachedLines, emotes, isHeader);
std::vector<MessageBlock> blocks = buildMessageBlocks(isHeader, isMine);
// Scrolling logic (unchanged) // Scrolling logic (unchanged)
int totalHeight = 0; int totalHeight = 0;
for (size_t i = 0; i < cachedHeights.size(); ++i) for (size_t i = 0; i < cachedHeights.size(); ++i)
@@ -785,124 +714,12 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
int finalScroll = (int)scrollY; int finalScroll = (int)scrollY;
int yOffset = -finalScroll + getTextPositions(display)[1]; int yOffset = -finalScroll + getTextPositions(display)[1];
const int contentTop = getTextPositions(display)[1];
const int contentBottom = scrollBottom; // already excludes nav line
const int rightEdge = SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN;
const int bubbleGapY = std::max(1, MESSAGE_BLOCK_GAP / 2);
std::vector<int> lineTop;
lineTop.resize(cachedLines.size());
{
int acc = 0;
for (size_t i = 0; i < cachedLines.size(); ++i) {
lineTop[i] = yOffset + acc;
acc += cachedHeights[i];
}
}
// Draw bubbles (only if enabled)
if (showBubbles) {
for (size_t bi = 0; bi < blocks.size(); ++bi) {
const auto &b = blocks[bi];
if (b.start >= cachedLines.size() || b.end >= cachedLines.size() || b.start > b.end)
continue;
int visualTop = lineTop[b.start];
int topY;
if (isHeader[b.start]) {
// Header start
constexpr int BUBBLE_PAD_TOP_HEADER = 1; // try 1 or 2
topY = visualTop - BUBBLE_PAD_TOP_HEADER;
} else {
// Body start
bool thisLineHasEmote = false;
for (int e = 0; e < numEmotes; ++e) {
if (cachedLines[b.start].find(emotes[e].label) != std::string::npos) {
thisLineHasEmote = true;
break;
}
}
if (thisLineHasEmote) {
constexpr int EMOTE_PADDING_ABOVE = 4;
visualTop -= EMOTE_PADDING_ABOVE;
}
topY = visualTop - BUBBLE_PAD_Y;
}
int visualBottom = getDrawnLinePixelBottom(lineTop[b.end], cachedLines[b.end], isHeader[b.end]);
int bottomY = visualBottom + BUBBLE_PAD_Y;
if (bi + 1 < blocks.size()) {
int nextHeaderIndex = (int)blocks[bi + 1].start;
int nextTop = lineTop[nextHeaderIndex];
int maxBottom = nextTop - 1 - bubbleGapY;
if (bottomY > maxBottom)
bottomY = maxBottom;
}
if (bottomY <= topY + 2)
continue;
if (bottomY < contentTop || topY > contentBottom - 1)
continue;
int maxLineW = 0;
for (size_t i = b.start; i <= b.end; ++i) {
int w = 0;
if (isHeader[i]) {
w = display->getStringWidth(cachedLines[i].c_str());
if (b.mine)
w += 12; // room for ACK/NACK/relay mark
} else {
w = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes);
}
if (w > maxLineW)
maxLineW = w;
}
int bubbleW = std::max(BUBBLE_MIN_W, maxLineW + (textIndent * 2));
int bubbleH = (bottomY - topY) + 1;
int bubbleX = 0;
if (b.mine) {
bubbleX = rightEdge - bubbleW;
} else {
bubbleX = x;
}
if (bubbleX < x)
bubbleX = x;
if (bubbleX + bubbleW > rightEdge)
bubbleW = std::max(1, rightEdge - bubbleX);
// Draw rounded rectangle bubble
if (bubbleW > BUBBLE_RADIUS * 2 && bubbleH > BUBBLE_RADIUS * 2) {
const int r = BUBBLE_RADIUS;
const int bx = bubbleX;
const int by = topY;
const int bw = bubbleW;
const int bh = bubbleH;
// Draw the 4 corner arcs using drawCircleQuads
display->drawCircleQuads(bx + r, by + r, r, 0x2); // Top-left
display->drawCircleQuads(bx + bw - r - 1, by + r, r, 0x1); // Top-right
display->drawCircleQuads(bx + r, by + bh - r - 1, r, 0x4); // Bottom-left
display->drawCircleQuads(bx + bw - r - 1, by + bh - r - 1, r, 0x8); // Bottom-right
// Draw the 4 edges between corners
display->drawHorizontalLine(bx + r, by, bw - 2 * r); // Top edge
display->drawHorizontalLine(bx + r, by + bh - 1, bw - 2 * r); // Bottom edge
display->drawVerticalLine(bx, by + r, bh - 2 * r); // Left edge
display->drawVerticalLine(bx + bw - 1, by + r, bh - 2 * r); // Right edge
} else if (bubbleW > 1 && bubbleH > 1) {
// Fallback to simple rectangle for very small bubbles
display->drawRect(bubbleX, topY, bubbleW, bubbleH);
}
}
} // end if (showBubbles)
// Render visible lines // Render visible lines
int lineY = yOffset;
for (size_t i = 0; i < cachedLines.size(); ++i) { for (size_t i = 0; i < cachedLines.size(); ++i) {
int lineY = yOffset;
for (size_t j = 0; j < i; ++j)
lineY += cachedHeights[j];
if (lineY > -cachedHeights[i] && lineY < scrollBottom) { if (lineY > -cachedHeights[i] && lineY < scrollBottom) {
if (isHeader[i]) { if (isHeader[i]) {
@@ -911,28 +728,14 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
int headerX; int headerX;
if (isMine[i]) { if (isMine[i]) {
// push header left to avoid overlap with scrollbar // push header left to avoid overlap with scrollbar
headerX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - w - (showBubbles ? textIndent : 0); headerX = SCREEN_WIDTH - w - SCROLLBAR_WIDTH - RIGHT_MARGIN;
if (headerX < LEFT_MARGIN) if (headerX < LEFT_MARGIN)
headerX = LEFT_MARGIN; headerX = LEFT_MARGIN;
} else { } else {
headerX = x + textIndent; headerX = x;
} }
display->drawString(headerX, lineY, cachedLines[i].c_str()); display->drawString(headerX, lineY, cachedLines[i].c_str());
// Draw underline just under header text
int underlineY = lineY + FONT_HEIGHT_SMALL;
int underlineW = w;
int maxW = rightEdge - headerX;
if (maxW < 0)
maxW = 0;
if (underlineW > maxW)
underlineW = maxW;
for (int px = 0; px < underlineW; ++px) {
display->setPixel(headerX + px, underlineY);
}
// Draw ACK/NACK mark for our own messages // Draw ACK/NACK mark for our own messages
if (isMine[i]) { if (isMine[i]) {
int markX = headerX - 10; int markX = headerX - 10;
@@ -950,27 +753,32 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// AckStatus::NONE → show nothing // AckStatus::NONE → show nothing
} }
// Draw underline just under header text
int underlineY = lineY + FONT_HEIGHT_SMALL;
for (int px = 0; px < w; ++px) {
display->setPixel(headerX + px, underlineY);
}
} else { } else {
// Render message line // Render message line
if (isMine[i]) { if (isMine[i]) {
// Calculate actual rendered width including emotes // Calculate actual rendered width including emotes
int renderedWidth = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes); int renderedWidth = getRenderedLineWidth(display, cachedLines[i], emotes, numEmotes);
int rightX = (SCREEN_WIDTH - SCROLLBAR_WIDTH - RIGHT_MARGIN) - renderedWidth - (showBubbles ? textIndent : 0); int rightX = SCREEN_WIDTH - renderedWidth - SCROLLBAR_WIDTH - RIGHT_MARGIN;
if (rightX < LEFT_MARGIN) if (rightX < LEFT_MARGIN)
rightX = LEFT_MARGIN; rightX = LEFT_MARGIN;
drawStringWithEmotes(display, rightX, lineY, cachedLines[i], emotes, numEmotes); drawStringWithEmotes(display, rightX, lineY, cachedLines[i], emotes, numEmotes);
} else { } else {
drawStringWithEmotes(display, x + textIndent, lineY, cachedLines[i], emotes, numEmotes); drawStringWithEmotes(display, x, lineY, cachedLines[i], emotes, numEmotes);
} }
} }
} }
lineY += cachedHeights[i];
} }
int totalContentHeight = totalHeight;
int visibleHeight = usableHeight;
// Draw scrollbar // Draw scrollbar
drawMessageScrollbar(display, usableHeight, totalHeight, finalScroll, getTextPositions(display)[1]); drawMessageScrollbar(display, visibleHeight, totalContentHeight, finalScroll, getTextPositions(display)[1]);
graphics::drawCommonHeader(display, x, y, titleStr); graphics::drawCommonHeader(display, x, y, titleStr);
graphics::drawCommonFooter(display, x, y); graphics::drawCommonFooter(display, x, y);
} }
@@ -1033,6 +841,7 @@ std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, con
constexpr int HEADER_UNDERLINE_GAP = 0; // space between underline and first body line constexpr int HEADER_UNDERLINE_GAP = 0; // space between underline and first body line
constexpr int HEADER_UNDERLINE_PIX = 1; // underline thickness (1px row drawn) constexpr int HEADER_UNDERLINE_PIX = 1; // underline thickness (1px row drawn)
constexpr int BODY_LINE_LEADING = -4; // default vertical leading for normal body lines constexpr int BODY_LINE_LEADING = -4; // default vertical leading for normal body lines
constexpr int MESSAGE_BLOCK_GAP = 4; // gap after a message block before a new header
constexpr int EMOTE_PADDING_ABOVE = 4; // space above emote line (added to line above) constexpr int EMOTE_PADDING_ABOVE = 4; // space above emote line (added to line above)
constexpr int EMOTE_PADDING_BELOW = 3; // space below emote line (added to emote line) constexpr int EMOTE_PADDING_BELOW = 3; // space below emote line (added to emote line)
@@ -1042,7 +851,6 @@ std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, con
for (size_t idx = 0; idx < lines.size(); ++idx) { for (size_t idx = 0; idx < lines.size(); ++idx) {
const auto &line = lines[idx]; const auto &line = lines[idx];
const int baseHeight = FONT_HEIGHT_SMALL; const int baseHeight = FONT_HEIGHT_SMALL;
int lineHeight = baseHeight;
// Detect if THIS line or NEXT line contains an emote // Detect if THIS line or NEXT line contains an emote
bool hasEmote = false; bool hasEmote = false;
@@ -1064,6 +872,8 @@ std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, con
} }
} }
int lineHeight = baseHeight;
if (isHeaderVec[idx]) { if (isHeaderVec[idx]) {
// Header line spacing // Header line spacing
lineHeight = baseHeight + HEADER_UNDERLINE_PIX + HEADER_UNDERLINE_GAP; lineHeight = baseHeight + HEADER_UNDERLINE_PIX + HEADER_UNDERLINE_GAP;
@@ -1112,7 +922,7 @@ void handleNewMessage(OLEDDisplay *display, const StoredMessage &sm, const mesht
// Banner logic // Banner logic
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from); const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from);
char longName[48] = "?"; char longName[48] = "???";
if (node && node->user.long_name) { if (node && node->user.long_name) {
strncpy(longName, node->user.long_name, sizeof(longName) - 1); strncpy(longName, node->user.long_name, sizeof(longName) - 1);
longName[sizeof(longName) - 1] = '\0'; longName[sizeof(longName) - 1] = '\0';
+3 -10
View File
@@ -55,7 +55,7 @@ InkHUD::Tile *InkHUD::Applet::getTile()
} }
// Draw the applet // Draw the applet
void InkHUD::Applet::render(bool full) void InkHUD::Applet::render()
{ {
assert(assignedTile); // Ensure that we have a tile assert(assignedTile); // Ensure that we have a tile
assert(assignedTile->getAssignedApplet() == this); // Ensure that we have a reciprocal link with the tile assert(assignedTile->getAssignedApplet() == this); // Ensure that we have a reciprocal link with the tile
@@ -65,11 +65,10 @@ void InkHUD::Applet::render(bool full)
wantRender = false; // Flag set by requestUpdate wantRender = false; // Flag set by requestUpdate
wantAutoshow = false; // Flag set by requestAutoShow. May or may not have been honored. wantAutoshow = false; // Flag set by requestAutoShow. May or may not have been honored.
wantUpdateType = Drivers::EInk::UpdateTypes::UNSPECIFIED; // Update type we wanted. May on may not have been granted. wantUpdateType = Drivers::EInk::UpdateTypes::UNSPECIFIED; // Update type we wanted. May on may not have been granted.
wantFullRender = true; // Default to a full render
updateDimensions(); updateDimensions();
resetDrawingSpace(); resetDrawingSpace();
onRender(full); // Draw the applet onRender(); // Derived applet's drawing takes place here
// Handle "Tile Highlighting" // Handle "Tile Highlighting"
// Some devices may use an auxiliary button to switch between tiles // Some devices may use an auxiliary button to switch between tiles
@@ -116,11 +115,6 @@ Drivers::EInk::UpdateTypes InkHUD::Applet::wantsUpdateType()
return wantUpdateType; return wantUpdateType;
} }
bool InkHUD::Applet::wantsFullRender()
{
return wantFullRender;
}
// Get size of the applet's drawing space from its tile // Get size of the applet's drawing space from its tile
// Performed immediately before derived applet's drawing code runs // Performed immediately before derived applet's drawing code runs
void InkHUD::Applet::updateDimensions() void InkHUD::Applet::updateDimensions()
@@ -148,11 +142,10 @@ void InkHUD::Applet::resetDrawingSpace()
// Once the renderer has given other applets a chance to process whatever event we just detected, // Once the renderer has given other applets a chance to process whatever event we just detected,
// it will run Applet::render(), which may draw our applet to screen, if it is shown (foreground) // it will run Applet::render(), which may draw our applet to screen, if it is shown (foreground)
// We should requestUpdate even if our applet is currently background, because this might be changed by autoshow // We should requestUpdate even if our applet is currently background, because this might be changed by autoshow
void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type, bool full) void InkHUD::Applet::requestUpdate(Drivers::EInk::UpdateTypes type)
{ {
wantRender = true; wantRender = true;
wantUpdateType = type; wantUpdateType = type;
wantFullRender = full;
inkhud->requestUpdate(); inkhud->requestUpdate();
} }
+4 -10
View File
@@ -64,11 +64,10 @@ class Applet : public GFX
// Rendering // Rendering
void render(bool full); // Draw the applet void render(); // Draw the applet
bool wantsToRender(); // Check whether applet wants to render bool wantsToRender(); // Check whether applet wants to render
bool wantsToAutoshow(); // Check whether applet wants to become foreground bool wantsToAutoshow(); // Check whether applet wants to become foreground
Drivers::EInk::UpdateTypes wantsUpdateType(); // Check which display update type the applet would prefer Drivers::EInk::UpdateTypes wantsUpdateType(); // Check which display update type the applet would prefer
bool wantsFullRender(); // Check whether applet wants to render over its previous render
void updateDimensions(); // Get current size from tile void updateDimensions(); // Get current size from tile
void resetDrawingSpace(); // Makes sure every render starts with same parameters void resetDrawingSpace(); // Makes sure every render starts with same parameters
@@ -83,7 +82,7 @@ class Applet : public GFX
// Event handlers // Event handlers
virtual void onRender(bool full) = 0; // For drawing the applet virtual void onRender() = 0; // All drawing happens here
virtual void onActivate() {} virtual void onActivate() {}
virtual void onDeactivate() {} virtual void onDeactivate() {}
virtual void onForeground() {} virtual void onForeground() {}
@@ -97,9 +96,6 @@ class Applet : public GFX
virtual void onNavDown() {} virtual void onNavDown() {}
virtual void onNavLeft() {} virtual void onNavLeft() {}
virtual void onNavRight() {} virtual void onNavRight() {}
virtual void onFreeText(char c) {}
virtual void onFreeTextDone() {}
virtual void onFreeTextCancel() {}
virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification
@@ -112,9 +108,8 @@ class Applet : public GFX
protected: protected:
void drawPixel(int16_t x, int16_t y, uint16_t color) override; // Place a single pixel. All drawing output passes through here void drawPixel(int16_t x, int16_t y, uint16_t color) override; // Place a single pixel. All drawing output passes through here
void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED, void requestUpdate(EInk::UpdateTypes type = EInk::UpdateTypes::UNSPECIFIED); // Ask WindowManager to schedule a display update
bool full = true); // Ask WindowManager to schedule a display update void requestAutoshow(); // Ask for applet to be moved to foreground
void requestAutoshow(); // Ask for applet to be moved to foreground
uint16_t X(float f); // Map applet width, mapped from 0 to 1.0 uint16_t X(float f); // Map applet width, mapped from 0 to 1.0
uint16_t Y(float f); // Map applet height, mapped from 0 to 1.0 uint16_t Y(float f); // Map applet height, mapped from 0 to 1.0
@@ -169,7 +164,6 @@ class Applet : public GFX
bool wantAutoshow = false; // Does the applet have new data it would like to display in foreground? bool wantAutoshow = false; // Does the applet have new data it would like to display in foreground?
NicheGraphics::Drivers::EInk::UpdateTypes wantUpdateType = NicheGraphics::Drivers::EInk::UpdateTypes wantUpdateType =
NicheGraphics::Drivers::EInk::UpdateTypes::UNSPECIFIED; // Which update method we'd prefer when redrawing the display NicheGraphics::Drivers::EInk::UpdateTypes::UNSPECIFIED; // Which update method we'd prefer when redrawing the display
bool wantFullRender = true; // Render with a fresh canvas
using GFX::setFont; // Make sure derived classes use AppletFont instead of AdafruitGFX fonts directly using GFX::setFont; // Make sure derived classes use AppletFont instead of AdafruitGFX fonts directly
using GFX::setRotation; // Block setRotation calls. Rotation is handled globally by WindowManager. using GFX::setRotation; // Block setRotation calls. Rotation is handled globally by WindowManager.
@@ -4,7 +4,7 @@
using namespace NicheGraphics; using namespace NicheGraphics;
void InkHUD::MapApplet::onRender(bool full) void InkHUD::MapApplet::onRender()
{ {
// Abort if no markers to render // Abort if no markers to render
if (!enoughMarkers()) { if (!enoughMarkers()) {
@@ -27,7 +27,7 @@ namespace NicheGraphics::InkHUD
class MapApplet : public Applet class MapApplet : public Applet
{ {
public: public:
void onRender(bool full) override; void onRender() override;
protected: protected:
virtual bool shouldDrawNode(meshtastic_NodeInfoLite *node) { return true; } // Allow derived applets to filter the nodes virtual bool shouldDrawNode(meshtastic_NodeInfoLite *node) { return true; } // Allow derived applets to filter the nodes
@@ -103,7 +103,7 @@ uint8_t InkHUD::NodeListApplet::maxCards()
} }
// Draw, using info which derived applet placed into NodeListApplet::cards for us // Draw, using info which derived applet placed into NodeListApplet::cards for us
void InkHUD::NodeListApplet::onRender(bool full) void InkHUD::NodeListApplet::onRender()
{ {
// ================================ // ================================
@@ -46,7 +46,7 @@ class NodeListApplet : public Applet, public MeshModule
public: public:
NodeListApplet(const char *name); NodeListApplet(const char *name);
void onRender(bool full) override; void onRender() override;
bool wantPacket(const meshtastic_MeshPacket *p) override; bool wantPacket(const meshtastic_MeshPacket *p) override;
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
@@ -6,7 +6,7 @@ using namespace NicheGraphics;
// All drawing happens here // All drawing happens here
// Our basic example doesn't do anything useful. It just passively prints some text. // Our basic example doesn't do anything useful. It just passively prints some text.
void InkHUD::BasicExampleApplet::onRender(bool full) void InkHUD::BasicExampleApplet::onRender()
{ {
printAt(0, 0, "Hello, World!"); printAt(0, 0, "Hello, World!");
@@ -28,7 +28,7 @@ class BasicExampleApplet : public Applet
// You must have an onRender() method // You must have an onRender() method
// All drawing happens here // All drawing happens here
void onRender(bool full) override; void onRender() override;
}; };
} // namespace NicheGraphics::InkHUD } // namespace NicheGraphics::InkHUD
@@ -35,7 +35,7 @@ ProcessMessage InkHUD::NewMsgExampleApplet::handleReceived(const meshtastic_Mesh
// We can trigger a render by calling requestUpdate() // We can trigger a render by calling requestUpdate()
// Render might be called by some external source // Render might be called by some external source
// We should always be ready to draw // We should always be ready to draw
void InkHUD::NewMsgExampleApplet::onRender(bool full) void InkHUD::NewMsgExampleApplet::onRender()
{ {
printAt(0, 0, "Example: NewMsg", LEFT, TOP); // Print top-left corner of text at (0,0) printAt(0, 0, "Example: NewMsg", LEFT, TOP); // Print top-left corner of text at (0,0)
@@ -34,7 +34,7 @@ class NewMsgExampleApplet : public Applet, public SinglePortModule
NewMsgExampleApplet() : SinglePortModule("NewMsgExampleApplet", meshtastic_PortNum_TEXT_MESSAGE_APP) {} NewMsgExampleApplet() : SinglePortModule("NewMsgExampleApplet", meshtastic_PortNum_TEXT_MESSAGE_APP) {}
// All drawing happens here // All drawing happens here
void onRender(bool full) override; void onRender() override;
// Your applet might also want to use some of these // Your applet might also want to use some of these
// Useful for setting up or tidying up // Useful for setting up or tidying up
@@ -10,7 +10,7 @@ InkHUD::AlignStickApplet::AlignStickApplet()
bringToForeground(); bringToForeground();
} }
void InkHUD::AlignStickApplet::onRender(bool full) void InkHUD::AlignStickApplet::onRender()
{ {
setFont(fontMedium); setFont(fontMedium);
printAt(0, 0, "Align Joystick:"); printAt(0, 0, "Align Joystick:");
@@ -152,17 +152,19 @@ void InkHUD::AlignStickApplet::onBackground()
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
void InkHUD::AlignStickApplet::onButtonLongPress() void InkHUD::AlignStickApplet::onButtonLongPress()
{ {
sendToBackground(); sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
void InkHUD::AlignStickApplet::onExitLong() void InkHUD::AlignStickApplet::onExitLong()
{ {
sendToBackground(); sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
void InkHUD::AlignStickApplet::onNavUp() void InkHUD::AlignStickApplet::onNavUp()
@@ -170,6 +172,7 @@ void InkHUD::AlignStickApplet::onNavUp()
settings->joystick.aligned = true; settings->joystick.aligned = true;
sendToBackground(); sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
void InkHUD::AlignStickApplet::onNavDown() void InkHUD::AlignStickApplet::onNavDown()
@@ -178,6 +181,7 @@ void InkHUD::AlignStickApplet::onNavDown()
settings->joystick.aligned = true; settings->joystick.aligned = true;
sendToBackground(); sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
void InkHUD::AlignStickApplet::onNavLeft() void InkHUD::AlignStickApplet::onNavLeft()
@@ -186,6 +190,7 @@ void InkHUD::AlignStickApplet::onNavLeft()
settings->joystick.aligned = true; settings->joystick.aligned = true;
sendToBackground(); sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
void InkHUD::AlignStickApplet::onNavRight() void InkHUD::AlignStickApplet::onNavRight()
@@ -194,6 +199,7 @@ void InkHUD::AlignStickApplet::onNavRight()
settings->joystick.aligned = true; settings->joystick.aligned = true;
sendToBackground(); sendToBackground();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
#endif #endif
@@ -23,7 +23,7 @@ class AlignStickApplet : public SystemApplet
public: public:
AlignStickApplet(); AlignStickApplet();
void onRender(bool full) override; void onRender() override;
void onForeground() override; void onForeground() override;
void onBackground() override; void onBackground() override;
void onButtonLongPress() override; void onButtonLongPress() override;
@@ -6,8 +6,6 @@ using namespace NicheGraphics;
InkHUD::BatteryIconApplet::BatteryIconApplet() InkHUD::BatteryIconApplet::BatteryIconApplet()
{ {
alwaysRender = true; // render everytime the screen is updated
// Show at boot, if user has previously enabled the feature // Show at boot, if user has previously enabled the feature
if (settings->optionalFeatures.batteryIcon) if (settings->optionalFeatures.batteryIcon)
bringToForeground(); bringToForeground();
@@ -46,7 +44,7 @@ int InkHUD::BatteryIconApplet::onPowerStatusUpdate(const meshtastic::Status *sta
return 0; // Tell Observable to continue informing other observers return 0; // Tell Observable to continue informing other observers
} }
void InkHUD::BatteryIconApplet::onRender(bool full) void InkHUD::BatteryIconApplet::onRender()
{ {
// Fill entire tile // Fill entire tile
// - size of icon controlled by size of tile // - size of icon controlled by size of tile
@@ -23,7 +23,7 @@ class BatteryIconApplet : public SystemApplet
public: public:
BatteryIconApplet(); BatteryIconApplet();
void onRender(bool full) override; void onRender() override;
int onPowerStatusUpdate(const meshtastic::Status *status); // Called when new info about battery is available int onPowerStatusUpdate(const meshtastic::Status *status); // Called when new info about battery is available
private: private:
@@ -1,257 +0,0 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./KeyboardApplet.h"
using namespace NicheGraphics;
InkHUD::KeyboardApplet::KeyboardApplet()
{
// Calculate row widths
for (uint8_t row = 0; row < KBD_ROWS; row++) {
rowWidths[row] = 0;
for (uint8_t col = 0; col < KBD_COLS; col++)
rowWidths[row] += keyWidths[row * KBD_COLS + col];
}
}
void InkHUD::KeyboardApplet::onRender(bool full)
{
uint16_t em = fontSmall.lineHeight(); // 16 pt
uint16_t keyH = Y(1.0) / KBD_ROWS;
int16_t keyTopPadding = (keyH - fontSmall.lineHeight()) / 2;
if (full) { // Draw full keyboard
for (uint8_t row = 0; row < KBD_ROWS; row++) {
// Calculate the remaining space to be used as padding
int16_t keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4);
// Draw keys
uint16_t xPos = 0;
for (uint8_t col = 0; col < KBD_COLS; col++) {
Color fgcolor = BLACK;
uint8_t index = row * KBD_COLS + col;
uint16_t keyX = ((xPos * em) >> 4) + ((col * keyXPadding) / (KBD_COLS - 1));
uint16_t keyY = row * keyH;
uint16_t keyW = (keyWidths[index] * em) >> 4;
if (index == selectedKey) {
fgcolor = WHITE;
fillRect(keyX, keyY, keyW, keyH, BLACK);
}
drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[index], fgcolor);
xPos += keyWidths[index];
}
}
} else { // Only draw the difference
if (selectedKey != prevSelectedKey) {
// Draw previously selected key
uint8_t row = prevSelectedKey / KBD_COLS;
int16_t keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4);
uint16_t xPos = 0;
for (uint8_t i = prevSelectedKey - (prevSelectedKey % KBD_COLS); i < prevSelectedKey; i++)
xPos += keyWidths[i];
uint16_t keyX = ((xPos * em) >> 4) + (((prevSelectedKey % KBD_COLS) * keyXPadding) / (KBD_COLS - 1));
uint16_t keyY = row * keyH;
uint16_t keyW = (keyWidths[prevSelectedKey] * em) >> 4;
fillRect(keyX, keyY, keyW, keyH, WHITE);
drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[prevSelectedKey], BLACK);
// Draw newly selected key
row = selectedKey / KBD_COLS;
keyXPadding = X(1.0) - ((rowWidths[row] * em) >> 4);
xPos = 0;
for (uint8_t i = selectedKey - (selectedKey % KBD_COLS); i < selectedKey; i++)
xPos += keyWidths[i];
keyX = ((xPos * em) >> 4) + (((selectedKey % KBD_COLS) * keyXPadding) / (KBD_COLS - 1));
keyY = row * keyH;
keyW = (keyWidths[selectedKey] * em) >> 4;
fillRect(keyX, keyY, keyW, keyH, BLACK);
drawKeyLabel(keyX, keyY + keyTopPadding, keyW, keys[selectedKey], WHITE);
}
}
prevSelectedKey = selectedKey;
}
// Draw the key label corresponding to the char
// for most keys it draws the character itself
// for ['\b', '\n', ' ', '\x1b'] it draws special glyphs
void InkHUD::KeyboardApplet::drawKeyLabel(uint16_t left, uint16_t top, uint16_t width, char key, Color color)
{
if (key == '\b') {
// Draw backspace glyph: 13 x 9 px
/**
* [][][][][][][][][]
* [][] []
* [][] [] [] []
* [][] [] [] []
* [][] [] []
* [][] [] [] []
* [][] [] [] []
* [][] []
* [][][][][][][][][]
*/
const uint8_t bsBitmap[] = {0x0f, 0xf8, 0x18, 0x08, 0x32, 0x28, 0x61, 0x48, 0xc0,
0x88, 0x61, 0x48, 0x32, 0x28, 0x18, 0x08, 0x0f, 0xf8};
uint16_t leftPadding = (width - 13) >> 1;
drawBitmap(left + leftPadding, top + 1, bsBitmap, 13, 9, color);
} else if (key == '\n') {
// Draw done glyph: 12 x 9 px
/**
* [][]
* [][]
* [][]
* [][]
* [][]
* [][] [][]
* [][] [][]
* [][][]
* []
*/
const uint8_t doneBitmap[] = {0x00, 0x30, 0x00, 0x60, 0x00, 0xc0, 0x01, 0x80, 0x03,
0x00, 0xc6, 0x00, 0x6c, 0x00, 0x38, 0x00, 0x10, 0x00};
uint16_t leftPadding = (width - 12) >> 1;
drawBitmap(left + leftPadding, top + 1, doneBitmap, 12, 9, color);
} else if (key == ' ') {
// Draw space glyph: 13 x 9 px
/**
*
*
*
*
* [] []
* [] []
* [][][][][][][][][][][][][]
*
*
*/
const uint8_t spaceBitmap[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x08, 0x80, 0x08, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00};
uint16_t leftPadding = (width - 13) >> 1;
drawBitmap(left + leftPadding, top + 1, spaceBitmap, 13, 9, color);
} else if (key == '\x1b') {
setTextColor(color);
std::string keyText = "ESC";
uint16_t leftPadding = (width - getTextWidth(keyText)) >> 1;
printAt(left + leftPadding, top, keyText);
} else {
setTextColor(color);
if (key >= 0x61)
key -= 32; // capitalize
std::string keyText = std::string(1, key);
uint16_t leftPadding = (width - getTextWidth(keyText)) >> 1;
printAt(left + leftPadding, top, keyText);
}
}
void InkHUD::KeyboardApplet::onForeground()
{
handleInput = true; // Intercept the button input for our applet
// Select the first key
selectedKey = 0;
prevSelectedKey = 0;
}
void InkHUD::KeyboardApplet::onBackground()
{
handleInput = false;
}
void InkHUD::KeyboardApplet::onButtonShortPress()
{
char key = keys[selectedKey];
if (key == '\n') {
inkhud->freeTextDone();
inkhud->closeKeyboard();
} else if (key == '\x1b') {
inkhud->freeTextCancel();
inkhud->closeKeyboard();
} else {
inkhud->freeText(key);
}
}
void InkHUD::KeyboardApplet::onButtonLongPress()
{
char key = keys[selectedKey];
if (key == '\n') {
inkhud->freeTextDone();
inkhud->closeKeyboard();
} else if (key == '\x1b') {
inkhud->freeTextCancel();
inkhud->closeKeyboard();
} else {
if (key >= 0x61)
key -= 32; // capitalize
inkhud->freeText(key);
}
}
void InkHUD::KeyboardApplet::onExitShort()
{
inkhud->freeTextCancel();
inkhud->closeKeyboard();
}
void InkHUD::KeyboardApplet::onExitLong()
{
inkhud->freeTextCancel();
inkhud->closeKeyboard();
}
void InkHUD::KeyboardApplet::onNavUp()
{
if (selectedKey < KBD_COLS) // wrap
selectedKey += KBD_COLS * (KBD_ROWS - 1);
else // move 1 row back
selectedKey -= KBD_COLS;
// Request rendering over the previously drawn render
requestUpdate(EInk::UpdateTypes::FAST, false);
// Force an update to bypass lockRequests
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
}
void InkHUD::KeyboardApplet::onNavDown()
{
selectedKey += KBD_COLS;
selectedKey %= (KBD_COLS * KBD_ROWS);
// Request rendering over the previously drawn render
requestUpdate(EInk::UpdateTypes::FAST, false);
// Force an update to bypass lockRequests
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
}
void InkHUD::KeyboardApplet::onNavLeft()
{
if (selectedKey % KBD_COLS == 0) // wrap
selectedKey += KBD_COLS - 1;
else // move 1 column back
selectedKey--;
// Request rendering over the previously drawn render
requestUpdate(EInk::UpdateTypes::FAST, false);
// Force an update to bypass lockRequests
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
}
void InkHUD::KeyboardApplet::onNavRight()
{
if (selectedKey % KBD_COLS == KBD_COLS - 1) // wrap
selectedKey -= KBD_COLS - 1;
else // move 1 column forward
selectedKey++;
// Request rendering over the previously drawn render
requestUpdate(EInk::UpdateTypes::FAST, false);
// Force an update to bypass lockRequests
inkhud->forceUpdate(EInk::UpdateTypes::FAST);
}
uint16_t InkHUD::KeyboardApplet::getKeyboardHeight()
{
const uint16_t keyH = fontSmall.lineHeight() * 1.2;
return keyH * KBD_ROWS;
}
#endif
@@ -1,66 +0,0 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
/*
System Applet to render an on-screeen keyboard
*/
#pragma once
#include "configuration.h"
#include "graphics/niche/InkHUD/InkHUD.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
#include <string>
namespace NicheGraphics::InkHUD
{
class KeyboardApplet : public SystemApplet
{
public:
KeyboardApplet();
void onRender(bool full) override;
void onForeground() override;
void onBackground() override;
void onButtonShortPress() override;
void onButtonLongPress() override;
void onExitShort() override;
void onExitLong() override;
void onNavUp() override;
void onNavDown() override;
void onNavLeft() override;
void onNavRight() override;
static uint16_t getKeyboardHeight(); // used to set the keyboard tile height
private:
void drawKeyLabel(uint16_t left, uint16_t top, uint16_t width, char key, Color color);
static const uint8_t KBD_COLS = 11;
static const uint8_t KBD_ROWS = 4;
const char keys[KBD_COLS * KBD_ROWS] = {
'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '\b', // row 0
'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '\n', // row 1
'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', '!', ' ', // row 2
'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '?', '\x1b' // row 3
};
// This array represents the widths of each key in points
// 16 pt = line height of the text
const uint16_t keyWidths[KBD_COLS * KBD_ROWS] = {
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 0
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 1
16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24, // row 2
16, 16, 16, 16, 16, 16, 16, 10, 10, 12, 40 // row 3
};
uint16_t rowWidths[KBD_ROWS];
uint8_t selectedKey = 0; // selected key index
uint8_t prevSelectedKey = 0;
};
} // namespace NicheGraphics::InkHUD
#endif
@@ -30,7 +30,7 @@ InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet")
// This is then drawn with a FULL refresh by Renderer::begin // This is then drawn with a FULL refresh by Renderer::begin
} }
void InkHUD::LogoApplet::onRender(bool full) void InkHUD::LogoApplet::onRender()
{ {
// Size of the region which the logo should "scale to fit" // Size of the region which the logo should "scale to fit"
uint16_t logoWLimit = X(0.8); uint16_t logoWLimit = X(0.8);
@@ -120,7 +120,7 @@ void InkHUD::LogoApplet::onBackground()
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
// Begin displaying the screen which is shown at shutdown // Begin displaying the screen which is shown at shutdown
@@ -138,10 +138,10 @@ void InkHUD::LogoApplet::onShutdown()
// Intention is to restore display health. // Intention is to restore display health.
inverted = true; inverted = true;
inkhud->forceUpdate(Drivers::EInk::FULL, true, false); inkhud->forceUpdate(Drivers::EInk::FULL, false);
delay(1000); // Cooldown. Back to back updates aren't great for health. delay(1000); // Cooldown. Back to back updates aren't great for health.
inverted = false; inverted = false;
inkhud->forceUpdate(Drivers::EInk::FULL, true, false); inkhud->forceUpdate(Drivers::EInk::FULL, false);
delay(1000); // Cooldown delay(1000); // Cooldown
// Prepare for the powered-off screen now // Prepare for the powered-off screen now
@@ -155,18 +155,6 @@ void InkHUD::LogoApplet::onShutdown()
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete // This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete
} }
void InkHUD::LogoApplet::onApplyingChanges()
{
bringToForeground();
textLeft = "";
textRight = "";
textTitle = "Applying changes";
fontTitle = fontSmall;
inkhud->forceUpdate(Drivers::EInk::FAST, false);
}
void InkHUD::LogoApplet::onReboot() void InkHUD::LogoApplet::onReboot()
{ {
bringToForeground(); bringToForeground();
@@ -176,7 +164,7 @@ void InkHUD::LogoApplet::onReboot()
textTitle = "Rebooting..."; textTitle = "Rebooting...";
fontTitle = fontSmall; fontTitle = fontSmall;
inkhud->forceUpdate(Drivers::EInk::FULL, true, false); inkhud->forceUpdate(Drivers::EInk::FULL, false);
// Perform the update right now, waiting here until complete // Perform the update right now, waiting here until complete
} }
@@ -21,12 +21,11 @@ class LogoApplet : public SystemApplet, public concurrency::OSThread
{ {
public: public:
LogoApplet(); LogoApplet();
void onRender(bool full) override; void onRender() override;
void onForeground() override; void onForeground() override;
void onBackground() override; void onBackground() override;
void onShutdown() override; void onShutdown() override;
void onReboot() override; void onReboot() override;
void onApplyingChanges();
protected: protected:
int32_t runOnce() override; int32_t runOnce() override;
@@ -19,7 +19,6 @@ namespace NicheGraphics::InkHUD
enum MenuAction { enum MenuAction {
NO_ACTION, NO_ACTION,
SEND_PING, SEND_PING,
FREE_TEXT,
STORE_CANNEDMESSAGE_SELECTION, STORE_CANNEDMESSAGE_SELECTION,
SEND_CANNEDMESSAGE, SEND_CANNEDMESSAGE,
SHUTDOWN, SHUTDOWN,
@@ -37,84 +36,6 @@ enum MenuAction {
TOGGLE_NOTIFICATIONS, TOGGLE_NOTIFICATIONS,
TOGGLE_INVERT_COLOR, TOGGLE_INVERT_COLOR,
TOGGLE_12H_CLOCK, TOGGLE_12H_CLOCK,
// Regions
SET_REGION_US,
SET_REGION_EU_868,
SET_REGION_EU_433,
SET_REGION_CN,
SET_REGION_JP,
SET_REGION_ANZ,
SET_REGION_KR,
SET_REGION_TW,
SET_REGION_RU,
SET_REGION_IN,
SET_REGION_NZ_865,
SET_REGION_TH,
SET_REGION_LORA_24,
SET_REGION_UA_433,
SET_REGION_UA_868,
SET_REGION_MY_433,
SET_REGION_MY_919,
SET_REGION_SG_923,
SET_REGION_PH_433,
SET_REGION_PH_868,
SET_REGION_PH_915,
SET_REGION_ANZ_433,
SET_REGION_KZ_433,
SET_REGION_KZ_863,
SET_REGION_NP_865,
SET_REGION_BR_902,
// Device Roles
SET_ROLE_CLIENT,
SET_ROLE_CLIENT_MUTE,
SET_ROLE_ROUTER,
SET_ROLE_REPEATER,
// Presets
SET_PRESET_LONG_SLOW,
SET_PRESET_LONG_MODERATE,
SET_PRESET_LONG_FAST,
SET_PRESET_MEDIUM_SLOW,
SET_PRESET_MEDIUM_FAST,
SET_PRESET_SHORT_SLOW,
SET_PRESET_SHORT_FAST,
SET_PRESET_SHORT_TURBO,
// Timezones
SET_TZ_US_HAWAII,
SET_TZ_US_ALASKA,
SET_TZ_US_PACIFIC,
SET_TZ_US_ARIZONA,
SET_TZ_US_MOUNTAIN,
SET_TZ_US_CENTRAL,
SET_TZ_US_EASTERN,
SET_TZ_BR_BRAZILIA,
SET_TZ_UTC,
SET_TZ_EU_WESTERN,
SET_TZ_EU_CENTRAL,
SET_TZ_EU_EASTERN,
SET_TZ_ASIA_KOLKATA,
SET_TZ_ASIA_HONG_KONG,
SET_TZ_AU_AWST,
SET_TZ_AU_ACST,
SET_TZ_AU_AEST,
SET_TZ_PACIFIC_NZ,
// Power
TOGGLE_POWER_SAVE,
CALIBRATE_ADC,
// Bluetooth
TOGGLE_BLUETOOTH,
TOGGLE_BLUETOOTH_PAIR_MODE,
// Channel
TOGGLE_CHANNEL_UPLINK,
TOGGLE_CHANNEL_DOWNLINK,
TOGGLE_CHANNEL_POSITION,
SET_CHANNEL_PRECISION,
// Display
TOGGLE_DISPLAY_UNITS,
// Network
TOGGLE_WIFI,
// Administration
RESET_NODEDB_ALL,
RESET_NODEDB_KEEP_FAVORITES,
}; };
} // namespace NicheGraphics::InkHUD } // namespace NicheGraphics::InkHUD
File diff suppressed because it is too large Load Diff
@@ -32,13 +32,9 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
void onNavDown() override; void onNavDown() override;
void onNavLeft() override; void onNavLeft() override;
void onNavRight() override; void onNavRight() override;
void onFreeText(char c) override; void onRender() override;
void onFreeTextDone() override;
void onFreeTextCancel() override;
void onRender(bool full) override;
void show(Tile *t); // Open the menu, onto a user tile void show(Tile *t); // Open the menu, onto a user tile
void setStartPage(MenuPage page);
protected: protected:
Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton Drivers::LatchingBacklight *backlight = nullptr; // Convenient access to the backlight singleton
@@ -54,32 +50,20 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
void populateAutoshowPage(); // Dynamically create MenuItems for selecting which applets can autoshow void populateAutoshowPage(); // Dynamically create MenuItems for selecting which applets can autoshow
void populateRecentsPage(); // Create menu items: a choice of values for settings.recentlyActiveSeconds void populateRecentsPage(); // Create menu items: a choice of values for settings.recentlyActiveSeconds
void drawInputField(uint16_t left, uint16_t top, uint16_t width, uint16_t height,
std::string text); // Draw input field for free text
uint16_t getSystemInfoPanelHeight(); uint16_t getSystemInfoPanelHeight();
void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width, void drawSystemInfoPanel(int16_t left, int16_t top, uint16_t width,
uint16_t *height = nullptr); // Info panel at top of root menu uint16_t *height = nullptr); // Info panel at top of root menu
void sendText(NodeNum dest, ChannelIndex channel, const char *message); // Send a text message to mesh void sendText(NodeNum dest, ChannelIndex channel, const char *message); // Send a text message to mesh
void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data
MenuPage startPageOverride = MenuPage::ROOT;
MenuPage currentPage = MenuPage::ROOT; MenuPage currentPage = MenuPage::ROOT;
MenuPage previousPage = MenuPage::EXIT; MenuPage previousPage = MenuPage::EXIT;
uint8_t cursor = 0; // Which menu item is currently highlighted uint8_t cursor = 0; // Which menu item is currently highlighted
bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection) bool cursorShown = false; // Is *any* item highlighted? (Root menu: no initial selection)
bool freeTextMode = false;
uint16_t systemInfoPanelHeight = 0; // Need to know before we render uint16_t systemInfoPanelHeight = 0; // Need to know before we render
uint16_t menuTextLimit = 200;
std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage std::vector<MenuItem> items; // MenuItems for the current page. Filled by ShowPage
std::vector<std::string> nodeConfigLabels; // Persistent labels for Node Config pages
uint8_t selectedChannelIndex = 0; // Currently selected LoRa channel (Node Config → Radio → Channel)
bool channelPositionEnabled = false;
bool gpsEnabled = false;
// Recents menu checkbox state (derived from settings.recentlyActiveSeconds)
static constexpr uint8_t RECENTS_COUNT = 6;
bool recentsSelected[RECENTS_COUNT] = {};
// Data for selecting and sending canned messages via the menu // Data for selecting and sending canned messages via the menu
// Placed into a sub-class for organization only // Placed into a sub-class for organization only
@@ -110,8 +94,6 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
// Cleared onBackground (when MenuApplet closes) // Cleared onBackground (when MenuApplet closes)
std::vector<MessageItem> messageItems; std::vector<MessageItem> messageItems;
std::vector<RecipientItem> recipientItems; std::vector<RecipientItem> recipientItems;
MessageItem freeTextItem;
} cm; } cm;
Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu Applet *borrowedTileOwner = nullptr; // Which applet we have temporarily replaced while displaying menu
@@ -30,7 +30,6 @@ class MenuItem
MenuAction action = NO_ACTION; MenuAction action = NO_ACTION;
MenuPage nextPage = EXIT; MenuPage nextPage = EXIT;
bool *checkState = nullptr; bool *checkState = nullptr;
bool isHeader = false; // Non-selectable section label
// Various constructors, depending on the intended function of the item // Various constructors, depending on the intended function of the item
@@ -41,12 +40,6 @@ class MenuItem
: label(label), action(action), nextPage(nextPage), checkState(checkState) : label(label), action(action), nextPage(nextPage), checkState(checkState)
{ {
} }
static MenuItem Header(const char *label)
{
MenuItem item(label, NO_ACTION, EXIT);
item.isHeader = true;
return item;
}
}; };
} // namespace NicheGraphics::InkHUD } // namespace NicheGraphics::InkHUD
@@ -20,27 +20,10 @@ enum MenuPage : uint8_t {
SEND, SEND,
CANNEDMESSAGE_RECIPIENT, // Select destination for a canned message CANNEDMESSAGE_RECIPIENT, // Select destination for a canned message
OPTIONS, OPTIONS,
NODE_CONFIG,
NODE_CONFIG_LORA,
NODE_CONFIG_CHANNELS, // List of channels
NODE_CONFIG_CHANNEL_DETAIL, // Per-channel options
NODE_CONFIG_CHANNEL_PRECISION,
NODE_CONFIG_PRESET,
NODE_CONFIG_DEVICE,
NODE_CONFIG_DEVICE_ROLE,
NODE_CONFIG_POWER,
NODE_CONFIG_POWER_ADC_CAL,
NODE_CONFIG_NETWORK,
NODE_CONFIG_DISPLAY,
NODE_CONFIG_BLUETOOTH,
NODE_CONFIG_POSITION,
NODE_CONFIG_ADMIN_RESET,
TIMEZONE,
APPLETS, APPLETS,
AUTOSHOW, AUTOSHOW,
RECENTS, // Select length of "recentlyActiveSeconds" RECENTS, // Select length of "recentlyActiveSeconds"
REGION, EXIT, // Dismiss the menu applet
EXIT, // Dismiss the menu applet
}; };
} // namespace NicheGraphics::InkHUD } // namespace NicheGraphics::InkHUD
@@ -65,7 +65,7 @@ int InkHUD::NotificationApplet::onReceiveTextMessage(const meshtastic_MeshPacket
return 0; return 0;
} }
void InkHUD::NotificationApplet::onRender(bool full) void InkHUD::NotificationApplet::onRender()
{ {
// Clear the region beneath the tile // Clear the region beneath the tile
// Most applets are drawing onto an empty frame buffer and don't need to do this // Most applets are drawing onto an empty frame buffer and don't need to do this
@@ -139,47 +139,54 @@ void InkHUD::NotificationApplet::onForeground()
void InkHUD::NotificationApplet::onBackground() void InkHUD::NotificationApplet::onBackground()
{ {
handleInput = false; handleInput = false;
inkhud->forceUpdate(EInk::UpdateTypes::FULL, true);
} }
void InkHUD::NotificationApplet::onButtonShortPress() void InkHUD::NotificationApplet::onButtonShortPress()
{ {
dismiss(); dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
void InkHUD::NotificationApplet::onButtonLongPress() void InkHUD::NotificationApplet::onButtonLongPress()
{ {
dismiss(); dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
void InkHUD::NotificationApplet::onExitShort() void InkHUD::NotificationApplet::onExitShort()
{ {
dismiss(); dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
void InkHUD::NotificationApplet::onExitLong() void InkHUD::NotificationApplet::onExitLong()
{ {
dismiss(); dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
void InkHUD::NotificationApplet::onNavUp() void InkHUD::NotificationApplet::onNavUp()
{ {
dismiss(); dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
void InkHUD::NotificationApplet::onNavDown() void InkHUD::NotificationApplet::onNavDown()
{ {
dismiss(); dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
void InkHUD::NotificationApplet::onNavLeft() void InkHUD::NotificationApplet::onNavLeft()
{ {
dismiss(); dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
void InkHUD::NotificationApplet::onNavRight() void InkHUD::NotificationApplet::onNavRight()
{ {
dismiss(); dismiss();
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
// Ask the WindowManager to check whether any displayed applets are already displaying the info from this notification // Ask the WindowManager to check whether any displayed applets are already displaying the info from this notification
@@ -26,7 +26,7 @@ class NotificationApplet : public SystemApplet
public: public:
NotificationApplet(); NotificationApplet();
void onRender(bool full) override; void onRender() override;
void onForeground() override; void onForeground() override;
void onBackground() override; void onBackground() override;
void onButtonShortPress() override; void onButtonShortPress() override;
@@ -9,7 +9,7 @@ InkHUD::PairingApplet::PairingApplet()
bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus); bluetoothStatusObserver.observe(&bluetoothStatus->onNewStatus);
} }
void InkHUD::PairingApplet::onRender(bool full) void InkHUD::PairingApplet::onRender()
{ {
// Header // Header
setFont(fontMedium); setFont(fontMedium);
@@ -45,7 +45,7 @@ void InkHUD::PairingApplet::onBackground()
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *status) int InkHUD::PairingApplet::onBluetoothStatusUpdate(const meshtastic::Status *status)
@@ -22,7 +22,7 @@ class PairingApplet : public SystemApplet
public: public:
PairingApplet(); PairingApplet();
void onRender(bool full) override; void onRender() override;
void onForeground() override; void onForeground() override;
void onBackground() override; void onBackground() override;
@@ -4,7 +4,7 @@
using namespace NicheGraphics; using namespace NicheGraphics;
void InkHUD::PlaceholderApplet::onRender(bool full) void InkHUD::PlaceholderApplet::onRender()
{ {
// This placeholder applet fills its area with sparse diagonal lines // This placeholder applet fills its area with sparse diagonal lines
hatchRegion(0, 0, width(), height(), 8, BLACK); hatchRegion(0, 0, width(), height(), 8, BLACK);
@@ -17,7 +17,7 @@ namespace NicheGraphics::InkHUD
class PlaceholderApplet : public SystemApplet class PlaceholderApplet : public SystemApplet
{ {
public: public:
void onRender(bool full) override; void onRender() override;
// Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet. // Note: onForeground, onBackground, and wantsToRender are not meaningful for this applet.
// The window manager decides when and where it should be rendered // The window manager decides when and where it should be rendered
@@ -10,42 +10,39 @@ using namespace NicheGraphics;
InkHUD::TipsApplet::TipsApplet() InkHUD::TipsApplet::TipsApplet()
{ {
bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET); // Decide which tips (if any) should be shown to user after the boot screen
bool showTutorialTips = (settings->tips.firstBoot || needsRegion);
// Welcome screen // Welcome screen
if (showTutorialTips) if (settings->tips.firstBoot)
tipQueue.push_back(Tip::WELCOME); tipQueue.push_back(Tip::WELCOME);
// Finish setup // Antenna, region, timezone
if (needsRegion) // Shown at boot if region not yet set
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET)
tipQueue.push_back(Tip::FINISH_SETUP); tipQueue.push_back(Tip::FINISH_SETUP);
// Using the UI
if (showTutorialTips) {
tipQueue.push_back(Tip::CUSTOMIZATION);
tipQueue.push_back(Tip::BUTTONS);
}
// Shutdown info // Shutdown info
// Shown until user performs one valid shutdown // Shown until user performs one valid shutdown
if (!settings->tips.safeShutdownSeen) if (!settings->tips.safeShutdownSeen)
tipQueue.push_back(Tip::SAFE_SHUTDOWN); tipQueue.push_back(Tip::SAFE_SHUTDOWN);
// Using the UI
if (settings->tips.firstBoot) {
tipQueue.push_back(Tip::CUSTOMIZATION);
tipQueue.push_back(Tip::BUTTONS);
}
// Catch an incorrect attempt at rotating display // Catch an incorrect attempt at rotating display
if (config.display.flip_screen) if (config.display.flip_screen)
tipQueue.push_back(Tip::ROTATION); tipQueue.push_back(Tip::ROTATION);
// Region picker // Applet is foreground immediately at boot, but is obscured by LogoApplet, which is also foreground
if (needsRegion) // LogoApplet can be considered to have a higher Z-index, because it is placed before TipsApplet in the systemApplets vector
tipQueue.push_back(Tip::PICK_REGION);
if (!tipQueue.empty()) if (!tipQueue.empty())
bringToForeground(); bringToForeground();
} }
void InkHUD::TipsApplet::onRender(bool full) void InkHUD::TipsApplet::onRender()
{ {
switch (tipQueue.front()) { switch (tipQueue.front()) {
case Tip::WELCOME: case Tip::WELCOME:
@@ -54,109 +51,81 @@ void InkHUD::TipsApplet::onRender(bool full)
case Tip::FINISH_SETUP: { case Tip::FINISH_SETUP: {
setFont(fontMedium); setFont(fontMedium);
const char *title = "Tip: Finish Setup"; printAt(0, 0, "Tip: Finish Setup");
uint16_t h = getWrappedTextHeight(0, width(), title);
printWrapped(0, 0, width(), title);
setFont(fontSmall); setFont(fontSmall);
int16_t cursorY = h + fontSmall.lineHeight(); int16_t cursorY = fontMedium.lineHeight() * 1.5;
printAt(0, cursorY, "- connect antenna");
auto drawBullet = [&](const char *text) { cursorY += fontSmall.lineHeight() * 1.2;
uint16_t bh = getWrappedTextHeight(0, width(), text); printAt(0, cursorY, "- connect a client app");
printWrapped(0, cursorY, width(), text);
cursorY += bh + (fontSmall.lineHeight() / 3);
};
drawBullet("- connect antenna"); // Only if region not set
drawBullet("- connect a client app"); if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- set region");
}
if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) // Only if tz not set
drawBullet("- set region"); if (!(*config.device.tzdef && config.device.tzdef[0] != 0)) {
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- set timezone");
}
if (!(*config.device.tzdef && config.device.tzdef[0] != 0)) cursorY += fontSmall.lineHeight() * 1.5;
drawBullet("- set timezone"); printAt(0, cursorY, "More info at meshtastic.org");
cursorY += fontSmall.lineHeight() / 2;
drawBullet("More info at meshtastic.org");
setFont(fontSmall);
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
} break; } break;
case Tip::PICK_REGION: {
setFont(fontMedium);
printAt(0, 0, "Set Region");
setFont(fontSmall);
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), "Please select your LoRa region to complete setup.");
printAt(0, Y(1.0), "Press button to choose", LEFT, BOTTOM);
} break;
case Tip::SAFE_SHUTDOWN: { case Tip::SAFE_SHUTDOWN: {
setFont(fontMedium); setFont(fontMedium);
printAt(0, 0, "Tip: Shutdown");
const char *title = "Tip: Shutdown";
uint16_t h = getWrappedTextHeight(0, width(), title);
printWrapped(0, 0, width(), title);
setFont(fontSmall); setFont(fontSmall);
int16_t cursorY = h + fontSmall.lineHeight(); std::string shutdown;
shutdown += "Before removing power, please shut down from InkHUD menu, or a client app. \n";
const char *body = "Before removing power, please shut down from InkHUD menu, or a client app.\n\n" shutdown += "\n";
"This ensures data is saved."; shutdown += "This ensures data is saved.";
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), shutdown);
uint16_t bodyH = getWrappedTextHeight(0, width(), body);
printWrapped(0, cursorY, width(), body);
cursorY += bodyH + (fontSmall.lineHeight() / 2);
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
} break; } break;
case Tip::CUSTOMIZATION: { case Tip::CUSTOMIZATION: {
setFont(fontMedium); setFont(fontMedium);
printAt(0, 0, "Tip: Customization");
const char *title = "Tip: Customization";
uint16_t h = getWrappedTextHeight(0, width(), title);
printWrapped(0, 0, width(), title);
setFont(fontSmall); setFont(fontSmall);
int16_t cursorY = h + fontSmall.lineHeight(); printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
"Configure & control display with the InkHUD menu. Optional features, layout, rotation, and more.");
const char *body = "Configure & control display with the InkHUD menu. "
"Optional features, layout, rotation, and more.";
uint16_t bodyH = getWrappedTextHeight(0, width(), body);
printWrapped(0, cursorY, width(), body);
cursorY += bodyH + (fontSmall.lineHeight() / 2);
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
} break; } break;
case Tip::BUTTONS: { case Tip::BUTTONS: {
setFont(fontMedium); setFont(fontMedium);
printAt(0, 0, "Tip: Buttons");
const char *title = "Tip: Buttons";
uint16_t h = getWrappedTextHeight(0, width(), title);
printWrapped(0, 0, width(), title);
setFont(fontSmall); setFont(fontSmall);
int16_t cursorY = h + fontSmall.lineHeight(); int16_t cursorY = fontMedium.lineHeight() * 1.5;
auto drawBullet = [&](const char *text) {
uint16_t bh = getWrappedTextHeight(0, width(), text);
printWrapped(0, cursorY, width(), text);
cursorY += bh + (fontSmall.lineHeight() / 3);
};
if (!settings->joystick.enabled) { if (!settings->joystick.enabled) {
drawBullet("User Button"); printAt(0, cursorY, "User Button");
drawBullet("- short press: next"); cursorY += fontSmall.lineHeight() * 1.2;
drawBullet("- long press: select or open menu"); printAt(0, cursorY, "- short press: next");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- long press: select / open menu");
} else { } else {
drawBullet("Joystick"); printAt(0, cursorY, "Joystick");
drawBullet("- press: open menu or select"); cursorY += fontSmall.lineHeight() * 1.2;
drawBullet("Exit Button"); printAt(0, cursorY, "- open menu / select");
drawBullet("- press: switch tile or close menu"); cursorY += fontSmall.lineHeight() * 1.5;
printAt(0, cursorY, "Exit Button");
cursorY += fontSmall.lineHeight() * 1.2;
printAt(0, cursorY, "- switch tile / close menu");
} }
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM); printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
@@ -164,21 +133,12 @@ void InkHUD::TipsApplet::onRender(bool full)
case Tip::ROTATION: { case Tip::ROTATION: {
setFont(fontMedium); setFont(fontMedium);
printAt(0, 0, "Tip: Rotation");
const char *title = "Tip: Rotation";
uint16_t h = getWrappedTextHeight(0, width(), title);
printWrapped(0, 0, width(), title);
setFont(fontSmall); setFont(fontSmall);
if (!settings->joystick.enabled) { if (!settings->joystick.enabled) {
int16_t cursorY = h + fontSmall.lineHeight(); printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
"To rotate the display, use the InkHUD menu. Long-press the user button > Options > Rotate.");
const char *body = "To rotate the display, use the InkHUD menu. "
"Long-press the user button > Options > Rotate.";
uint16_t bh = getWrappedTextHeight(0, width(), body);
printWrapped(0, cursorY, width(), body);
cursorY += bh + (fontSmall.lineHeight() / 2);
} else { } else {
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
"To rotate the display, use the InkHUD menu. Press the user button > Options > Rotate."); "To rotate the display, use the InkHUD menu. Press the user button > Options > Rotate.");
@@ -199,15 +159,12 @@ void InkHUD::TipsApplet::renderWelcome()
{ {
uint16_t padW = X(0.05); uint16_t padW = X(0.05);
// Detect portrait orientation
bool portrait = height() > width();
// Block 1 - logo & title // Block 1 - logo & title
// ======================== // ========================
// Logo size // Logo size
uint16_t logoWLimit = portrait ? X(0.5) : X(0.3); uint16_t logoWLimit = X(0.3);
uint16_t logoHLimit = portrait ? Y(0.25) : Y(0.3); uint16_t logoHLimit = Y(0.3);
uint16_t logoW = getLogoWidth(logoWLimit, logoHLimit); uint16_t logoW = getLogoWidth(logoWLimit, logoHLimit);
uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit); uint16_t logoH = getLogoHeight(logoWLimit, logoHLimit);
@@ -220,7 +177,7 @@ void InkHUD::TipsApplet::renderWelcome()
// Center the block // Center the block
// Desired effect: equal margin from display edge for logo left and title right // Desired effect: equal margin from display edge for logo left and title right
int16_t block1Y = portrait ? Y(0.2) : Y(0.3); int16_t block1Y = Y(0.3);
int16_t block1CX = X(0.5) + (logoW / 2) - (titleW / 2); int16_t block1CX = X(0.5) + (logoW / 2) - (titleW / 2);
int16_t logoCX = block1CX - (logoW / 2) - (padW / 2); int16_t logoCX = block1CX - (logoW / 2) - (padW / 2);
int16_t titleCX = block1CX + (titleW / 2) + (padW / 2); int16_t titleCX = block1CX + (titleW / 2) + (padW / 2);
@@ -235,7 +192,7 @@ void InkHUD::TipsApplet::renderWelcome()
std::string subtitle = "InkHUD"; std::string subtitle = "InkHUD";
if (width() >= 200) if (width() >= 200)
subtitle += " - A Heads-Up Display"; // Future proofing: narrower for tiny display subtitle += " - A Heads-Up Display"; // Future proofing: narrower for tiny display
printAt(X(0.5), portrait ? Y(0.45) : Y(0.6), subtitle, CENTER, MIDDLE); printAt(X(0.5), Y(0.6), subtitle, CENTER, MIDDLE);
// Block 3 - press to continue // Block 3 - press to continue
// ============================ // ============================
@@ -261,42 +218,32 @@ void InkHUD::TipsApplet::onBackground()
// Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background // Need to force an update, as a polite request wouldn't be honored, seeing how we are now in the background
// Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case // Usually, onBackground is followed by another applet's onForeground (which requests update), but not in this case
inkhud->forceUpdate(EInk::UpdateTypes::FULL, true); inkhud->forceUpdate(EInk::UpdateTypes::FULL);
} }
// While our SystemApplet::handleInput flag is true // While our SystemApplet::handleInput flag is true
void InkHUD::TipsApplet::onButtonShortPress() void InkHUD::TipsApplet::onButtonShortPress()
{ {
bool needsRegion = (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET);
// If we're prompting the user to pick a region, hand off to the menu
if (!tipQueue.empty() && tipQueue.front() == Tip::PICK_REGION) {
tipQueue.pop_front();
// Signal InkHUD to open the menu on Region page
inkhud->forceRegionMenu = true;
// Close tips and open menu
sendToBackground();
inkhud->openMenu();
return;
}
// Consume current tip
tipQueue.pop_front(); tipQueue.pop_front();
// All tips done // All tips done
if (tipQueue.empty()) { if (tipQueue.empty()) {
// Record that user has now seen the "tutorial" set of tips // Record that user has now seen the "tutorial" set of tips
// Don't show them on subsequent boots // Don't show them on subsequent boots
if (settings->tips.firstBoot && !needsRegion) { if (settings->tips.firstBoot) {
settings->tips.firstBoot = false; settings->tips.firstBoot = false;
inkhud->persistence->saveSettings(); inkhud->persistence->saveSettings();
} }
// Close applet // Close applet, and full refresh to clean the screen
// Need to force update, because our request would be ignored otherwise, as we are now background
sendToBackground(); sendToBackground();
} else { inkhud->forceUpdate(EInk::UpdateTypes::FULL);
requestUpdate();
} }
// More tips left
else
requestUpdate();
} }
// Functions the same as the user button in this instance // Functions the same as the user button in this instance
@@ -305,4 +252,4 @@ void InkHUD::TipsApplet::onExitShort()
onButtonShortPress(); onButtonShortPress();
} }
#endif #endif
@@ -23,7 +23,6 @@ class TipsApplet : public SystemApplet
enum class Tip { enum class Tip {
WELCOME, WELCOME,
FINISH_SETUP, FINISH_SETUP,
PICK_REGION,
SAFE_SHUTDOWN, SAFE_SHUTDOWN,
CUSTOMIZATION, CUSTOMIZATION,
BUTTONS, BUTTONS,
@@ -33,7 +32,7 @@ class TipsApplet : public SystemApplet
public: public:
TipsApplet(); TipsApplet();
void onRender(bool full) override; void onRender() override;
void onForeground() override; void onForeground() override;
void onBackground() override; void onBackground() override;
void onButtonShortPress() override; void onButtonShortPress() override;
@@ -34,7 +34,7 @@ int InkHUD::AllMessageApplet::onReceiveTextMessage(const meshtastic_MeshPacket *
return 0; return 0;
} }
void InkHUD::AllMessageApplet::onRender(bool full) void InkHUD::AllMessageApplet::onRender()
{ {
// Find newest message, regardless of whether DM or broadcast // Find newest message, regardless of whether DM or broadcast
MessageStore::Message *message; MessageStore::Message *message;
@@ -30,7 +30,7 @@ class Applet;
class AllMessageApplet : public Applet class AllMessageApplet : public Applet
{ {
public: public:
void onRender(bool full) override; void onRender() override;
void onActivate() override; void onActivate() override;
void onDeactivate() override; void onDeactivate() override;
@@ -37,7 +37,7 @@ int InkHUD::DMApplet::onReceiveTextMessage(const meshtastic_MeshPacket *p)
return 0; return 0;
} }
void InkHUD::DMApplet::onRender(bool full) void InkHUD::DMApplet::onRender()
{ {
// Abort if no text message // Abort if no text message
if (!latestMessage->dm.sender) { if (!latestMessage->dm.sender) {
@@ -30,7 +30,7 @@ class Applet;
class DMApplet : public Applet class DMApplet : public Applet
{ {
public: public:
void onRender(bool full) override; void onRender() override;
void onActivate() override; void onActivate() override;
void onDeactivate() override; void onDeactivate() override;
@@ -5,10 +5,10 @@
using namespace NicheGraphics; using namespace NicheGraphics;
void InkHUD::PositionsApplet::onRender(bool full) void InkHUD::PositionsApplet::onRender()
{ {
// Draw the usual map applet first // Draw the usual map applet first
MapApplet::onRender(full); MapApplet::onRender();
// Draw our latest "node of interest" as a special marker // Draw our latest "node of interest" as a special marker
// ------------------------------------------------------- // -------------------------------------------------------
@@ -24,7 +24,7 @@ class PositionsApplet : public MapApplet, public SinglePortModule
{ {
public: public:
PositionsApplet() : SinglePortModule("PositionsApplet", meshtastic_PortNum_POSITION_APP) {} PositionsApplet() : SinglePortModule("PositionsApplet", meshtastic_PortNum_POSITION_APP) {}
void onRender(bool full) override; void onRender() override;
protected: protected:
ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override; ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
@@ -22,7 +22,7 @@ InkHUD::ThreadedMessageApplet::ThreadedMessageApplet(uint8_t channelIndex)
store = new MessageStore("ch" + to_string(channelIndex)); store = new MessageStore("ch" + to_string(channelIndex));
} }
void InkHUD::ThreadedMessageApplet::onRender(bool full) void InkHUD::ThreadedMessageApplet::onRender()
{ {
// ============= // =============
// Draw a header // Draw a header
@@ -36,7 +36,7 @@ class ThreadedMessageApplet : public Applet, public SinglePortModule
explicit ThreadedMessageApplet(uint8_t channelIndex); explicit ThreadedMessageApplet(uint8_t channelIndex);
ThreadedMessageApplet() = delete; ThreadedMessageApplet() = delete;
void onRender(bool full) override; void onRender() override;
void onActivate() override; void onActivate() override;
void onDeactivate() override; void onDeactivate() override;
+1 -43
View File
@@ -238,39 +238,6 @@ void InkHUD::Events::onNavRight()
} }
} }
void InkHUD::Events::onFreeText(char c)
{
// Trigger the first system applet that wants to handle the new character
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleFreeText) {
sa->onFreeText(c);
break;
}
}
}
void InkHUD::Events::onFreeTextDone()
{
// Trigger the first system applet that wants to handle it
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleFreeText) {
sa->onFreeTextDone();
break;
}
}
}
void InkHUD::Events::onFreeTextCancel()
{
// Trigger the first system applet that wants to handle it
for (SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleFreeText) {
sa->onFreeTextCancel();
break;
}
}
}
// Callback for deepSleepObserver // Callback for deepSleepObserver
// Returns 0 to signal that we agree to sleep now // Returns 0 to signal that we agree to sleep now
int InkHUD::Events::beforeDeepSleep(void *unused) int InkHUD::Events::beforeDeepSleep(void *unused)
@@ -299,7 +266,7 @@ int InkHUD::Events::beforeDeepSleep(void *unused)
// then prepared a final powered-off screen for us, which shows device shortname. // then prepared a final powered-off screen for us, which shows device shortname.
// We're updating to show that one now. // We're updating to show that one now.
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL, true, false); inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FULL, false);
delay(1000); // Cooldown, before potentially yanking display power delay(1000); // Cooldown, before potentially yanking display power
// InkHUD shutdown complete // InkHUD shutdown complete
@@ -309,15 +276,6 @@ int InkHUD::Events::beforeDeepSleep(void *unused)
return 0; // We agree: deep sleep now return 0; // We agree: deep sleep now
} }
// Display an intermediate screen while configuration changes are applied
void InkHUD::Events::applyingChanges()
{
// Bring the logo applet forward with a temporary message
for (SystemApplet *sa : inkhud->systemApplets) {
sa->onApplyingChanges();
}
}
// Callback for rebootObserver // Callback for rebootObserver
// Same as shutdown, without drawing the logoApplet // Same as shutdown, without drawing the logoApplet
// Makes sure we don't lose message history / InkHUD config // Makes sure we don't lose message history / InkHUD config
+6 -12
View File
@@ -29,18 +29,12 @@ class Events
void onButtonShort(); // User button: short press void onButtonShort(); // User button: short press
void onButtonLong(); // User button: long press void onButtonLong(); // User button: long press
void applyingChanges(); void onExitShort(); // Exit button: short press
void onExitShort(); // Exit button: short press void onExitLong(); // Exit button: long press
void onExitLong(); // Exit button: long press void onNavUp(); // Navigate up
void onNavUp(); // Navigate up void onNavDown(); // Navigate down
void onNavDown(); // Navigate down void onNavLeft(); // Navigate left
void onNavLeft(); // Navigate left void onNavRight(); // Navigate right
void onNavRight(); // Navigate right
// Free text typing events
void onFreeText(char c); // New freetext character input
void onFreeTextDone();
void onFreeTextCancel();
int beforeDeepSleep(void *unused); // Prepare for shutdown int beforeDeepSleep(void *unused); // Prepare for shutdown
int beforeReboot(void *unused); // Prepare for reboot int beforeReboot(void *unused); // Prepare for reboot
+2 -41
View File
@@ -53,13 +53,6 @@ void InkHUD::InkHUD::addApplet(const char *name, Applet *a, bool defaultActive,
windowManager->addApplet(name, a, defaultActive, defaultAutoshow, onTile); windowManager->addApplet(name, a, defaultActive, defaultAutoshow, onTile);
} }
void InkHUD::InkHUD::notifyApplyingChanges()
{
if (events) {
events->applyingChanges();
}
}
// Start InkHUD! // Start InkHUD!
// Call this only after you have configured InkHUD // Call this only after you have configured InkHUD
void InkHUD::InkHUD::begin() void InkHUD::InkHUD::begin()
@@ -175,25 +168,6 @@ void InkHUD::InkHUD::navRight()
} }
} }
// Call this for keyboard input
// The Keyboard Applet also calls this
void InkHUD::InkHUD::freeText(char c)
{
events->onFreeText(c);
}
// Call this to complete a freetext input
void InkHUD::InkHUD::freeTextDone()
{
events->onFreeTextDone();
}
// Call this to cancel a freetext input
void InkHUD::InkHUD::freeTextCancel()
{
events->onFreeTextCancel();
}
// Cycle the next user applet to the foreground // Cycle the next user applet to the foreground
// Only activated applets are cycled // Only activated applets are cycled
// If user has a multi-applet layout, the applets will cycle on the "focused tile" // If user has a multi-applet layout, the applets will cycle on the "focused tile"
@@ -223,18 +197,6 @@ void InkHUD::InkHUD::openAlignStick()
windowManager->openAlignStick(); windowManager->openAlignStick();
} }
// Open the on-screen keyboard
void InkHUD::InkHUD::openKeyboard()
{
windowManager->openKeyboard();
}
// Close the on-screen keyboard
void InkHUD::InkHUD::closeKeyboard()
{
windowManager->closeKeyboard();
}
// In layouts where multiple applets are shown at once, change which tile is focused // In layouts where multiple applets are shown at once, change which tile is focused
// The focused tile in the one which cycles applets on button short press, and displays menu on long press // The focused tile in the one which cycles applets on button short press, and displays menu on long press
void InkHUD::InkHUD::nextTile() void InkHUD::InkHUD::nextTile()
@@ -283,11 +245,10 @@ void InkHUD::InkHUD::requestUpdate()
// Ignores all diplomacy: // Ignores all diplomacy:
// - the display *will* update // - the display *will* update
// - the specified update type *will* be used // - the specified update type *will* be used
// If the all parameter is true, the whole screen buffer is cleared and re-rendered
// If the async parameter is false, code flow is blocked while the update takes place // If the async parameter is false, code flow is blocked while the update takes place
void InkHUD::InkHUD::forceUpdate(EInk::UpdateTypes type, bool all, bool async) void InkHUD::InkHUD::forceUpdate(EInk::UpdateTypes type, bool async)
{ {
renderer->forceUpdate(type, all, async); renderer->forceUpdate(type, async);
} }
// Wait for any in-progress display update to complete before continuing // Wait for any in-progress display update to complete before continuing
+1 -13
View File
@@ -47,7 +47,6 @@ class InkHUD
void setDriver(Drivers::EInk *driver); void setDriver(Drivers::EInk *driver);
void setDisplayResilience(uint8_t fastPerFull = 5, float stressMultiplier = 2.0); void setDisplayResilience(uint8_t fastPerFull = 5, float stressMultiplier = 2.0);
void addApplet(const char *name, Applet *a, bool defaultActive = false, bool defaultAutoshow = false, uint8_t onTile = -1); void addApplet(const char *name, Applet *a, bool defaultActive = false, bool defaultAutoshow = false, uint8_t onTile = -1);
void notifyApplyingChanges();
void begin(); void begin();
@@ -63,11 +62,6 @@ class InkHUD
void navLeft(); void navLeft();
void navRight(); void navRight();
// Freetext handlers
void freeText(char c);
void freeTextDone();
void freeTextCancel();
// Trigger UI changes // Trigger UI changes
// - called by various InkHUD components // - called by various InkHUD components
// - suitable(?) for use by aux button, connected in variant nicheGraphics.h // - suitable(?) for use by aux button, connected in variant nicheGraphics.h
@@ -76,23 +70,17 @@ class InkHUD
void prevApplet(); void prevApplet();
void openMenu(); void openMenu();
void openAlignStick(); void openAlignStick();
void openKeyboard();
void closeKeyboard();
void nextTile(); void nextTile();
void prevTile(); void prevTile();
void rotate(); void rotate();
void rotateJoystick(uint8_t angle = 1); // rotate 90 deg by default void rotateJoystick(uint8_t angle = 1); // rotate 90 deg by default
void toggleBatteryIcon(); void toggleBatteryIcon();
// Used by TipsApplet to force menu to start on Region selection
bool forceRegionMenu = false;
// Updating the display // Updating the display
// - called by various InkHUD components // - called by various InkHUD components
void requestUpdate(); void requestUpdate();
void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool all = false, void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool async = true);
bool async = true);
void awaitUpdate(); void awaitUpdate();
// (Re)configuring WindowManager // (Re)configuring WindowManager
+10 -103
View File
@@ -56,16 +56,15 @@ void InkHUD::Renderer::setDisplayResilience(uint8_t fastPerFull, float stressMul
void InkHUD::Renderer::begin() void InkHUD::Renderer::begin()
{ {
forceUpdate(Drivers::EInk::UpdateTypes::FULL, true, false); forceUpdate(Drivers::EInk::UpdateTypes::FULL, false);
} }
// Set a flag, which will be picked up by runOnce, ASAP. // Set a flag, which will be picked up by runOnce, ASAP.
// Quite likely, multiple applets will all want to respond to one event (Observable, etc) // Quite likely, multiple applets will all want to respond to one event (Observable, etc)
// Each affected applet can independently call requestUpdate(), and all share the one opportunity to render, at next runOnce // Each affected applet can independently call requestUpdate(), and all share the one opportunity to render, at next runOnce
void InkHUD::Renderer::requestUpdate(bool all) void InkHUD::Renderer::requestUpdate()
{ {
requested = true; requested = true;
renderAll |= all;
// We will run the thread as soon as we loop(), // We will run the thread as soon as we loop(),
// after all Applets have had a chance to observe whatever event set this off // after all Applets have had a chance to observe whatever event set this off
@@ -80,11 +79,10 @@ void InkHUD::Renderer::requestUpdate(bool all)
// Sometimes, however, we will want to trigger a display update manually, in the absence of any sort of applet event // Sometimes, however, we will want to trigger a display update manually, in the absence of any sort of applet event
// Display health, for example. // Display health, for example.
// In these situations, we use forceUpdate // In these situations, we use forceUpdate
void InkHUD::Renderer::forceUpdate(Drivers::EInk::UpdateTypes type, bool all, bool async) void InkHUD::Renderer::forceUpdate(Drivers::EInk::UpdateTypes type, bool async)
{ {
requested = true; requested = true;
forced = true; forced = true;
renderAll |= all;
displayHealth.forceUpdateType(type); displayHealth.forceUpdateType(type);
// Normally, we need to start the timer, in case the display is busy and we briefly defer the update // Normally, we need to start the timer, in case the display is busy and we briefly defer the update
@@ -221,8 +219,7 @@ void InkHUD::Renderer::render(bool async)
Drivers::EInk::UpdateTypes updateType = decideUpdateType(); Drivers::EInk::UpdateTypes updateType = decideUpdateType();
// Render the new image // Render the new image
if (renderAll) clearBuffer();
clearBuffer();
renderUserApplets(); renderUserApplets();
renderPlaceholders(); renderPlaceholders();
renderSystemApplets(); renderSystemApplets();
@@ -250,7 +247,6 @@ void InkHUD::Renderer::render(bool async)
// Tidy up, ready for a new request // Tidy up, ready for a new request
requested = false; requested = false;
forced = false; forced = false;
renderAll = false;
} }
// Manually fill the image buffer with WHITE // Manually fill the image buffer with WHITE
@@ -263,76 +259,6 @@ void InkHUD::Renderer::clearBuffer()
memset(imageBuffer, 0xFF, imageBufferHeight * imageBufferWidth); memset(imageBuffer, 0xFF, imageBufferHeight * imageBufferWidth);
} }
// Manually clear the pixels below a tile
void InkHUD::Renderer::clearTile(Tile *t)
{
// Rotate the tile dimensions
int16_t left = 0;
int16_t top = 0;
uint16_t width = 0;
uint16_t height = 0;
switch (settings->rotation) {
case 0:
left = t->getLeft();
top = t->getTop();
width = t->getWidth();
height = t->getHeight();
break;
case 1:
left = driver->width - (t->getTop() + t->getHeight());
top = t->getLeft();
width = t->getHeight();
height = t->getWidth();
break;
case 2:
left = driver->width - (t->getLeft() + t->getWidth());
top = driver->height - (t->getTop() + t->getHeight());
width = t->getWidth();
height = t->getHeight();
break;
case 3:
left = t->getTop();
top = driver->height - (t->getLeft() + t->getWidth());
width = t->getHeight();
height = t->getWidth();
break;
}
// Calculate the bounds to clear
uint16_t xStart = (left < 0) ? 0 : left;
uint16_t yStart = (top < 0) ? 0 : top;
if (xStart >= driver->width || yStart >= driver->height || left + width < 0 || top + height < 0)
return; // the box is completely off the screen
uint16_t xEnd = left + width;
uint16_t yEnd = top + height;
if (xEnd > driver->width)
xEnd = driver->width;
if (yEnd > driver->height)
yEnd = driver->height;
// Clear the pixels
if (xStart == 0 && xEnd == driver->width) { // full width box is easier to clear
memset(imageBuffer + (yStart * imageBufferWidth), 0xFF, (yEnd - yStart) * imageBufferWidth);
} else {
const uint16_t byteStart = (xStart / 8) + 1;
const uint16_t byteEnd = xEnd / 8;
const uint8_t leadingByte = 0xFF >> (xStart - ((byteStart - 1) * 8));
const uint8_t trailingByte = (0xFF00 >> (xEnd - (byteEnd * 8))) & 0xFF;
for (uint16_t i = yStart * imageBufferWidth; i < yEnd * imageBufferWidth; i += imageBufferWidth) {
// Set the leading byte
imageBuffer[i + byteStart - 1] |= leadingByte;
// Set the continuous bytes
if (byteStart < byteEnd)
memset(imageBuffer + i + byteStart, 0xFF, byteEnd - byteStart);
// Set the trailing byte
if (byteEnd != imageBufferWidth)
imageBuffer[i + byteEnd] |= trailingByte;
}
}
}
void InkHUD::Renderer::checkLocks() void InkHUD::Renderer::checkLocks()
{ {
lockRendering = nullptr; lockRendering = nullptr;
@@ -397,12 +323,12 @@ Drivers::EInk::UpdateTypes InkHUD::Renderer::decideUpdateType()
if (!forced) { if (!forced) {
// User applets // User applets
for (Applet *ua : inkhud->userApplets) { for (Applet *ua : inkhud->userApplets) {
if (ua && ua->isForeground() && (ua->wantsToRender() || renderAll)) if (ua && ua->isForeground())
displayHealth.requestUpdateType(ua->wantsUpdateType()); displayHealth.requestUpdateType(ua->wantsUpdateType());
} }
// System Applets // System Applets
for (SystemApplet *sa : inkhud->systemApplets) { for (SystemApplet *sa : inkhud->systemApplets) {
if (sa && sa->isForeground() && (sa->wantsToRender() || sa->alwaysRender || renderAll)) if (sa && sa->isForeground())
displayHealth.requestUpdateType(sa->wantsUpdateType()); displayHealth.requestUpdateType(sa->wantsUpdateType());
} }
} }
@@ -420,16 +346,9 @@ void InkHUD::Renderer::renderUserApplets()
// Render any user applets which are currently visible // Render any user applets which are currently visible
for (Applet *ua : inkhud->userApplets) { for (Applet *ua : inkhud->userApplets) {
if (ua && ua->isActive() && ua->isForeground() && (ua->wantsToRender() || renderAll)) { if (ua && ua->isActive() && ua->isForeground()) {
// Clear the tile unless the applet wants to draw over its previous render
// or everything is getting re-rendered anyways
if (ua->wantsFullRender() && !renderAll)
clearTile(ua->getTile());
uint32_t start = millis(); uint32_t start = millis();
bool full = ua->wantsFullRender() || renderAll; ua->render(); // Draw!
ua->render(full); // Draw!
uint32_t stop = millis(); uint32_t stop = millis();
LOG_DEBUG("%s took %dms to render", ua->name, stop - start); LOG_DEBUG("%s took %dms to render", ua->name, stop - start);
} }
@@ -451,9 +370,6 @@ void InkHUD::Renderer::renderSystemApplets()
if (!sa->isForeground()) if (!sa->isForeground())
continue; continue;
if (!sa->wantsToRender() && !sa->alwaysRender && !renderAll)
continue;
// Skip if locked by another applet // Skip if locked by another applet
if (lockRendering && lockRendering != sa) if (lockRendering && lockRendering != sa)
continue; continue;
@@ -465,14 +381,8 @@ void InkHUD::Renderer::renderSystemApplets()
assert(sa->getTile()); assert(sa->getTile());
// Clear the tile unless the applet wants to draw over its previous render
// or everything is getting re-rendered anyways
if (sa->wantsFullRender() && !renderAll)
clearTile(sa->getTile());
// uint32_t start = millis(); // uint32_t start = millis();
bool full = sa->wantsFullRender() || renderAll; sa->render(); // Draw!
sa->render(full); // Draw!
// uint32_t stop = millis(); // uint32_t stop = millis();
// LOG_DEBUG("%s took %dms to render", sa->name, stop - start); // LOG_DEBUG("%s took %dms to render", sa->name, stop - start);
} }
@@ -499,10 +409,7 @@ void InkHUD::Renderer::renderPlaceholders()
// uint32_t start = millis(); // uint32_t start = millis();
for (Tile *t : emptyTiles) { for (Tile *t : emptyTiles) {
t->assignApplet(placeholder); t->assignApplet(placeholder);
// Clear the tile unless everything is getting re-rendered placeholder->render();
if (!renderAll)
clearTile(t);
placeholder->render(true); // full render
t->assignApplet(nullptr); t->assignApplet(nullptr);
} }
// uint32_t stop = millis(); // uint32_t stop = millis();
+2 -4
View File
@@ -37,8 +37,8 @@ class Renderer : protected concurrency::OSThread
// Call these to make the image change // Call these to make the image change
void requestUpdate(bool all = false); // Update display, if a foreground applet has info it wants to show void requestUpdate(); // Update display, if a foreground applet has info it wants to show
void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED, bool all = false, void forceUpdate(Drivers::EInk::UpdateTypes type = Drivers::EInk::UpdateTypes::UNSPECIFIED,
bool async = true); // Update display, regardless of whether any applets requested this bool async = true); // Update display, regardless of whether any applets requested this
// Wait for an update to complete // Wait for an update to complete
@@ -65,7 +65,6 @@ class Renderer : protected concurrency::OSThread
// Steps of the rendering process // Steps of the rendering process
void clearBuffer(); void clearBuffer();
void clearTile(Tile *t);
void checkLocks(); void checkLocks();
bool shouldUpdate(); bool shouldUpdate();
Drivers::EInk::UpdateTypes decideUpdateType(); Drivers::EInk::UpdateTypes decideUpdateType();
@@ -86,7 +85,6 @@ class Renderer : protected concurrency::OSThread
bool requested = false; bool requested = false;
bool forced = false; bool forced = false;
bool renderAll = false;
// For convenience // For convenience
InkHUD *inkhud = nullptr; InkHUD *inkhud = nullptr;
+4 -7
View File
@@ -22,14 +22,11 @@ class SystemApplet : public Applet
public: public:
// System applets have the right to: // System applets have the right to:
bool handleInput = false; // - respond to input from the user button bool handleInput = false; // - respond to input from the user button
bool handleFreeText = false; // - respond to free text input bool lockRendering = false; // - prevent other applets from being rendered during an update
bool lockRendering = false; // - prevent other applets from being rendered during an update bool lockRequests = false; // - prevent other applets from triggering display updates
bool lockRequests = false; // - prevent other applets from triggering display updates
bool alwaysRender = false; // - render every time the screen is updated
virtual void onReboot() { onShutdown(); } // - handle reboot specially virtual void onReboot() { onShutdown(); } // - handle reboot specially
virtual void onApplyingChanges() {}
// Other system applets may take precedence over our own system applet though // Other system applets may take precedence over our own system applet though
// The order an applet is passed to WindowManager::addSystemApplet determines this hierarchy (added earlier = higher rank) // The order an applet is passed to WindowManager::addSystemApplet determines this hierarchy (added earlier = higher rank)
@@ -43,4 +40,4 @@ class SystemApplet : public Applet
}; // namespace NicheGraphics::InkHUD }; // namespace NicheGraphics::InkHUD
#endif #endif
+2 -14
View File
@@ -18,7 +18,7 @@ static int32_t runtaskHighlight()
LOG_DEBUG("Dismissing Highlight"); LOG_DEBUG("Dismissing Highlight");
InkHUD::Tile::highlightShown = false; InkHUD::Tile::highlightShown = false;
InkHUD::Tile::highlightTarget = nullptr; InkHUD::Tile::highlightTarget = nullptr;
InkHUD::InkHUD::getInstance()->forceUpdate(Drivers::EInk::UpdateTypes::FAST, true); // Re-render, clearing the highlighting InkHUD::InkHUD::getInstance()->forceUpdate(Drivers::EInk::UpdateTypes::FAST); // Re-render, clearing the highlighting
return taskHighlight->disable(); return taskHighlight->disable();
} }
static void inittaskHighlight() static void inittaskHighlight()
@@ -190,18 +190,6 @@ void InkHUD::Tile::handleAppletPixel(int16_t x, int16_t y, Color c)
} }
} }
// Used in Renderer for clearing the tile
int16_t InkHUD::Tile::getLeft()
{
return left;
}
// Used in Renderer for clearing the tile
int16_t InkHUD::Tile::getTop()
{
return top;
}
// Called by Applet base class, when setting applet dimensions, immediately before render // Called by Applet base class, when setting applet dimensions, immediately before render
uint16_t InkHUD::Tile::getWidth() uint16_t InkHUD::Tile::getWidth()
{ {
@@ -232,7 +220,7 @@ void InkHUD::Tile::requestHighlight()
{ {
Tile::highlightTarget = this; Tile::highlightTarget = this;
Tile::highlightShown = false; Tile::highlightShown = false;
inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FAST, true); inkhud->forceUpdate(Drivers::EInk::UpdateTypes::FAST);
} }
// Starts the timer which will automatically dismiss the highlighting, if the tile doesn't organically redraw first // Starts the timer which will automatically dismiss the highlighting, if the tile doesn't organically redraw first

Some files were not shown because too many files have changed in this diff Show More