mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-28 05:30:30 +00:00
Compare commits
10 Commits
sfpp-dev
...
refactor-n
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90e2083d2c | ||
|
|
af1d4e08ff | ||
|
|
d772cd4431 | ||
|
|
b7b8071056 | ||
|
|
4a3d28f06b | ||
|
|
0f4210d2e8 | ||
|
|
dc6e109329 | ||
|
|
10141b2562 | ||
|
|
770085ddf7 | ||
|
|
833f950edc |
4
.github/actions/build-variant/action.yml
vendored
4
.github/actions/build-variant/action.yml
vendored
@@ -76,7 +76,7 @@ runs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
- name: PlatformIO ${{ inputs.arch }} download cache
|
- name: PlatformIO ${{ inputs.arch }} download cache
|
||||||
uses: actions/cache@v5
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.platformio/.cache
|
path: ~/.platformio/.cache
|
||||||
key: pio-cache-${{ inputs.arch }}-${{ hashFiles('.github/actions/**', '**.ini') }}
|
key: pio-cache-${{ inputs.arch }}-${{ hashFiles('.github/actions/**', '**.ini') }}
|
||||||
@@ -100,7 +100,7 @@ runs:
|
|||||||
id: version
|
id: version
|
||||||
|
|
||||||
- name: Store binaries as an artifact
|
- name: Store binaries as an artifact
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}
|
name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.long }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|||||||
2
.github/workflows/build_debian_src.yml
vendored
2
.github/workflows/build_debian_src.yml
vendored
@@ -64,7 +64,7 @@ jobs:
|
|||||||
PKG_VERSION: ${{ steps.version.outputs.deb }}
|
PKG_VERSION: ${{ steps.version.outputs.deb }}
|
||||||
|
|
||||||
- name: Store binaries as an artifact
|
- name: Store binaries as an artifact
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|||||||
23
.github/workflows/build_firmware.yml
vendored
23
.github/workflows/build_firmware.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
# Use 'arctastic' self-hosted runner pool when building in the main repo
|
# Use 'arctastic' self-hosted runner pool when building in the main repo
|
||||||
runs-on: ${{ github.repository_owner == 'meshtastic' && 'arctastic' || 'ubuntu-latest' }}
|
runs-on: ${{ github.repository_owner == 'meshtastic' && 'arctastic' || 'ubuntu-latest' }}
|
||||||
outputs:
|
outputs:
|
||||||
artifact-id: ${{ steps.upload-firmware.outputs.artifact-id }}
|
artifact-id: ${{ steps.upload.outputs.artifact-id }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
@@ -56,22 +56,20 @@ jobs:
|
|||||||
ota_firmware_source: ${{ steps.ota_dir.outputs.src || '' }}
|
ota_firmware_source: ${{ steps.ota_dir.outputs.src || '' }}
|
||||||
ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }}
|
ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }}
|
||||||
|
|
||||||
- name: Job summary
|
- name: Echo manifest from release/firmware-*.mt.json to job summary
|
||||||
|
if: ${{ always() }}
|
||||||
env:
|
env:
|
||||||
PIO_ENV: ${{ inputs.pio_env }}
|
PIO_ENV: ${{ inputs.pio_env }}
|
||||||
run: |
|
run: |
|
||||||
echo "## $PIO_ENV" >> $GITHUB_STEP_SUMMARY
|
echo "## Manifest: \`$PIO_ENV\`" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "<details><summary><strong>Manifest</strong></summary>" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo '' >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo '```json' >> $GITHUB_STEP_SUMMARY
|
echo '```json' >> $GITHUB_STEP_SUMMARY
|
||||||
cat release/firmware-*.mt.json >> $GITHUB_STEP_SUMMARY
|
cat release/firmware-*.mt.json >> $GITHUB_STEP_SUMMARY
|
||||||
echo '' >> $GITHUB_STEP_SUMMARY
|
echo '' >> $GITHUB_STEP_SUMMARY
|
||||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
echo "</details>" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
- name: Store binaries as an artifact
|
- name: Store binaries as an artifact
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
id: upload-firmware
|
id: upload
|
||||||
with:
|
with:
|
||||||
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}
|
name: firmware-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@@ -84,12 +82,3 @@ jobs:
|
|||||||
release/*.zip
|
release/*.zip
|
||||||
release/device-*.sh
|
release/device-*.sh
|
||||||
release/device-*.bat
|
release/device-*.bat
|
||||||
|
|
||||||
- name: Store manifests as an artifact
|
|
||||||
uses: actions/upload-artifact@v6
|
|
||||||
id: upload-manifest
|
|
||||||
with:
|
|
||||||
name: manifest-${{ inputs.platform }}-${{ inputs.pio_env }}-${{ inputs.version }}
|
|
||||||
overwrite: true
|
|
||||||
path: |
|
|
||||||
release/*.mt.json
|
|
||||||
|
|||||||
8
.github/workflows/build_one_target.yml
vendored
8
.github/workflows/build_one_target.yml
vendored
@@ -98,7 +98,7 @@ jobs:
|
|||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
path: ./
|
path: ./
|
||||||
pattern: firmware-*-*
|
pattern: firmware-*-*
|
||||||
@@ -111,7 +111,7 @@ jobs:
|
|||||||
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
||||||
|
|
||||||
- name: Repackage in single firmware zip
|
- name: Repackage in single firmware zip
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
|
name: firmware-${{inputs.target}}-${{ needs.version.outputs.long }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@@ -127,7 +127,7 @@ jobs:
|
|||||||
./Meshtastic_nRF52_factory_erase*.uf2
|
./Meshtastic_nRF52_factory_erase*.uf2
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: firmware-*-${{ needs.version.outputs.long }}
|
pattern: firmware-*-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -146,7 +146,7 @@ jobs:
|
|||||||
run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
|
run: zip -j -9 -r ./firmware-${{inputs.target}}-${{ needs.version.outputs.long }}.zip ./output
|
||||||
|
|
||||||
- name: Repackage in single elfs zip
|
- name: Repackage in single elfs zip
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
|
name: debug-elfs-${{inputs.target}}-${{ needs.version.outputs.long }}.zip
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|||||||
73
.github/workflows/main_matrix.yml
vendored
73
.github/workflows/main_matrix.yml
vendored
@@ -77,21 +77,16 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
check: ${{ fromJson(needs.setup.outputs.check) }}
|
check: ${{ fromJson(needs.setup.outputs.check) }}
|
||||||
# Use 'arctastic' self-hosted runner pool when checking in the main repo
|
|
||||||
runs-on: ${{ github.repository_owner == 'meshtastic' && 'arctastic' || 'ubuntu-latest' }}
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
|
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
with:
|
- name: Build base
|
||||||
submodules: recursive
|
id: base
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
uses: ./.github/actions/setup-base
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
|
||||||
- name: Check ${{ matrix.check.board }}
|
- name: Check ${{ matrix.check.board }}
|
||||||
uses: meshtastic/gh-action-firmware@main
|
run: bin/check-all.sh ${{ matrix.check.board }}
|
||||||
with:
|
|
||||||
pio_platform: ${{ matrix.check.platform }}
|
|
||||||
pio_env: ${{ matrix.check.board }}
|
|
||||||
pio_target: check
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
needs: [setup, version]
|
needs: [setup, version]
|
||||||
@@ -173,7 +168,7 @@ jobs:
|
|||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
path: ./
|
path: ./
|
||||||
pattern: firmware-${{matrix.arch}}-*
|
pattern: firmware-${{matrix.arch}}-*
|
||||||
@@ -183,7 +178,7 @@ jobs:
|
|||||||
run: ls -R
|
run: ls -R
|
||||||
|
|
||||||
- name: Repackage in single firmware zip
|
- name: Repackage in single firmware zip
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@@ -200,7 +195,7 @@ jobs:
|
|||||||
./Meshtastic_nRF52_factory_erase*.uf2
|
./Meshtastic_nRF52_factory_erase*.uf2
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -219,7 +214,7 @@ jobs:
|
|||||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||||
|
|
||||||
- name: Repackage in single elfs zip
|
- name: Repackage in single elfs zip
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@@ -233,40 +228,6 @@ jobs:
|
|||||||
description: "Download firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
|
description: "Download firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip. This artifact will be available for 90 days from creation"
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
shame:
|
|
||||||
if: github.repository == 'meshtastic/firmware'
|
|
||||||
continue-on-error: true
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [build]
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
filter: blob:none # means we download all the git history but none of the commit (except ones with checkout like the head)
|
|
||||||
fetch-depth: 0
|
|
||||||
- name: Download the current manifests
|
|
||||||
uses: actions/download-artifact@v7
|
|
||||||
with:
|
|
||||||
path: ./manifests-new/
|
|
||||||
pattern: manifest-*
|
|
||||||
merge-multiple: true
|
|
||||||
- name: Upload combined manifests for later commit and global stats crunching.
|
|
||||||
uses: actions/upload-artifact@v6
|
|
||||||
id: upload-manifest
|
|
||||||
with:
|
|
||||||
name: manifests-all
|
|
||||||
overwrite: true
|
|
||||||
path: |
|
|
||||||
manifests-new/*.mt.json
|
|
||||||
- name: Find the merge base
|
|
||||||
run: echo "MERGE_BASE=$(git merge-base "origin/$base" "$head")" >> $GITHUB_ENV
|
|
||||||
env:
|
|
||||||
base: ${{ github.base_ref }}
|
|
||||||
head: ${{ github.head_ref }}
|
|
||||||
- name: Download the old manifests
|
|
||||||
run: gh run download -R ${{ github.repository }} --commit ${{ env.MERGE_BASE }} --name manifests-all --dir manifest-old/
|
|
||||||
- name: Do scan and post comment
|
|
||||||
run: python3 bin/shame.py ${{ github.event.pull_request.number }} manifests-old/ manifests-new/
|
|
||||||
|
|
||||||
release-artifacts:
|
release-artifacts:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
|
if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
|
||||||
@@ -294,14 +255,14 @@ jobs:
|
|||||||
Autogenerated by github action, developer should edit as required before publishing...
|
Autogenerated by github action, developer should edit as required before publishing...
|
||||||
|
|
||||||
- name: Download source deb
|
- name: Download source deb
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
path: ./output/debian-src
|
path: ./output/debian-src
|
||||||
|
|
||||||
- name: Download `native-tft` pio deps
|
- name: Download `native-tft` pio deps
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -325,7 +286,7 @@ jobs:
|
|||||||
}' > firmware-${{ needs.version.outputs.long }}.json
|
}' > firmware-${{ needs.version.outputs.long }}.json
|
||||||
|
|
||||||
- name: Save Release manifest artifact
|
- name: Save Release manifest artifact
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: manifest-${{ needs.version.outputs.long }}
|
name: manifest-${{ needs.version.outputs.long }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@@ -366,7 +327,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -383,7 +344,7 @@ jobs:
|
|||||||
- name: Zip firmware
|
- name: Zip firmware
|
||||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||||
|
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -422,14 +383,14 @@ jobs:
|
|||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
- name: Get firmware artifacts
|
- name: Get firmware artifacts
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
path: ./publish
|
path: ./publish
|
||||||
|
|
||||||
- name: Get manifest artifact
|
- name: Get manifest artifact
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: manifest-${{ needs.version.outputs.long }}
|
pattern: manifest-${{ needs.version.outputs.long }}
|
||||||
path: ./publish
|
path: ./publish
|
||||||
|
|||||||
18
.github/workflows/merge_queue.yml
vendored
18
.github/workflows/merge_queue.yml
vendored
@@ -147,7 +147,7 @@ jobs:
|
|||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
path: ./
|
path: ./
|
||||||
pattern: firmware-${{matrix.arch}}-*
|
pattern: firmware-${{matrix.arch}}-*
|
||||||
@@ -160,7 +160,7 @@ jobs:
|
|||||||
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
|
||||||
|
|
||||||
- name: Repackage in single firmware zip
|
- name: Repackage in single firmware zip
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@@ -176,7 +176,7 @@ jobs:
|
|||||||
./Meshtastic_nRF52_factory_erase*.uf2
|
./Meshtastic_nRF52_factory_erase*.uf2
|
||||||
retention-days: 30
|
retention-days: 30
|
||||||
|
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
name: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -195,7 +195,7 @@ jobs:
|
|||||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||||
|
|
||||||
- name: Repackage in single elfs zip
|
- name: Repackage in single elfs zip
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@@ -235,14 +235,14 @@ jobs:
|
|||||||
Autogenerated by github action, developer should edit as required before publishing...
|
Autogenerated by github action, developer should edit as required before publishing...
|
||||||
|
|
||||||
- name: Download source deb
|
- name: Download source deb
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
pattern: firmware-debian-${{ needs.version.outputs.deb }}~UNRELEASED-src
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
path: ./output/debian-src
|
path: ./output/debian-src
|
||||||
|
|
||||||
- name: Download `native-tft` pio deps
|
- name: Download `native-tft` pio deps
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
pattern: platformio-deps-native-tft-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -292,7 +292,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
pattern: firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -309,7 +309,7 @@ jobs:
|
|||||||
- name: Zip firmware
|
- name: Zip firmware
|
||||||
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ needs.version.outputs.long }}.zip ./output
|
||||||
|
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
name: debug-elfs-${{matrix.arch}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -347,7 +347,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
|
|
||||||
- uses: actions/download-artifact@v7
|
- uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
pattern: firmware-{${{ env.targets }}}-${{ needs.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
2
.github/workflows/package_obs.yml
vendored
2
.github/workflows/package_obs.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
2
.github/workflows/package_pio_deps.yml
vendored
2
.github/workflows/package_pio_deps.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
|||||||
PLATFORMIO_CORE_DIR: pio/core
|
PLATFORMIO_CORE_DIR: pio/core
|
||||||
|
|
||||||
- name: Store binaries as an artifact
|
- name: Store binaries as an artifact
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: platformio-deps-${{ inputs.pio_env }}-${{ steps.version.outputs.long }}
|
name: platformio-deps-${{ inputs.pio_env }}-${{ steps.version.outputs.long }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|||||||
2
.github/workflows/package_ppa.yml
vendored
2
.github/workflows/package_ppa.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
|
|
||||||
- name: Download artifacts
|
- name: Download artifacts
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
name: firmware-debian-${{ steps.version.outputs.deb }}~${{ inputs.series }}-src
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
2
.github/workflows/pr_tests.yml
vendored
2
.github/workflows/pr_tests.yml
vendored
@@ -50,7 +50,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Download test artifacts
|
- name: Download test artifacts
|
||||||
if: needs.native-tests.result != 'skipped'
|
if: needs.native-tests.result != 'skipped'
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: platformio-test-report-${{ steps.version.outputs.long }}
|
name: platformio-test-report-${{ steps.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|||||||
2
.github/workflows/release_channels.yml
vendored
2
.github/workflows/release_channels.yml
vendored
@@ -102,7 +102,7 @@ jobs:
|
|||||||
PIP_DISABLE_PIP_VERSION_CHECK: 1
|
PIP_DISABLE_PIP_VERSION_CHECK: 1
|
||||||
|
|
||||||
- name: Create Bumps pull request
|
- name: Create Bumps pull request
|
||||||
uses: peter-evans/create-pull-request@v8
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
base: ${{ github.event.repository.default_branch }}
|
base: ${{ github.event.repository.default_branch }}
|
||||||
branch: create-pull-request/bump-version
|
branch: create-pull-request/bump-version
|
||||||
|
|||||||
2
.github/workflows/sec_sast_semgrep_cron.yml
vendored
2
.github/workflows/sec_sast_semgrep_cron.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
|||||||
|
|
||||||
# step 3
|
# step 3
|
||||||
- name: save report as pipeline artifact
|
- name: save report as pipeline artifact
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: report.sarif
|
name: report.sarif
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|||||||
12
.github/workflows/test_native.yml
vendored
12
.github/workflows/test_native.yml
vendored
@@ -59,7 +59,7 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
|
|
||||||
- name: Save coverage information
|
- name: Save coverage information
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
if: always() # run this step even if previous step failed
|
if: always() # run this step even if previous step failed
|
||||||
with:
|
with:
|
||||||
name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }}
|
name: lcov-coverage-info-native-simulator-test-${{ steps.version.outputs.long }}
|
||||||
@@ -94,7 +94,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Save test results
|
- name: Save test results
|
||||||
if: always() # run this step even if previous step failed
|
if: always() # run this step even if previous step failed
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: platformio-test-report-${{ steps.version.outputs.long }}
|
name: platformio-test-report-${{ steps.version.outputs.long }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
@@ -108,7 +108,7 @@ jobs:
|
|||||||
sed -i -e "s#${PWD}#.#" coverage_tests.info # Make paths relative.
|
sed -i -e "s#${PWD}#.#" coverage_tests.info # Make paths relative.
|
||||||
|
|
||||||
- name: Save coverage information
|
- name: Save coverage information
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
if: always() # run this step even if previous step failed
|
if: always() # run this step even if previous step failed
|
||||||
with:
|
with:
|
||||||
name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }}
|
name: lcov-coverage-info-native-platformio-tests-${{ steps.version.outputs.long }}
|
||||||
@@ -137,7 +137,7 @@ jobs:
|
|||||||
id: version
|
id: version
|
||||||
|
|
||||||
- name: Download test artifacts
|
- name: Download test artifacts
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
name: platformio-test-report-${{ steps.version.outputs.long }}
|
name: platformio-test-report-${{ steps.version.outputs.long }}
|
||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
@@ -150,7 +150,7 @@ jobs:
|
|||||||
reporter: java-junit
|
reporter: java-junit
|
||||||
|
|
||||||
- name: Download coverage artifacts
|
- name: Download coverage artifacts
|
||||||
uses: actions/download-artifact@v7
|
uses: actions/download-artifact@v6
|
||||||
with:
|
with:
|
||||||
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}
|
pattern: lcov-coverage-info-native-*-${{ steps.version.outputs.long }}
|
||||||
path: code-coverage-report
|
path: code-coverage-report
|
||||||
@@ -163,7 +163,7 @@ jobs:
|
|||||||
genhtml --quiet --legend --prefix "${PWD}" code-coverage-report/coverage_src.info --output-directory code-coverage-report
|
genhtml --quiet --legend --prefix "${PWD}" code-coverage-report/coverage_src.info --output-directory code-coverage-report
|
||||||
|
|
||||||
- name: Save Code Coverage Report
|
- name: Save Code Coverage Report
|
||||||
uses: actions/upload-artifact@v6
|
uses: actions/upload-artifact@v5
|
||||||
with:
|
with:
|
||||||
name: code-coverage-report-${{ steps.version.outputs.long }}
|
name: code-coverage-report-${{ steps.version.outputs.long }}
|
||||||
path: code-coverage-report
|
path: code-coverage-report
|
||||||
|
|||||||
4
.github/workflows/update_protobufs.yml
vendored
4
.github/workflows/update_protobufs.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
- name: Update submodule
|
- name: Update submodule
|
||||||
if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' }}
|
if: ${{ github.ref == 'refs/heads/master' }}
|
||||||
run: |
|
run: |
|
||||||
git submodule update --remote protobufs
|
git submodule update --remote protobufs
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
./bin/regen-protos.sh
|
./bin/regen-protos.sh
|
||||||
|
|
||||||
- name: Create pull request
|
- name: Create pull request
|
||||||
uses: peter-evans/create-pull-request@v8
|
uses: peter-evans/create-pull-request@v7
|
||||||
with:
|
with:
|
||||||
branch: create-pull-request/update-protobufs
|
branch: create-pull-request/update-protobufs
|
||||||
labels: submodules
|
labels: submodules
|
||||||
|
|||||||
@@ -9,24 +9,24 @@ plugins:
|
|||||||
lint:
|
lint:
|
||||||
enabled:
|
enabled:
|
||||||
- checkov@3.2.495
|
- checkov@3.2.495
|
||||||
- renovate@42.64.1
|
- renovate@42.30.4
|
||||||
- prettier@3.7.4
|
- prettier@3.7.4
|
||||||
- trufflehog@3.92.3
|
- trufflehog@3.91.2
|
||||||
- yamllint@1.37.1
|
- yamllint@1.37.1
|
||||||
- bandit@1.9.2
|
- bandit@1.9.2
|
||||||
- trivy@0.68.2
|
- trivy@0.67.2
|
||||||
- taplo@0.10.0
|
- taplo@0.10.0
|
||||||
- ruff@0.14.10
|
- ruff@0.14.7
|
||||||
- isort@7.0.0
|
- isort@7.0.0
|
||||||
- markdownlint@0.47.0
|
- markdownlint@0.46.0
|
||||||
- oxipng@10.0.0
|
- oxipng@9.1.5
|
||||||
- svgo@4.0.0
|
- svgo@4.0.0
|
||||||
- actionlint@1.7.9
|
- actionlint@1.7.9
|
||||||
- flake8@7.3.0
|
- flake8@7.3.0
|
||||||
- hadolint@2.14.0
|
- hadolint@2.14.0
|
||||||
- shfmt@3.6.0
|
- shfmt@3.6.0
|
||||||
- shellcheck@0.11.0
|
- shellcheck@0.11.0
|
||||||
- black@25.12.0
|
- black@25.11.0
|
||||||
- git-diff-check
|
- git-diff-check
|
||||||
- gitleaks@8.30.0
|
- gitleaks@8.30.0
|
||||||
- clang-format@16.0.3
|
- clang-format@16.0.3
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export APP_VERSION=$VERSION
|
|||||||
|
|
||||||
basename=firmware-$1-$VERSION
|
basename=firmware-$1-$VERSION
|
||||||
|
|
||||||
pio run --environment $1 -t mtjson # -v
|
pio run --environment $1 # -v
|
||||||
|
|
||||||
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
|
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
|
||||||
|
|
||||||
@@ -32,10 +32,20 @@ cp $BUILDDIR/$basename.factory.bin $OUTDIR/$basename.factory.bin
|
|||||||
echo "Copying ESP32 update bin file"
|
echo "Copying ESP32 update bin file"
|
||||||
cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin
|
cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin
|
||||||
|
|
||||||
echo "Copying Filesystem for ESP32 targets"
|
echo "Building Filesystem for ESP32 targets"
|
||||||
|
# If you want to build the webui, uncomment the following lines
|
||||||
|
# pio run --environment $1 -t buildfs
|
||||||
|
# cp .pio/build/$1/littlefs.bin $OUTDIR/littlefswebui-$1-$VERSION.bin
|
||||||
|
# # Remove webserver files from the filesystem and rebuild
|
||||||
|
# ls -l data/static # Diagnostic list of files
|
||||||
|
# rm -rf data/static
|
||||||
|
pio run --environment $1 -t buildfs --disable-auto-clean
|
||||||
cp $BUILDDIR/littlefs-$1-$VERSION.bin $OUTDIR/littlefs-$1-$VERSION.bin
|
cp $BUILDDIR/littlefs-$1-$VERSION.bin $OUTDIR/littlefs-$1-$VERSION.bin
|
||||||
cp bin/device-install.* $OUTDIR/
|
cp bin/device-install.* $OUTDIR/
|
||||||
cp bin/device-update.* $OUTDIR/
|
cp bin/device-update.* $OUTDIR/
|
||||||
|
|
||||||
echo "Copying manifest"
|
# Generate the manifest file
|
||||||
|
echo "Generating Meshtastic manifest"
|
||||||
|
TIMEFORMAT="Generated manifest in %E seconds"
|
||||||
|
time pio run --environment $1 -t mtjson --silent --disable-auto-clean
|
||||||
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
|
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export APP_VERSION=$VERSION
|
|||||||
|
|
||||||
basename=firmware-$1-$VERSION
|
basename=firmware-$1-$VERSION
|
||||||
|
|
||||||
pio run --environment $1 -t mtjson # -v
|
pio run --environment $1 # -v
|
||||||
|
|
||||||
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
|
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
|
||||||
|
|
||||||
@@ -47,5 +47,8 @@ if (echo $1 | grep -q "rak4631"); then
|
|||||||
cp $SRCHEX $OUTDIR/
|
cp $SRCHEX $OUTDIR/
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Copying manifest"
|
# Generate the manifest file
|
||||||
|
echo "Generating Meshtastic manifest"
|
||||||
|
TIMEFORMAT="Generated manifest in %E seconds"
|
||||||
|
time pio run --environment $1 -t mtjson --silent --disable-auto-clean
|
||||||
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
|
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
|
||||||
|
|||||||
@@ -22,12 +22,15 @@ export APP_VERSION=$VERSION
|
|||||||
|
|
||||||
basename=firmware-$1-$VERSION
|
basename=firmware-$1-$VERSION
|
||||||
|
|
||||||
pio run --environment $1 -t mtjson # -v
|
pio run --environment $1 # -v
|
||||||
|
|
||||||
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
|
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
|
||||||
|
|
||||||
echo "Copying uf2 file"
|
echo "Copying uf2 file"
|
||||||
cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2
|
cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2
|
||||||
|
|
||||||
echo "Copying manifest"
|
# Generate the manifest file
|
||||||
|
echo "Generating Meshtastic manifest"
|
||||||
|
TIMEFORMAT="Generated manifest in %E seconds"
|
||||||
|
time pio run --environment $1 -t mtjson --silent --disable-auto-clean
|
||||||
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
|
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
|
||||||
|
|||||||
@@ -22,12 +22,15 @@ export APP_VERSION=$VERSION
|
|||||||
|
|
||||||
basename=firmware-$1-$VERSION
|
basename=firmware-$1-$VERSION
|
||||||
|
|
||||||
pio run --environment $1 -t mtjson # -v
|
pio run --environment $1 # -v
|
||||||
|
|
||||||
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
|
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
|
||||||
|
|
||||||
echo "Copying STM32 bin file"
|
echo "Copying STM32 bin file"
|
||||||
cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin
|
cp $BUILDDIR/$basename.bin $OUTDIR/$basename.bin
|
||||||
|
|
||||||
echo "Copying manifest"
|
# Generate the manifest file
|
||||||
|
echo "Generating Meshtastic manifest"
|
||||||
|
TIMEFORMAT="Generated manifest in %E seconds"
|
||||||
|
time pio run --environment $1 -t mtjson --silent --disable-auto-clean
|
||||||
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
|
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
|
||||||
|
|||||||
@@ -184,8 +184,6 @@ Input:
|
|||||||
Logging:
|
Logging:
|
||||||
LogLevel: info # debug, info, warn, error
|
LogLevel: info # debug, info, warn, error
|
||||||
# TraceFile: /var/log/meshtasticd.json
|
# TraceFile: /var/log/meshtasticd.json
|
||||||
# JSONFile: /packets.json # File location for JSON output of decoded packets
|
|
||||||
# JSONFilter: position # filter for packets to save to JSON file
|
|
||||||
# AsciiLogs: true # default if not specified is !isatty() on stdout
|
# AsciiLogs: true # default if not specified is !isatty() on stdout
|
||||||
|
|
||||||
Webserver:
|
Webserver:
|
||||||
|
|||||||
@@ -87,9 +87,6 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
<releases>
|
<releases>
|
||||||
<release version="2.7.18" date="2025-12-20">
|
|
||||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.18</url>
|
|
||||||
</release>
|
|
||||||
<release version="2.7.17" date="2025-11-28">
|
<release version="2.7.17" date="2025-11-28">
|
||||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.17</url>
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.17</url>
|
||||||
</release>
|
</release>
|
||||||
|
|||||||
@@ -159,22 +159,20 @@ def load_boot_logo(source, target, env):
|
|||||||
|
|
||||||
# Load the boot logo on TFT builds
|
# Load the boot logo on TFT builds
|
||||||
if ("HAS_TFT", 1) in env.get("CPPDEFINES", []):
|
if ("HAS_TFT", 1) in env.get("CPPDEFINES", []):
|
||||||
env.AddPreAction(f"$BUILD_DIR/{lfsbin}", load_boot_logo)
|
env.AddPreAction('$BUILD_DIR/littlefs.bin', load_boot_logo)
|
||||||
|
|
||||||
mtjson_deps = ["buildprog"]
|
# Rename (mv) littlefs.bin to include the PROGNAME
|
||||||
if platform.name == "espressif32":
|
# This ensures the littlefs.bin is named consistently with the firmware
|
||||||
# Build littlefs image as part of mtjson target
|
env.AddPostAction('$BUILD_DIR/littlefs.bin', env.VerboseAction(
|
||||||
# Equivalent to `pio run -t buildfs`
|
f'mv $BUILD_DIR/littlefs.bin $BUILD_DIR/{lfsbin}',
|
||||||
target_lfs = env.DataToBin(
|
f'Renaming littlefs.bin to {lfsbin}'
|
||||||
join("$BUILD_DIR", "${ESP32_FS_IMAGE_NAME}"), "$PROJECT_DATA_DIR"
|
))
|
||||||
)
|
|
||||||
mtjson_deps.append(target_lfs)
|
|
||||||
|
|
||||||
env.AddCustomTarget(
|
env.AddCustomTarget(
|
||||||
name="mtjson",
|
name="mtjson",
|
||||||
dependencies=mtjson_deps,
|
dependencies=None,
|
||||||
actions=[manifest_gather],
|
actions=[manifest_gather],
|
||||||
title="Meshtastic Manifest",
|
title="Meshtastic Manifest",
|
||||||
description="Generating Meshtastic manifest JSON + Checksums",
|
description="Generating Meshtastic manifest JSON + Checksums",
|
||||||
always_build=False,
|
always_build=True,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ else:
|
|||||||
prefsLoc = env["PROJECT_DIR"] + "/version.properties"
|
prefsLoc = env["PROJECT_DIR"] + "/version.properties"
|
||||||
verObj = readProps(prefsLoc)
|
verObj = readProps(prefsLoc)
|
||||||
env.Replace(PROGNAME=f"firmware-{env.get('PIOENV')}-{verObj['long']}")
|
env.Replace(PROGNAME=f"firmware-{env.get('PIOENV')}-{verObj['long']}")
|
||||||
env.Replace(ESP32_FS_IMAGE_NAME=f"littlefs-{env.get('PIOENV')}-{verObj['long']}")
|
|
||||||
|
|
||||||
# Print the new program name for verification
|
# Print the new program name for verification
|
||||||
print(f"PROGNAME: {env.get('PROGNAME')}")
|
print(f"PROGNAME: {env.get('PROGNAME')}")
|
||||||
if platform.name == "espressif32":
|
|
||||||
print(f"ESP32_FS_IMAGE_NAME: {env.get('ESP32_FS_IMAGE_NAME')}")
|
|
||||||
|
|||||||
95
bin/shame.py
95
bin/shame.py
@@ -1,95 +0,0 @@
|
|||||||
import sys
|
|
||||||
import os
|
|
||||||
import json
|
|
||||||
from github import Github
|
|
||||||
|
|
||||||
def parseFile(path):
|
|
||||||
with open(path, "r") as f:
|
|
||||||
data = json.loads(f)
|
|
||||||
for file in data["files"]:
|
|
||||||
if file["name"].endswith(".bin"):
|
|
||||||
return file["name"], file["bytes"]
|
|
||||||
|
|
||||||
if len(sys.argv) != 4:
|
|
||||||
print(f"expected usage: {sys.argv[0]} <PR number> <path to old-manifests> <path to new-manifests>")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
pr_number = int(sys.argv[1])
|
|
||||||
|
|
||||||
token = os.getenv("GITHUB_TOKEN")
|
|
||||||
if not token:
|
|
||||||
raise EnvironmentError("GITHUB_TOKEN not found in environment.")
|
|
||||||
|
|
||||||
repo_name = os.getenv("GITHUB_REPOSITORY") # "owner/repo"
|
|
||||||
if not repo_name:
|
|
||||||
raise EnvironmentError("GITHUB_REPOSITORY not found in environment.")
|
|
||||||
|
|
||||||
oldFiles = sys.argv[2]
|
|
||||||
old = set(os.path.join(oldFiles, f) for f in os.listdir(oldFiles) if os.path.isfile(f))
|
|
||||||
newFiles = sys.argv[3]
|
|
||||||
new = set(os.path.join(newFiles, f) for f in os.listdir(newFiles) if os.path.isfile(f))
|
|
||||||
|
|
||||||
startMarkdown = "# Target Size Changes\n\n"
|
|
||||||
markdown = ""
|
|
||||||
|
|
||||||
newlyIntroduced = new - old
|
|
||||||
if len(newlyIntroduced) > 0:
|
|
||||||
markdown += "## Newly Introduced Targets\n\n"
|
|
||||||
# create a table
|
|
||||||
markdown += "| File | Size |\n"
|
|
||||||
markdown += "| ---- | ---- |\n"
|
|
||||||
for f in newlyIntroduced:
|
|
||||||
name, size = parseFile(f)
|
|
||||||
markdown += f"| `{name}` | {size}b |\n"
|
|
||||||
|
|
||||||
# do not log removed targets
|
|
||||||
# PRs only run a small subset of builds, so removed targets are not meaningful
|
|
||||||
# since they are very likely to just be not ran in PR CI
|
|
||||||
|
|
||||||
both = old & new
|
|
||||||
degradations = []
|
|
||||||
improvements = []
|
|
||||||
for f in both:
|
|
||||||
oldName, oldSize = parseFile(f)
|
|
||||||
_, newSize = parseFile(f)
|
|
||||||
if oldSize != newSize:
|
|
||||||
if newSize < oldSize:
|
|
||||||
improvements.append((oldName, oldSize, newSize))
|
|
||||||
else:
|
|
||||||
degradations.append((oldName, oldSize, newSize))
|
|
||||||
|
|
||||||
if len(degradations) > 0:
|
|
||||||
markdown += "\n## Degradation\n\n"
|
|
||||||
# create a table
|
|
||||||
markdown += "| File | Difference | Old Size | New Size |\n"
|
|
||||||
markdown += "| ---- | ---------- | -------- | -------- |\n"
|
|
||||||
for oldName, oldSize, newSize in degradations:
|
|
||||||
markdown += f"| `{oldName}` | **{oldSize - newSize}b** | {oldSize}b | {newSize}b |\n"
|
|
||||||
|
|
||||||
if len(improvements) > 0:
|
|
||||||
markdown += "\n## Improvement\n\n"
|
|
||||||
# create a table
|
|
||||||
markdown += "| File | Difference | Old Size | New Size |\n"
|
|
||||||
markdown += "| ---- | ---------- | -------- | -------- |\n"
|
|
||||||
for oldName, oldSize, newSize in improvements:
|
|
||||||
markdown += f"| `{oldName}` | **{oldSize - newSize}b** | {oldSize}b | {newSize}b |\n"
|
|
||||||
|
|
||||||
if len(markdown) == 0:
|
|
||||||
markdown = "No changes in target sizes detected."
|
|
||||||
|
|
||||||
g = Github(token)
|
|
||||||
repo = g.get_repo(repo_name)
|
|
||||||
pr = repo.get_pull(pr_number)
|
|
||||||
|
|
||||||
existing_comment = None
|
|
||||||
for comment in pr.get_issue_comments():
|
|
||||||
if comment.body.startswith(startMarkdown):
|
|
||||||
existing_comment = comment
|
|
||||||
break
|
|
||||||
|
|
||||||
final_markdown = startMarkdown + markdown
|
|
||||||
|
|
||||||
if existing_comment:
|
|
||||||
existing_comment.edit(body=final_markdown)
|
|
||||||
else:
|
|
||||||
pr.create_issue_comment(body=final_markdown)
|
|
||||||
6
debian/changelog
vendored
6
debian/changelog
vendored
@@ -1,9 +1,3 @@
|
|||||||
meshtasticd (2.7.18.0) unstable; urgency=medium
|
|
||||||
|
|
||||||
* Version 2.7.18
|
|
||||||
|
|
||||||
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Sat, 20 Dec 2025 15:47:25 +0000
|
|
||||||
|
|
||||||
meshtasticd (2.7.17.0) unstable; urgency=medium
|
meshtasticd (2.7.17.0) unstable; urgency=medium
|
||||||
|
|
||||||
* Version 2.7.17
|
* Version 2.7.17
|
||||||
|
|||||||
@@ -10,12 +10,6 @@ Import("env")
|
|||||||
platform = env.PioPlatform()
|
platform = env.PioPlatform()
|
||||||
|
|
||||||
sys.path.append(join(platform.get_package_dir("tool-esptoolpy")))
|
sys.path.append(join(platform.get_package_dir("tool-esptoolpy")))
|
||||||
# IntelHex workaround, remove after fixed upstream
|
|
||||||
# https://github.com/platformio/platform-espressif32/issues/1632
|
|
||||||
try:
|
|
||||||
import intelhex
|
|
||||||
except ImportError:
|
|
||||||
env.Execute("$PYTHONEXE -m pip install intelhex")
|
|
||||||
import esptool
|
import esptool
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ lib_deps =
|
|||||||
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL
|
# renovate: datasource=custom.pio depName=NonBlockingRTTTL packageName=end2endzone/library/NonBlockingRTTTL
|
||||||
end2endzone/NonBlockingRTTTL@1.4.0
|
end2endzone/NonBlockingRTTTL@1.4.0
|
||||||
build_flags = ${env.build_flags} -Os
|
build_flags = ${env.build_flags} -Os
|
||||||
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/> -<modules/Native/>
|
build_src_filter = ${env.build_src_filter} -<platform/portduino/> -<graphics/niche/>
|
||||||
|
|
||||||
; Common libs for communicating over TCP/IP networks such as MQTT
|
; Common libs for communicating over TCP/IP networks such as MQTT
|
||||||
[networking_base]
|
[networking_base]
|
||||||
@@ -103,13 +103,17 @@ lib_deps =
|
|||||||
thingsboard/TBPubSubClient@2.12.1
|
thingsboard/TBPubSubClient@2.12.1
|
||||||
# renovate: datasource=custom.pio depName=NTPClient packageName=arduino-libraries/library/NTPClient
|
# renovate: datasource=custom.pio depName=NTPClient packageName=arduino-libraries/library/NTPClient
|
||||||
arduino-libraries/NTPClient@3.2.1
|
arduino-libraries/NTPClient@3.2.1
|
||||||
|
|
||||||
; Extra TCP/IP networking libs for supported devices
|
|
||||||
[networking_extra]
|
|
||||||
lib_deps =
|
|
||||||
# renovate: datasource=custom.pio depName=Syslog packageName=arcao/library/Syslog
|
# renovate: datasource=custom.pio depName=Syslog packageName=arcao/library/Syslog
|
||||||
arcao/Syslog@2.0.0
|
arcao/Syslog@2.0.0
|
||||||
|
|
||||||
|
; Minimal networking libs for nrf52 (excludes Syslog to save flash)
|
||||||
|
[nrf52_networking_base]
|
||||||
|
lib_deps =
|
||||||
|
# renovate: datasource=custom.pio depName=TBPubSubClient packageName=thingsboard/library/TBPubSubClient
|
||||||
|
thingsboard/TBPubSubClient@2.12.1
|
||||||
|
# renovate: datasource=custom.pio depName=NTPClient packageName=arduino-libraries/library/NTPClient
|
||||||
|
arduino-libraries/NTPClient@3.2.1
|
||||||
|
|
||||||
[radiolib_base]
|
[radiolib_base]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
||||||
@@ -119,7 +123,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/862ed040c4ab44f0dfbbe492691f144886102588.zip
|
https://github.com/meshtastic/device-ui/archive/4fb5f24787caa841b58dbf623a52c4c5861d6722.zip
|
||||||
|
|
||||||
; Common libs for environmental measurements in telemetry module
|
; Common libs for environmental measurements in telemetry module
|
||||||
[environmental_base]
|
[environmental_base]
|
||||||
@@ -158,8 +162,8 @@ lib_deps =
|
|||||||
emotibit/EmotiBit MLX90632@1.0.8
|
emotibit/EmotiBit MLX90632@1.0.8
|
||||||
# renovate: datasource=custom.pio depName=Adafruit MLX90614 packageName=adafruit/library/Adafruit MLX90614 Library
|
# renovate: datasource=custom.pio depName=Adafruit MLX90614 packageName=adafruit/library/Adafruit MLX90614 Library
|
||||||
adafruit/Adafruit MLX90614 Library@2.1.5
|
adafruit/Adafruit MLX90614 Library@2.1.5
|
||||||
# renovate: datasource=git-refs depName=INA3221 packageName=https://github.com/sgtwilko/INA3221 gitBranch=FixOverflow
|
# renovate: datasource=github-tags depName=INA3221 packageName=sgtwilko/INA3221
|
||||||
https://github.com/sgtwilko/INA3221/archive/bb03d7e9bfcc74fc798838a54f4f99738f29fc6a.zip
|
https://github.com/sgtwilko/INA3221#bb03d7e9bfcc74fc798838a54f4f99738f29fc6a
|
||||||
# renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass
|
# renovate: datasource=custom.pio depName=QMC5883L Compass packageName=mprograms/library/QMC5883LCompass
|
||||||
mprograms/QMC5883LCompass@1.2.3
|
mprograms/QMC5883LCompass@1.2.3
|
||||||
# renovate: datasource=custom.pio depName=DFRobot_RTU packageName=dfrobot/library/DFRobot_RTU
|
# renovate: datasource=custom.pio depName=DFRobot_RTU packageName=dfrobot/library/DFRobot_RTU
|
||||||
|
|||||||
Submodule protobufs updated: c474fd3f49...4095e59890
@@ -31,9 +31,6 @@ const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaC
|
|||||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST:
|
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST:
|
||||||
return useShortName ? "LongF" : "LongFast";
|
return useShortName ? "LongF" : "LongFast";
|
||||||
break;
|
break;
|
||||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO:
|
|
||||||
return useShortName ? "LongT" : "LongTurbo";
|
|
||||||
break;
|
|
||||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
|
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
|
||||||
return useShortName ? "LongM" : "LongMod";
|
return useShortName ? "LongM" : "LongMod";
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -10,7 +10,10 @@
|
|||||||
*/
|
*/
|
||||||
#include "FSCommon.h"
|
#include "FSCommon.h"
|
||||||
#include "SPILock.h"
|
#include "SPILock.h"
|
||||||
|
#include "SafeFile.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
#include <pb_decode.h>
|
||||||
|
#include <pb_encode.h>
|
||||||
|
|
||||||
// Software SPI is used by MUI so disable SD card here until it's also implemented
|
// Software SPI is used by MUI so disable SD card here until it's also implemented
|
||||||
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
|
||||||
@@ -335,4 +338,63 @@ void setupSDCard()
|
|||||||
LOG_DEBUG("Total space: %lu MB", (uint32_t)(SD.totalBytes() / (1024 * 1024)));
|
LOG_DEBUG("Total space: %lu MB", (uint32_t)(SD.totalBytes() / (1024 * 1024)));
|
||||||
LOG_DEBUG("Used space: %lu MB", (uint32_t)(SD.usedBytes() / (1024 * 1024)));
|
LOG_DEBUG("Used space: %lu MB", (uint32_t)(SD.usedBytes() / (1024 * 1024)));
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Load a protobuf from a file, return LoadFileResult */
|
||||||
|
LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct)
|
||||||
|
{
|
||||||
|
LoadFileResult state = LoadFileResult::OTHER_FAILURE;
|
||||||
|
#ifdef FSCom
|
||||||
|
concurrency::LockGuard g(spiLock);
|
||||||
|
|
||||||
|
auto f = FSCom.open(filename, FILE_O_READ);
|
||||||
|
|
||||||
|
if (f) {
|
||||||
|
LOG_INFO("Load %s", filename);
|
||||||
|
pb_istream_t stream = {&readcb, &f, protoSize};
|
||||||
|
if (fields != &meshtastic_NodeDatabase_msg) // contains a vector object
|
||||||
|
memset(dest_struct, 0, objSize);
|
||||||
|
if (!pb_decode(&stream, fields, dest_struct)) {
|
||||||
|
LOG_ERROR("Error: can't decode protobuf %s", PB_GET_ERROR(&stream));
|
||||||
|
state = LoadFileResult::DECODE_FAILED;
|
||||||
|
} else {
|
||||||
|
LOG_INFO("Loaded %s successfully", filename);
|
||||||
|
state = LoadFileResult::LOAD_SUCCESS;
|
||||||
|
}
|
||||||
|
f.close();
|
||||||
|
} else {
|
||||||
|
LOG_ERROR("Could not open / read %s", filename);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
LOG_ERROR("ERROR: Filesystem not implemented");
|
||||||
|
state = LoadFileResult::NO_FILESYSTEM;
|
||||||
|
#endif
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Save a protobuf from a file, return true for success */
|
||||||
|
bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct, bool fullAtomic)
|
||||||
|
{
|
||||||
|
bool okay = false;
|
||||||
|
#ifdef FSCom
|
||||||
|
auto f = SafeFile(filename, fullAtomic);
|
||||||
|
|
||||||
|
LOG_INFO("Save %s", filename);
|
||||||
|
pb_ostream_t stream = {&writecb, static_cast<Print *>(&f), protoSize};
|
||||||
|
|
||||||
|
if (!pb_encode(&stream, fields, dest_struct)) {
|
||||||
|
LOG_ERROR("Error: can't encode protobuf %s", PB_GET_ERROR(&stream));
|
||||||
|
} else {
|
||||||
|
okay = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool writeSucceeded = f.close();
|
||||||
|
|
||||||
|
if (!okay || !writeSucceeded) {
|
||||||
|
LOG_ERROR("Can't write prefs!");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
LOG_ERROR("ERROR: Filesystem not implemented");
|
||||||
|
#endif
|
||||||
|
return okay;
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,19 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
enum LoadFileResult {
|
||||||
|
// Successfully opened the file
|
||||||
|
LOAD_SUCCESS = 1,
|
||||||
|
// File does not exist
|
||||||
|
NOT_FOUND = 2,
|
||||||
|
// Device does not have a filesystem
|
||||||
|
NO_FILESYSTEM = 3,
|
||||||
|
// File exists, but could not decode protobufs
|
||||||
|
DECODE_FAILED = 4,
|
||||||
|
// File exists, but open failed for some reason
|
||||||
|
OTHER_FAILURE = 5
|
||||||
|
};
|
||||||
|
|
||||||
// Cross platform filesystem API
|
// Cross platform filesystem API
|
||||||
|
|
||||||
#if defined(ARCH_PORTDUINO)
|
#if defined(ARCH_PORTDUINO)
|
||||||
@@ -55,4 +68,8 @@ bool renameFile(const char *pathFrom, const char *pathTo);
|
|||||||
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels);
|
std::vector<meshtastic_FileInfo> getFiles(const char *dirname, uint8_t levels);
|
||||||
void listDir(const char *dirname, uint8_t levels, bool del = false);
|
void listDir(const char *dirname, uint8_t levels, bool del = false);
|
||||||
void rmDir(const char *dirname);
|
void rmDir(const char *dirname);
|
||||||
void setupSDCard();
|
void setupSDCard();
|
||||||
|
LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields, void *dest_struct);
|
||||||
|
|
||||||
|
bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct,
|
||||||
|
bool fullAtomic = true);
|
||||||
@@ -1,426 +0,0 @@
|
|||||||
#include "configuration.h"
|
|
||||||
#if HAS_SCREEN
|
|
||||||
#include "FSCommon.h"
|
|
||||||
#include "MessageStore.h"
|
|
||||||
#include "NodeDB.h"
|
|
||||||
#include "SPILock.h"
|
|
||||||
#include "SafeFile.h"
|
|
||||||
#include "gps/RTC.h"
|
|
||||||
#include "graphics/draw/MessageRenderer.h"
|
|
||||||
#include <cstring> // memcpy
|
|
||||||
|
|
||||||
#ifndef MESSAGE_TEXT_POOL_SIZE
|
|
||||||
#define MESSAGE_TEXT_POOL_SIZE (MAX_MESSAGES_SAVED * MAX_MESSAGE_SIZE)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Global message text pool and state
|
|
||||||
static char *g_messagePool = nullptr;
|
|
||||||
static size_t g_poolWritePos = 0;
|
|
||||||
|
|
||||||
// Reset pool (called on boot or clear)
|
|
||||||
static inline void resetMessagePool()
|
|
||||||
{
|
|
||||||
if (!g_messagePool) {
|
|
||||||
g_messagePool = static_cast<char *>(malloc(MESSAGE_TEXT_POOL_SIZE));
|
|
||||||
if (!g_messagePool) {
|
|
||||||
LOG_ERROR("MessageStore: Failed to allocate %d bytes for message pool", MESSAGE_TEXT_POOL_SIZE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_poolWritePos = 0;
|
|
||||||
memset(g_messagePool, 0, MESSAGE_TEXT_POOL_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate text in pool and return offset
|
|
||||||
// If not enough space remains, wrap around (ring buffer style)
|
|
||||||
static inline uint16_t storeTextInPool(const char *src, size_t len)
|
|
||||||
{
|
|
||||||
if (len >= MAX_MESSAGE_SIZE)
|
|
||||||
len = MAX_MESSAGE_SIZE - 1;
|
|
||||||
|
|
||||||
// Wrap pool if out of space
|
|
||||||
if (g_poolWritePos + len + 1 >= MESSAGE_TEXT_POOL_SIZE) {
|
|
||||||
g_poolWritePos = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t offset = g_poolWritePos;
|
|
||||||
memcpy(&g_messagePool[g_poolWritePos], src, len);
|
|
||||||
g_messagePool[g_poolWritePos + len] = '\0';
|
|
||||||
g_poolWritePos += (len + 1);
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve a const pointer to message text by offset
|
|
||||||
static inline const char *getTextFromPool(uint16_t offset)
|
|
||||||
{
|
|
||||||
if (!g_messagePool || offset >= MESSAGE_TEXT_POOL_SIZE)
|
|
||||||
return "";
|
|
||||||
return &g_messagePool[offset];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper: assign a timestamp (RTC if available, else boot-relative)
|
|
||||||
static inline void assignTimestamp(StoredMessage &sm)
|
|
||||||
{
|
|
||||||
uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true);
|
|
||||||
if (nowSecs) {
|
|
||||||
sm.timestamp = nowSecs;
|
|
||||||
sm.isBootRelative = false;
|
|
||||||
} else {
|
|
||||||
sm.timestamp = millis() / 1000;
|
|
||||||
sm.isBootRelative = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic push with cap (used by live + persisted queues)
|
|
||||||
template <typename T> static inline void pushWithLimit(std::deque<T> &queue, const T &msg)
|
|
||||||
{
|
|
||||||
if (queue.size() >= MAX_MESSAGES_SAVED)
|
|
||||||
queue.pop_front();
|
|
||||||
queue.push_back(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T> static inline void pushWithLimit(std::deque<T> &queue, T &&msg)
|
|
||||||
{
|
|
||||||
if (queue.size() >= MAX_MESSAGES_SAVED)
|
|
||||||
queue.pop_front();
|
|
||||||
queue.emplace_back(std::move(msg));
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageStore::MessageStore(const std::string &label)
|
|
||||||
{
|
|
||||||
filename = "/Messages_" + label + ".msgs";
|
|
||||||
resetMessagePool(); // initialize text pool on boot
|
|
||||||
}
|
|
||||||
|
|
||||||
// Live message handling (RAM only)
|
|
||||||
void MessageStore::addLiveMessage(StoredMessage &&msg)
|
|
||||||
{
|
|
||||||
pushWithLimit(liveMessages, std::move(msg));
|
|
||||||
}
|
|
||||||
void MessageStore::addLiveMessage(const StoredMessage &msg)
|
|
||||||
{
|
|
||||||
pushWithLimit(liveMessages, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add from incoming/outgoing packet
|
|
||||||
const StoredMessage &MessageStore::addFromPacket(const meshtastic_MeshPacket &packet)
|
|
||||||
{
|
|
||||||
StoredMessage sm;
|
|
||||||
assignTimestamp(sm);
|
|
||||||
sm.channelIndex = packet.channel;
|
|
||||||
|
|
||||||
const char *payload = reinterpret_cast<const char *>(packet.decoded.payload.bytes);
|
|
||||||
size_t len = strnlen(payload, MAX_MESSAGE_SIZE - 1);
|
|
||||||
sm.textOffset = storeTextInPool(payload, len);
|
|
||||||
sm.textLength = len;
|
|
||||||
|
|
||||||
// Determine sender
|
|
||||||
uint32_t localNode = nodeDB->getNodeNum();
|
|
||||||
sm.sender = (packet.from == 0) ? localNode : packet.from;
|
|
||||||
|
|
||||||
sm.dest = packet.to;
|
|
||||||
|
|
||||||
bool isDM = (sm.dest != 0 && sm.dest != NODENUM_BROADCAST);
|
|
||||||
|
|
||||||
if (packet.from == 0) {
|
|
||||||
sm.type = isDM ? MessageType::DM_TO_US : MessageType::BROADCAST;
|
|
||||||
sm.ackStatus = AckStatus::NONE;
|
|
||||||
} else {
|
|
||||||
sm.type = isDM ? MessageType::DM_TO_US : MessageType::BROADCAST;
|
|
||||||
sm.ackStatus = AckStatus::ACKED;
|
|
||||||
}
|
|
||||||
|
|
||||||
addLiveMessage(sm);
|
|
||||||
return liveMessages.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Outgoing/manual message
|
|
||||||
void MessageStore::addFromString(uint32_t sender, uint8_t channelIndex, const std::string &text)
|
|
||||||
{
|
|
||||||
StoredMessage sm;
|
|
||||||
|
|
||||||
// Always use our local time (helper handles RTC vs boot time)
|
|
||||||
assignTimestamp(sm);
|
|
||||||
|
|
||||||
sm.sender = sender;
|
|
||||||
sm.channelIndex = channelIndex;
|
|
||||||
sm.textOffset = storeTextInPool(text.c_str(), text.size());
|
|
||||||
sm.textLength = text.size();
|
|
||||||
|
|
||||||
// Use the provided destination
|
|
||||||
sm.dest = sender;
|
|
||||||
sm.type = MessageType::DM_TO_US;
|
|
||||||
|
|
||||||
// Outgoing messages always start with unknown ack status
|
|
||||||
sm.ackStatus = AckStatus::NONE;
|
|
||||||
|
|
||||||
addLiveMessage(sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if ENABLE_MESSAGE_PERSISTENCE
|
|
||||||
|
|
||||||
// Compact, fixed-size on-flash representation using offset + length
|
|
||||||
struct __attribute__((packed)) StoredMessageRecord {
|
|
||||||
uint32_t timestamp;
|
|
||||||
uint32_t sender;
|
|
||||||
uint8_t channelIndex;
|
|
||||||
uint32_t dest;
|
|
||||||
uint8_t isBootRelative;
|
|
||||||
uint8_t ackStatus; // static_cast<uint8_t>(AckStatus)
|
|
||||||
uint8_t type; // static_cast<uint8_t>(MessageType)
|
|
||||||
uint16_t textLength; // message length
|
|
||||||
char text[MAX_MESSAGE_SIZE]; // store actual text here
|
|
||||||
};
|
|
||||||
|
|
||||||
// Serialize one StoredMessage to flash
|
|
||||||
static inline void writeMessageRecord(SafeFile &f, const StoredMessage &m)
|
|
||||||
{
|
|
||||||
StoredMessageRecord rec = {};
|
|
||||||
rec.timestamp = m.timestamp;
|
|
||||||
rec.sender = m.sender;
|
|
||||||
rec.channelIndex = m.channelIndex;
|
|
||||||
rec.dest = m.dest;
|
|
||||||
rec.isBootRelative = m.isBootRelative;
|
|
||||||
rec.ackStatus = static_cast<uint8_t>(m.ackStatus);
|
|
||||||
rec.type = static_cast<uint8_t>(m.type);
|
|
||||||
rec.textLength = m.textLength;
|
|
||||||
|
|
||||||
// Copy the actual text into the record from RAM pool
|
|
||||||
const char *txt = getTextFromPool(m.textOffset);
|
|
||||||
strncpy(rec.text, txt, MAX_MESSAGE_SIZE - 1);
|
|
||||||
rec.text[MAX_MESSAGE_SIZE - 1] = '\0';
|
|
||||||
|
|
||||||
f.write(reinterpret_cast<const uint8_t *>(&rec), sizeof(rec));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deserialize one StoredMessage from flash; returns false on short read
|
|
||||||
static inline bool readMessageRecord(File &f, StoredMessage &m)
|
|
||||||
{
|
|
||||||
StoredMessageRecord rec = {};
|
|
||||||
if (f.readBytes(reinterpret_cast<char *>(&rec), sizeof(rec)) != sizeof(rec))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
m.timestamp = rec.timestamp;
|
|
||||||
m.sender = rec.sender;
|
|
||||||
m.channelIndex = rec.channelIndex;
|
|
||||||
m.dest = rec.dest;
|
|
||||||
m.isBootRelative = rec.isBootRelative;
|
|
||||||
m.ackStatus = static_cast<AckStatus>(rec.ackStatus);
|
|
||||||
m.type = static_cast<MessageType>(rec.type);
|
|
||||||
m.textLength = rec.textLength;
|
|
||||||
|
|
||||||
// 💡 Re-store text into pool and update offset
|
|
||||||
m.textLength = strnlen(rec.text, MAX_MESSAGE_SIZE - 1);
|
|
||||||
m.textOffset = storeTextInPool(rec.text, m.textLength);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageStore::saveToFlash()
|
|
||||||
{
|
|
||||||
#ifdef FSCom
|
|
||||||
// Ensure root exists
|
|
||||||
spiLock->lock();
|
|
||||||
FSCom.mkdir("/");
|
|
||||||
spiLock->unlock();
|
|
||||||
|
|
||||||
SafeFile f(filename.c_str(), false);
|
|
||||||
|
|
||||||
spiLock->lock();
|
|
||||||
uint8_t count = static_cast<uint8_t>(liveMessages.size());
|
|
||||||
if (count > MAX_MESSAGES_SAVED)
|
|
||||||
count = MAX_MESSAGES_SAVED;
|
|
||||||
f.write(&count, 1);
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < count; ++i) {
|
|
||||||
writeMessageRecord(f, liveMessages[i]);
|
|
||||||
}
|
|
||||||
spiLock->unlock();
|
|
||||||
|
|
||||||
f.close();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageStore::loadFromFlash()
|
|
||||||
{
|
|
||||||
std::deque<StoredMessage>().swap(liveMessages);
|
|
||||||
resetMessagePool(); // reset pool when loading
|
|
||||||
|
|
||||||
#ifdef FSCom
|
|
||||||
concurrency::LockGuard guard(spiLock);
|
|
||||||
|
|
||||||
if (!FSCom.exists(filename.c_str()))
|
|
||||||
return;
|
|
||||||
|
|
||||||
auto f = FSCom.open(filename.c_str(), FILE_O_READ);
|
|
||||||
if (!f)
|
|
||||||
return;
|
|
||||||
|
|
||||||
uint8_t count = 0;
|
|
||||||
f.readBytes(reinterpret_cast<char *>(&count), 1);
|
|
||||||
if (count > MAX_MESSAGES_SAVED)
|
|
||||||
count = MAX_MESSAGES_SAVED;
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < count; ++i) {
|
|
||||||
StoredMessage m;
|
|
||||||
if (!readMessageRecord(f, m))
|
|
||||||
break;
|
|
||||||
liveMessages.push_back(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
f.close();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
// If persistence is disabled, these functions become no-ops
|
|
||||||
void MessageStore::saveToFlash() {}
|
|
||||||
void MessageStore::loadFromFlash() {}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Clear all messages (RAM + persisted queue)
|
|
||||||
void MessageStore::clearAllMessages()
|
|
||||||
{
|
|
||||||
std::deque<StoredMessage>().swap(liveMessages);
|
|
||||||
resetMessagePool();
|
|
||||||
|
|
||||||
#ifdef FSCom
|
|
||||||
SafeFile f(filename.c_str(), false);
|
|
||||||
uint8_t count = 0;
|
|
||||||
f.write(&count, 1); // write "0 messages"
|
|
||||||
f.close();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal helper: erase first or last message matching a predicate
|
|
||||||
template <typename Predicate> static void eraseIf(std::deque<StoredMessage> &deque, Predicate pred, bool fromBack = false)
|
|
||||||
{
|
|
||||||
if (fromBack) {
|
|
||||||
// Iterate from the back and erase all matches from the end
|
|
||||||
for (auto it = deque.rbegin(); it != deque.rend();) {
|
|
||||||
if (pred(*it)) {
|
|
||||||
it = std::deque<StoredMessage>::reverse_iterator(deque.erase(std::next(it).base()));
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Manual forward search to erase all matches
|
|
||||||
for (auto it = deque.begin(); it != deque.end();) {
|
|
||||||
if (pred(*it)) {
|
|
||||||
it = deque.erase(it);
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete oldest message (RAM + persisted queue)
|
|
||||||
void MessageStore::deleteOldestMessage()
|
|
||||||
{
|
|
||||||
eraseIf(liveMessages, [](StoredMessage &) { return true; });
|
|
||||||
saveToFlash();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete oldest message in a specific channel
|
|
||||||
void MessageStore::deleteOldestMessageInChannel(uint8_t channel)
|
|
||||||
{
|
|
||||||
auto pred = [channel](const StoredMessage &m) { return m.type == MessageType::BROADCAST && m.channelIndex == channel; };
|
|
||||||
eraseIf(liveMessages, pred);
|
|
||||||
saveToFlash();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageStore::deleteAllMessagesInChannel(uint8_t channel)
|
|
||||||
{
|
|
||||||
auto pred = [channel](const StoredMessage &m) { return m.type == MessageType::BROADCAST && m.channelIndex == channel; };
|
|
||||||
eraseIf(liveMessages, pred, false /* delete ALL, not just first */);
|
|
||||||
saveToFlash();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MessageStore::deleteAllMessagesWithPeer(uint32_t peer)
|
|
||||||
{
|
|
||||||
uint32_t local = nodeDB->getNodeNum();
|
|
||||||
auto pred = [&](const StoredMessage &m) {
|
|
||||||
if (m.type != MessageType::DM_TO_US)
|
|
||||||
return false;
|
|
||||||
uint32_t other = (m.sender == local) ? m.dest : m.sender;
|
|
||||||
return other == peer;
|
|
||||||
};
|
|
||||||
eraseIf(liveMessages, pred, false);
|
|
||||||
saveToFlash();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete oldest message in a direct chat with a node
|
|
||||||
void MessageStore::deleteOldestMessageWithPeer(uint32_t peer)
|
|
||||||
{
|
|
||||||
auto pred = [peer](const StoredMessage &m) {
|
|
||||||
if (m.type != MessageType::DM_TO_US)
|
|
||||||
return false;
|
|
||||||
uint32_t other = (m.sender == nodeDB->getNodeNum()) ? m.dest : m.sender;
|
|
||||||
return other == peer;
|
|
||||||
};
|
|
||||||
eraseIf(liveMessages, pred);
|
|
||||||
saveToFlash();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::deque<StoredMessage> MessageStore::getChannelMessages(uint8_t channel) const
|
|
||||||
{
|
|
||||||
std::deque<StoredMessage> result;
|
|
||||||
for (const auto &m : liveMessages) {
|
|
||||||
if (m.type == MessageType::BROADCAST && m.channelIndex == channel) {
|
|
||||||
result.push_back(m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::deque<StoredMessage> MessageStore::getDirectMessages() const
|
|
||||||
{
|
|
||||||
std::deque<StoredMessage> result;
|
|
||||||
for (const auto &m : liveMessages) {
|
|
||||||
if (m.type == MessageType::DM_TO_US) {
|
|
||||||
result.push_back(m);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upgrade boot-relative timestamps once RTC is valid
|
|
||||||
// Only same-boot boot-relative messages are healed.
|
|
||||||
// Persisted boot-relative messages from old boots stay ??? forever.
|
|
||||||
void MessageStore::upgradeBootRelativeTimestamps()
|
|
||||||
{
|
|
||||||
uint32_t nowSecs = getValidTime(RTCQuality::RTCQualityDevice, true);
|
|
||||||
if (nowSecs == 0)
|
|
||||||
return; // Still no valid RTC
|
|
||||||
|
|
||||||
uint32_t bootNow = millis() / 1000;
|
|
||||||
|
|
||||||
auto fix = [&](std::deque<StoredMessage> &dq) {
|
|
||||||
for (auto &m : dq) {
|
|
||||||
if (m.isBootRelative && m.timestamp <= bootNow) {
|
|
||||||
uint32_t bootOffset = nowSecs - bootNow;
|
|
||||||
m.timestamp += bootOffset;
|
|
||||||
m.isBootRelative = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fix(liveMessages);
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *MessageStore::getText(const StoredMessage &msg)
|
|
||||||
{
|
|
||||||
// Wrapper around the internal helper
|
|
||||||
return getTextFromPool(msg.textOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t MessageStore::storeText(const char *src, size_t len)
|
|
||||||
{
|
|
||||||
// Wrapper around the internal helper
|
|
||||||
return storeTextInPool(src, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Global definition
|
|
||||||
MessageStore messageStore("default");
|
|
||||||
#endif
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#if HAS_SCREEN
|
|
||||||
|
|
||||||
// Disable debug logging entirely on release builds of HELTEC_MESH_SOLAR for space constraints
|
|
||||||
#if defined(HELTEC_MESH_SOLAR)
|
|
||||||
#define LOG_DEBUG(...)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Enable or disable message persistence (flash storage)
|
|
||||||
// Define -DENABLE_MESSAGE_PERSISTENCE=0 in build_flags to disable it entirely
|
|
||||||
#ifndef ENABLE_MESSAGE_PERSISTENCE
|
|
||||||
#define ENABLE_MESSAGE_PERSISTENCE 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "mesh/generated/meshtastic/mesh.pb.h"
|
|
||||||
#include <cstdint>
|
|
||||||
#include <deque>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
// How many messages are stored (RAM + flash).
|
|
||||||
// Define -DMESSAGE_HISTORY_LIMIT=N in build_flags to control memory usage.
|
|
||||||
#ifndef MESSAGE_HISTORY_LIMIT
|
|
||||||
#define MESSAGE_HISTORY_LIMIT 20
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Internal alias used everywhere in code – do NOT redefine elsewhere.
|
|
||||||
#define MAX_MESSAGES_SAVED MESSAGE_HISTORY_LIMIT
|
|
||||||
|
|
||||||
// Maximum text payload size per message in bytes.
|
|
||||||
// This still defines the max message length, but we no longer reserve this space per message.
|
|
||||||
#define MAX_MESSAGE_SIZE 220
|
|
||||||
|
|
||||||
// Total shared text pool size for all messages combined.
|
|
||||||
// The text pool is RAM-only. Text is re-stored from flash into the pool on boot.
|
|
||||||
#ifndef MESSAGE_TEXT_POOL_SIZE
|
|
||||||
#define MESSAGE_TEXT_POOL_SIZE (MAX_MESSAGES_SAVED * MAX_MESSAGE_SIZE)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Explicit message classification
|
|
||||||
enum class MessageType : uint8_t {
|
|
||||||
BROADCAST = 0, // broadcast message
|
|
||||||
DM_TO_US = 1 // direct message addressed to this node
|
|
||||||
};
|
|
||||||
|
|
||||||
// Delivery status for messages we sent
|
|
||||||
enum class AckStatus : uint8_t {
|
|
||||||
NONE = 0, // just sent, waiting (no symbol shown)
|
|
||||||
ACKED = 1, // got a valid ACK from destination
|
|
||||||
NACKED = 2, // explicitly failed
|
|
||||||
TIMEOUT = 3, // no ACK after retry window
|
|
||||||
RELAYED = 4 // got an ACK from relay, not destination
|
|
||||||
};
|
|
||||||
|
|
||||||
struct StoredMessage {
|
|
||||||
uint32_t timestamp; // When message was created (secs since boot or RTC)
|
|
||||||
uint32_t sender; // NodeNum of sender
|
|
||||||
uint8_t channelIndex; // Channel index used
|
|
||||||
uint32_t dest; // Destination node (broadcast or direct)
|
|
||||||
MessageType type; // Derived from dest (explicit classification)
|
|
||||||
bool isBootRelative; // true = millis()/1000 fallback; false = epoch/RTC absolute
|
|
||||||
AckStatus ackStatus; // Delivery status (only meaningful for our own sent messages)
|
|
||||||
|
|
||||||
// Text storage metadata — rebuilt from flash at boot
|
|
||||||
uint16_t textOffset; // Offset into global text pool (valid only after loadFromFlash())
|
|
||||||
uint16_t textLength; // Length of text in bytes
|
|
||||||
|
|
||||||
// Default constructor initializes all fields safely
|
|
||||||
StoredMessage()
|
|
||||||
: timestamp(0), sender(0), channelIndex(0), dest(0xffffffff), type(MessageType::BROADCAST), isBootRelative(false),
|
|
||||||
ackStatus(AckStatus::NONE), textOffset(0), textLength(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class MessageStore
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit MessageStore(const std::string &label);
|
|
||||||
|
|
||||||
// Live RAM methods (always current, used by UI and runtime)
|
|
||||||
void addLiveMessage(StoredMessage &&msg);
|
|
||||||
void addLiveMessage(const StoredMessage &msg); // convenience overload
|
|
||||||
const std::deque<StoredMessage> &getLiveMessages() const { return liveMessages; }
|
|
||||||
|
|
||||||
// Add new messages from packets or manual input
|
|
||||||
const StoredMessage &addFromPacket(const meshtastic_MeshPacket &mp); // Incoming/outgoing → RAM only
|
|
||||||
void addFromString(uint32_t sender, uint8_t channelIndex, const std::string &text); // Manual add
|
|
||||||
|
|
||||||
// Persistence methods (used only on boot/shutdown)
|
|
||||||
void saveToFlash(); // Save messages to flash
|
|
||||||
void loadFromFlash(); // Load messages from flash
|
|
||||||
|
|
||||||
// Clear all messages (RAM + persisted queue + text pool)
|
|
||||||
void clearAllMessages();
|
|
||||||
|
|
||||||
// Delete helpers
|
|
||||||
void deleteOldestMessage(); // remove oldest from RAM (and flash on save)
|
|
||||||
void deleteOldestMessageInChannel(uint8_t channel);
|
|
||||||
void deleteOldestMessageWithPeer(uint32_t peer);
|
|
||||||
void deleteAllMessagesInChannel(uint8_t channel);
|
|
||||||
void deleteAllMessagesWithPeer(uint32_t peer);
|
|
||||||
|
|
||||||
// Unified accessor (for UI code, defaults to RAM buffer)
|
|
||||||
const std::deque<StoredMessage> &getMessages() const { return liveMessages; }
|
|
||||||
|
|
||||||
// Helper filters for future use
|
|
||||||
std::deque<StoredMessage> getChannelMessages(uint8_t channel) const; // Only broadcast messages on a channel
|
|
||||||
std::deque<StoredMessage> getDirectMessages() const; // Only direct messages
|
|
||||||
|
|
||||||
// Upgrade boot-relative timestamps once RTC is valid
|
|
||||||
void upgradeBootRelativeTimestamps();
|
|
||||||
|
|
||||||
// Retrieve the C-string text for a stored message
|
|
||||||
static const char *getText(const StoredMessage &msg);
|
|
||||||
|
|
||||||
// Allocate text into pool (used by sender-side code)
|
|
||||||
static uint16_t storeText(const char *src, size_t len);
|
|
||||||
|
|
||||||
// Used when loading from flash to rebuild the text pool
|
|
||||||
static uint16_t rebuildTextFromFlash(const char *src, size_t len);
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::deque<StoredMessage> liveMessages; // Single in-RAM message buffer (also used for persistence)
|
|
||||||
std::string filename; // Flash filename for persistence
|
|
||||||
};
|
|
||||||
|
|
||||||
// Global instance (defined in MessageStore.cpp)
|
|
||||||
extern MessageStore messageStore;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -11,7 +11,6 @@
|
|||||||
* For more information, see: https://meshtastic.org/
|
* For more information, see: https://meshtastic.org/
|
||||||
*/
|
*/
|
||||||
#include "power.h"
|
#include "power.h"
|
||||||
#include "MessageStore.h"
|
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
#include "Throttle.h"
|
#include "Throttle.h"
|
||||||
@@ -787,9 +786,7 @@ void Power::shutdown()
|
|||||||
playShutdownMelody();
|
playShutdownMelody();
|
||||||
#endif
|
#endif
|
||||||
nodeDB->saveToDisk();
|
nodeDB->saveToDisk();
|
||||||
#if HAS_SCREEN
|
|
||||||
messageStore.saveToFlash();
|
|
||||||
#endif
|
|
||||||
#if defined(ARCH_NRF52) || defined(ARCH_ESP32) || defined(ARCH_RP2040)
|
#if defined(ARCH_NRF52) || defined(ARCH_ESP32) || defined(ARCH_RP2040)
|
||||||
#ifdef PIN_LED1
|
#ifdef PIN_LED1
|
||||||
ledOff(PIN_LED1);
|
ledOff(PIN_LED1);
|
||||||
|
|||||||
@@ -131,7 +131,6 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
|
|||||||
int hour = hms / SEC_PER_HOUR;
|
int hour = hms / SEC_PER_HOUR;
|
||||||
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
||||||
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
|
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
|
||||||
|
|
||||||
#ifdef ARCH_PORTDUINO
|
#ifdef ARCH_PORTDUINO
|
||||||
::printf("%s ", logLevel);
|
::printf("%s ", logLevel);
|
||||||
if (color) {
|
if (color) {
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#if __has_include("Melopero_RV3028.h")
|
#if __has_include("Melopero_RV3028.h")
|
||||||
#include "Melopero_RV3028.h"
|
#include "Melopero_RV3028.h"
|
||||||
#endif
|
#endif
|
||||||
#if __has_include("SensorRtcHelper.hpp")
|
#if __has_include("pcf8563.h")
|
||||||
#include "SensorRtcHelper.hpp"
|
#include "pcf8563.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Offer chance for variant-specific defines */
|
/* Offer chance for variant-specific defines */
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ ScanI2C::FoundDevice ScanI2C::firstScreen() const
|
|||||||
|
|
||||||
ScanI2C::FoundDevice ScanI2C::firstRTC() const
|
ScanI2C::FoundDevice ScanI2C::firstRTC() const
|
||||||
{
|
{
|
||||||
ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563, RTC_PCF85063, RTC_RX8130CE};
|
ScanI2C::DeviceType types[] = {RTC_RV3028, RTC_PCF8563, RTC_RX8130CE};
|
||||||
return firstOfOrNONE(4, types);
|
return firstOfOrNONE(3, types);
|
||||||
}
|
}
|
||||||
|
|
||||||
ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
|
ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ class ScanI2C
|
|||||||
SCREEN_ST7567,
|
SCREEN_ST7567,
|
||||||
RTC_RV3028,
|
RTC_RV3028,
|
||||||
RTC_PCF8563,
|
RTC_PCF8563,
|
||||||
RTC_PCF85063,
|
|
||||||
RTC_RX8130CE,
|
RTC_RX8130CE,
|
||||||
CARDKB,
|
CARDKB,
|
||||||
TDECKKB,
|
TDECKKB,
|
||||||
|
|||||||
@@ -202,10 +202,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
SCAN_SIMPLE_CASE(RX8130CE_RTC, RTC_RX8130CE, "RX8130CE", (uint8_t)addr.address)
|
SCAN_SIMPLE_CASE(RX8130CE_RTC, RTC_RX8130CE, "RX8130CE", (uint8_t)addr.address)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef PCF85063_RTC
|
|
||||||
SCAN_SIMPLE_CASE(PCF85063_RTC, RTC_PCF85063, "PCF85063", (uint8_t)addr.address)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
case CARDKB_ADDR:
|
case CARDKB_ADDR:
|
||||||
// Do we have the RAK14006 instead?
|
// Do we have the RAK14006 instead?
|
||||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x04), 1);
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x04), 1);
|
||||||
|
|||||||
@@ -66,26 +66,26 @@ RTCSetResult readFromRTC()
|
|||||||
currentQuality = RTCQualityDevice;
|
currentQuality = RTCQualityDevice;
|
||||||
}
|
}
|
||||||
return RTCSetResultSuccess;
|
return RTCSetResultSuccess;
|
||||||
} else {
|
|
||||||
LOG_WARN("RTC not found (found address 0x%02X)", rtc_found.address);
|
|
||||||
}
|
}
|
||||||
#elif defined(PCF8563_RTC) || defined(PCF85063_RTC)
|
#elif defined(PCF8563_RTC)
|
||||||
#if defined(PCF8563_RTC)
|
|
||||||
if (rtc_found.address == PCF8563_RTC) {
|
if (rtc_found.address == PCF8563_RTC) {
|
||||||
#elif defined(PCF85063_RTC)
|
|
||||||
if (rtc_found.address == PCF85063_RTC) {
|
|
||||||
#endif
|
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
SensorRtcHelper rtc;
|
PCF8563_Class 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);
|
||||||
#else
|
#else
|
||||||
rtc.begin(Wire);
|
rtc.begin();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
RTC_DateTime datetime = rtc.getDateTime();
|
auto tc = rtc.getDateTime();
|
||||||
tm t = datetime.toUnixTime();
|
tm t;
|
||||||
|
t.tm_year = tc.year - 1900;
|
||||||
|
t.tm_mon = tc.month - 1;
|
||||||
|
t.tm_mday = tc.day;
|
||||||
|
t.tm_hour = tc.hour;
|
||||||
|
t.tm_min = tc.minute;
|
||||||
|
t.tm_sec = tc.second;
|
||||||
tv.tv_sec = gm_mktime(&t);
|
tv.tv_sec = gm_mktime(&t);
|
||||||
tv.tv_usec = 0;
|
tv.tv_usec = 0;
|
||||||
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
|
||||||
@@ -100,16 +100,14 @@ RTCSetResult readFromRTC()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
LOG_DEBUG("Read RTC time from %s getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", rtc.getChipName(), t.tm_year + 1900,
|
LOG_DEBUG("Read RTC time from PCF8563 getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1,
|
||||||
t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
|
t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
|
||||||
if (currentQuality == RTCQualityNone) {
|
if (currentQuality == RTCQualityNone) {
|
||||||
timeStartMsec = now;
|
timeStartMsec = now;
|
||||||
zeroOffsetSecs = tv.tv_sec;
|
zeroOffsetSecs = tv.tv_sec;
|
||||||
currentQuality = RTCQualityDevice;
|
currentQuality = RTCQualityDevice;
|
||||||
}
|
}
|
||||||
return RTCSetResultSuccess;
|
return RTCSetResultSuccess;
|
||||||
} else {
|
|
||||||
LOG_WARN("RTC not found (found address 0x%02X)", rtc_found.address);
|
|
||||||
}
|
}
|
||||||
#elif defined(RX8130CE_RTC)
|
#elif defined(RX8130CE_RTC)
|
||||||
if (rtc_found.address == RX8130CE_RTC) {
|
if (rtc_found.address == RX8130CE_RTC) {
|
||||||
@@ -234,28 +232,20 @@ RTCSetResult perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpd
|
|||||||
rtc.setTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_wday, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
|
rtc.setTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_wday, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
|
||||||
LOG_DEBUG("RV3028_RTC setTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
|
LOG_DEBUG("RV3028_RTC setTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
|
||||||
t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
|
t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
|
||||||
} else {
|
|
||||||
LOG_WARN("RTC not found (found address 0x%02X)", rtc_found.address);
|
|
||||||
}
|
}
|
||||||
#elif defined(PCF8563_RTC) || defined(PCF85063_RTC)
|
#elif defined(PCF8563_RTC)
|
||||||
#if defined(PCF8563_RTC)
|
|
||||||
if (rtc_found.address == PCF8563_RTC) {
|
if (rtc_found.address == PCF8563_RTC) {
|
||||||
#elif defined(PCF85063_RTC)
|
PCF8563_Class rtc;
|
||||||
if (rtc_found.address == PCF85063_RTC) {
|
|
||||||
#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);
|
||||||
#else
|
#else
|
||||||
rtc.begin(Wire);
|
rtc.begin();
|
||||||
#endif
|
#endif
|
||||||
tm *t = gmtime(&tv->tv_sec);
|
tm *t = gmtime(&tv->tv_sec);
|
||||||
rtc.setDateTime(*t);
|
rtc.setDateTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
|
||||||
LOG_DEBUG("%s setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", rtc.getChipName(), t->tm_year + 1900, t->tm_mon + 1,
|
LOG_DEBUG("PCF8563_RTC setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
|
||||||
t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
|
t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
|
||||||
} else {
|
|
||||||
LOG_WARN("RTC not found (found address 0x%02X)", rtc_found.address);
|
|
||||||
}
|
}
|
||||||
#elif defined(RX8130CE_RTC)
|
#elif defined(RX8130CE_RTC)
|
||||||
if (rtc_found.address == RX8130CE_RTC) {
|
if (rtc_found.address == RX8130CE_RTC) {
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#endif
|
#endif
|
||||||
#include "FSCommon.h"
|
#include "FSCommon.h"
|
||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
#include "MessageStore.h"
|
|
||||||
#include "RadioLibInterface.h"
|
#include "RadioLibInterface.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "gps/GeoCoord.h"
|
#include "gps/GeoCoord.h"
|
||||||
@@ -65,7 +64,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#include "modules/WaypointModule.h"
|
#include "modules/WaypointModule.h"
|
||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
#include "target_specific.h"
|
#include "target_specific.h"
|
||||||
extern MessageStore messageStore;
|
|
||||||
|
using graphics::Emote;
|
||||||
|
using graphics::emotes;
|
||||||
|
using graphics::numEmotes;
|
||||||
|
|
||||||
#if USE_TFTDISPLAY
|
#if USE_TFTDISPLAY
|
||||||
extern uint16_t TFT_MESH;
|
extern uint16_t TFT_MESH;
|
||||||
@@ -117,6 +119,10 @@ uint32_t dopThresholds[5] = {2000, 1000, 500, 200, 100};
|
|||||||
// we'll need to hold onto pointers for the modules that can draw a frame.
|
// we'll need to hold onto pointers for the modules that can draw a frame.
|
||||||
std::vector<MeshModule *> moduleFrames;
|
std::vector<MeshModule *> moduleFrames;
|
||||||
|
|
||||||
|
// Global variables for screen function overlay symbols
|
||||||
|
std::vector<std::string> functionSymbol;
|
||||||
|
std::string functionSymbolString;
|
||||||
|
|
||||||
#if HAS_GPS
|
#if HAS_GPS
|
||||||
// GeoCoord object for the screen
|
// GeoCoord object for the screen
|
||||||
GeoCoord geoCoord;
|
GeoCoord geoCoord;
|
||||||
@@ -257,11 +263,19 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int
|
|||||||
} else {
|
} else {
|
||||||
// otherwise, just display the module frame that's aligned with the current frame
|
// otherwise, just display the module frame that's aligned with the current frame
|
||||||
module_frame = state->currentFrame;
|
module_frame = state->currentFrame;
|
||||||
|
// LOG_DEBUG("Screen is not in transition. Frame: %d", module_frame);
|
||||||
}
|
}
|
||||||
|
// LOG_DEBUG("Draw Module Frame %d", module_frame);
|
||||||
MeshModule &pi = *moduleFrames.at(module_frame);
|
MeshModule &pi = *moduleFrames.at(module_frame);
|
||||||
pi.drawFrame(display, state, x, y);
|
pi.drawFrame(display, state, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore messages originating from phone (from the current node 0x0) unless range test or store and forward module are enabled
|
||||||
|
static bool shouldDrawMessage(const meshtastic_MeshPacket *packet)
|
||||||
|
{
|
||||||
|
return packet->from != 0 && !moduleConfig.store_forward.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a recent lat/lon return a guess of the heading the user is walking on.
|
* Given a recent lat/lon return a guess of the heading the user is walking on.
|
||||||
*
|
*
|
||||||
@@ -308,16 +322,16 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
|
|||||||
{
|
{
|
||||||
graphics::normalFrames = new FrameCallback[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
|
graphics::normalFrames = new FrameCallback[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
|
||||||
|
|
||||||
|
LOG_INFO("Protobuf Value uiconfig.screen_rgb_color: %d", uiconfig.screen_rgb_color);
|
||||||
int32_t rawRGB = uiconfig.screen_rgb_color;
|
int32_t rawRGB = uiconfig.screen_rgb_color;
|
||||||
|
|
||||||
// Only validate the combined value once
|
|
||||||
if (rawRGB > 0 && rawRGB <= 255255255) {
|
if (rawRGB > 0 && rawRGB <= 255255255) {
|
||||||
// Extract each component as a normal int first
|
uint8_t TFT_MESH_r = (rawRGB >> 16) & 0xFF;
|
||||||
int r = (rawRGB >> 16) & 0xFF;
|
uint8_t TFT_MESH_g = (rawRGB >> 8) & 0xFF;
|
||||||
int g = (rawRGB >> 8) & 0xFF;
|
uint8_t TFT_MESH_b = rawRGB & 0xFF;
|
||||||
int b = rawRGB & 0xFF;
|
LOG_INFO("Values of r,g,b: %d, %d, %d", TFT_MESH_r, TFT_MESH_g, TFT_MESH_b);
|
||||||
if (r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
|
|
||||||
TFT_MESH = COLOR565(static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b));
|
if (TFT_MESH_r <= 255 && TFT_MESH_g <= 255 && TFT_MESH_b <= 255) {
|
||||||
|
TFT_MESH = COLOR565(TFT_MESH_r, TFT_MESH_g, TFT_MESH_b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,10 +550,10 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
|
|||||||
void Screen::setup()
|
void Screen::setup()
|
||||||
{
|
{
|
||||||
|
|
||||||
// Enable display rendering
|
// === Enable display rendering ===
|
||||||
useDisplay = true;
|
useDisplay = true;
|
||||||
|
|
||||||
// Load saved brightness from UI config
|
// === Load saved brightness from UI config ===
|
||||||
// For OLED displays (SSD1306), default brightness is 255 if not set
|
// For OLED displays (SSD1306), default brightness is 255 if not set
|
||||||
if (uiconfig.screen_brightness == 0) {
|
if (uiconfig.screen_brightness == 0) {
|
||||||
#if defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
|
#if defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107)
|
||||||
@@ -551,7 +565,7 @@ void Screen::setup()
|
|||||||
brightness = uiconfig.screen_brightness;
|
brightness = uiconfig.screen_brightness;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect OLED subtype (if supported by board variant)
|
// === Detect OLED subtype (if supported by board variant) ===
|
||||||
#ifdef AutoOLEDWire_h
|
#ifdef AutoOLEDWire_h
|
||||||
if (isAUTOOled)
|
if (isAUTOOled)
|
||||||
static_cast<AutoOLEDWire *>(dispdev)->setDetected(model);
|
static_cast<AutoOLEDWire *>(dispdev)->setDetected(model);
|
||||||
@@ -573,7 +587,7 @@ void Screen::setup()
|
|||||||
static_cast<ST7796Spi *>(dispdev)->setRGB(TFT_MESH);
|
static_cast<ST7796Spi *>(dispdev)->setRGB(TFT_MESH);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Initialize display and UI system
|
// === Initialize display and UI system ===
|
||||||
ui->init();
|
ui->init();
|
||||||
displayWidth = dispdev->width();
|
displayWidth = dispdev->width();
|
||||||
displayHeight = dispdev->height();
|
displayHeight = dispdev->height();
|
||||||
@@ -585,7 +599,7 @@ void Screen::setup()
|
|||||||
ui->disableAllIndicators(); // Disable page indicator dots
|
ui->disableAllIndicators(); // Disable page indicator dots
|
||||||
ui->getUiState()->userData = this; // Allow static callbacks to access Screen instance
|
ui->getUiState()->userData = this; // Allow static callbacks to access Screen instance
|
||||||
|
|
||||||
// Apply loaded brightness
|
// === Apply loaded brightness ===
|
||||||
#if defined(ST7789_CS)
|
#if defined(ST7789_CS)
|
||||||
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
|
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
|
||||||
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SPISSD1306)
|
#elif defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SPISSD1306)
|
||||||
@@ -593,20 +607,20 @@ void Screen::setup()
|
|||||||
#endif
|
#endif
|
||||||
LOG_INFO("Applied screen brightness: %d", brightness);
|
LOG_INFO("Applied screen brightness: %d", brightness);
|
||||||
|
|
||||||
// Set custom overlay callbacks
|
// === Set custom overlay callbacks ===
|
||||||
static OverlayCallback overlays[] = {
|
static OverlayCallback overlays[] = {
|
||||||
graphics::UIRenderer::drawNavigationBar // Custom indicator icons for each frame
|
graphics::UIRenderer::drawNavigationBar // Custom indicator icons for each frame
|
||||||
};
|
};
|
||||||
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
|
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
|
||||||
|
|
||||||
// Enable UTF-8 to display mapping
|
// === Enable UTF-8 to display mapping ===
|
||||||
dispdev->setFontTableLookupFunction(customFontTableLookup);
|
dispdev->setFontTableLookupFunction(customFontTableLookup);
|
||||||
|
|
||||||
#ifdef USERPREFS_OEM_TEXT
|
#ifdef USERPREFS_OEM_TEXT
|
||||||
logo_timeout *= 2; // Give more time for branded boot logos
|
logo_timeout *= 2; // Give more time for branded boot logos
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Configure alert frames (e.g., "Resuming..." or region name)
|
// === Configure alert frames (e.g., "Resuming..." or region name) ===
|
||||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip slow refresh
|
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip slow refresh
|
||||||
alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) {
|
alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) {
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
@@ -622,10 +636,10 @@ void Screen::setup()
|
|||||||
ui->setFrames(alertFrames, 1);
|
ui->setFrames(alertFrames, 1);
|
||||||
ui->disableAutoTransition(); // Require manual navigation between frames
|
ui->disableAutoTransition(); // Require manual navigation between frames
|
||||||
|
|
||||||
// Log buffer for on-screen logs (3 lines max)
|
// === Log buffer for on-screen logs (3 lines max) ===
|
||||||
dispdev->setLogBuffer(3, 32);
|
dispdev->setLogBuffer(3, 32);
|
||||||
|
|
||||||
// Optional screen mirroring or flipping (e.g. for T-Beam orientation)
|
// === Optional screen mirroring or flipping (e.g. for T-Beam orientation) ===
|
||||||
#ifdef SCREEN_MIRROR
|
#ifdef SCREEN_MIRROR
|
||||||
dispdev->mirrorScreen();
|
dispdev->mirrorScreen();
|
||||||
#else
|
#else
|
||||||
@@ -643,7 +657,7 @@ void Screen::setup()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Generate device ID from MAC address
|
// === Generate device ID from MAC address ===
|
||||||
uint8_t dmac[6];
|
uint8_t dmac[6];
|
||||||
getMacAddr(dmac);
|
getMacAddr(dmac);
|
||||||
snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]);
|
snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]);
|
||||||
@@ -652,9 +666,9 @@ void Screen::setup()
|
|||||||
handleSetOn(false); // Ensure proper init for Arduino targets
|
handleSetOn(false); // Ensure proper init for Arduino targets
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Turn on display and trigger first draw
|
// === Turn on display and trigger first draw ===
|
||||||
handleSetOn(true);
|
handleSetOn(true);
|
||||||
graphics::currentResolution = graphics::determineScreenResolution(dispdev->height(), dispdev->width());
|
determineResolution(dispdev->height(), dispdev->width());
|
||||||
ui->update();
|
ui->update();
|
||||||
#ifndef USE_EINK
|
#ifndef USE_EINK
|
||||||
ui->update(); // Some SSD1306 clones drop the first draw, so run twice
|
ui->update(); // Some SSD1306 clones drop the first draw, so run twice
|
||||||
@@ -675,7 +689,7 @@ void Screen::setup()
|
|||||||
touchScreenImpl1->init();
|
touchScreenImpl1->init();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Subscribe to device status updates
|
// === Subscribe to device status updates ===
|
||||||
powerStatusObserver.observe(&powerStatus->onNewStatus);
|
powerStatusObserver.observe(&powerStatus->onNewStatus);
|
||||||
gpsStatusObserver.observe(&gpsStatus->onNewStatus);
|
gpsStatusObserver.observe(&gpsStatus->onNewStatus);
|
||||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||||
@@ -683,14 +697,12 @@ void Screen::setup()
|
|||||||
#if !MESHTASTIC_EXCLUDE_ADMIN
|
#if !MESHTASTIC_EXCLUDE_ADMIN
|
||||||
adminMessageObserver.observe(adminModule);
|
adminMessageObserver.observe(adminModule);
|
||||||
#endif
|
#endif
|
||||||
|
if (textMessageModule)
|
||||||
|
textMessageObserver.observe(textMessageModule);
|
||||||
if (inputBroker)
|
if (inputBroker)
|
||||||
inputObserver.observe(inputBroker);
|
inputObserver.observe(inputBroker);
|
||||||
|
|
||||||
// Load persisted messages into RAM
|
// === Notify modules that support UI events ===
|
||||||
messageStore.loadFromFlash();
|
|
||||||
LOG_INFO("MessageStore loaded from flash");
|
|
||||||
|
|
||||||
// Notify modules that support UI events
|
|
||||||
MeshModule::observeUIEvents(&uiFrameEventObserver);
|
MeshModule::observeUIEvents(&uiFrameEventObserver);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -761,23 +773,6 @@ int32_t Screen::runOnce()
|
|||||||
if (displayHeight == 0) {
|
if (displayHeight == 0) {
|
||||||
displayHeight = dispdev->getHeight();
|
displayHeight = dispdev->getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect frame transitions and clear message cache when leaving text message screen
|
|
||||||
{
|
|
||||||
static int8_t lastFrameIndex = -1;
|
|
||||||
int8_t currentFrameIndex = ui->getUiState()->currentFrame;
|
|
||||||
int8_t textMsgIndex = framesetInfo.positions.textMessage;
|
|
||||||
|
|
||||||
if (lastFrameIndex != -1 && currentFrameIndex != lastFrameIndex) {
|
|
||||||
|
|
||||||
if (lastFrameIndex == textMsgIndex && currentFrameIndex != textMsgIndex) {
|
|
||||||
graphics::MessageRenderer::clearMessageCache();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastFrameIndex = currentFrameIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
menuHandler::handleMenuSwitch(dispdev);
|
menuHandler::handleMenuSwitch(dispdev);
|
||||||
|
|
||||||
// Show boot screen for first logo_timeout seconds, then switch to normal operation.
|
// Show boot screen for first logo_timeout seconds, then switch to normal operation.
|
||||||
@@ -833,17 +828,17 @@ int32_t Screen::runOnce()
|
|||||||
break;
|
break;
|
||||||
case Cmd::ON_PRESS:
|
case Cmd::ON_PRESS:
|
||||||
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
||||||
showFrame(FrameDirection::NEXT);
|
handleOnPress();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Cmd::SHOW_PREV_FRAME:
|
case Cmd::SHOW_PREV_FRAME:
|
||||||
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
||||||
showFrame(FrameDirection::PREVIOUS);
|
handleShowPrevFrame();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Cmd::SHOW_NEXT_FRAME:
|
case Cmd::SHOW_NEXT_FRAME:
|
||||||
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
||||||
showFrame(FrameDirection::NEXT);
|
handleShowNextFrame();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Cmd::START_ALERT_FRAME: {
|
case Cmd::START_ALERT_FRAME: {
|
||||||
@@ -864,7 +859,6 @@ int32_t Screen::runOnce()
|
|||||||
break;
|
break;
|
||||||
case Cmd::STOP_ALERT_FRAME:
|
case Cmd::STOP_ALERT_FRAME:
|
||||||
NotificationRenderer::pauseBanner = false;
|
NotificationRenderer::pauseBanner = false;
|
||||||
break;
|
|
||||||
case Cmd::STOP_BOOT_SCREEN:
|
case Cmd::STOP_BOOT_SCREEN:
|
||||||
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
|
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
|
||||||
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
if (NotificationRenderer::current_notification_type != notificationTypeEnum::text_input) {
|
||||||
@@ -1035,6 +1029,9 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Declare this early so it’s available in FOCUS_PRESERVE block
|
||||||
|
bool willInsertTextMessage = shouldDrawMessage(&devicestate.rx_text_message);
|
||||||
|
|
||||||
if (!hiddenFrames.home) {
|
if (!hiddenFrames.home) {
|
||||||
fsi.positions.home = numframes;
|
fsi.positions.home = numframes;
|
||||||
normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
|
normalFrames[numframes++] = graphics::UIRenderer::drawDeviceFocused;
|
||||||
@@ -1046,16 +1043,11 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
indicatorIcons.push_back(icon_mail);
|
indicatorIcons.push_back(icon_mail);
|
||||||
|
|
||||||
#ifndef USE_EINK
|
#ifndef USE_EINK
|
||||||
if (!hiddenFrames.nodelist_nodes) {
|
if (!hiddenFrames.nodelist) {
|
||||||
fsi.positions.nodelist_nodes = numframes;
|
fsi.positions.nodelist = numframes;
|
||||||
normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicListScreen_Nodes;
|
normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicNodeListScreen;
|
||||||
indicatorIcons.push_back(icon_nodes);
|
indicatorIcons.push_back(icon_nodes);
|
||||||
}
|
}
|
||||||
if (!hiddenFrames.nodelist_location) {
|
|
||||||
fsi.positions.nodelist_location = numframes;
|
|
||||||
normalFrames[numframes++] = graphics::NodeListRenderer::drawDynamicListScreen_Location;
|
|
||||||
indicatorIcons.push_back(icon_list);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Show detailed node views only on E-Ink builds
|
// Show detailed node views only on E-Ink builds
|
||||||
@@ -1077,13 +1069,11 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if HAS_GPS
|
#if HAS_GPS
|
||||||
#ifdef USE_EINK
|
|
||||||
if (!hiddenFrames.nodelist_bearings) {
|
if (!hiddenFrames.nodelist_bearings) {
|
||||||
fsi.positions.nodelist_bearings = numframes;
|
fsi.positions.nodelist_bearings = numframes;
|
||||||
normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
|
normalFrames[numframes++] = graphics::NodeListRenderer::drawNodeListWithCompasses;
|
||||||
indicatorIcons.push_back(icon_list);
|
indicatorIcons.push_back(icon_list);
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
if (!hiddenFrames.gps) {
|
if (!hiddenFrames.gps) {
|
||||||
fsi.positions.gps = numframes;
|
fsi.positions.gps = numframes;
|
||||||
normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
|
normalFrames[numframes++] = graphics::UIRenderer::drawCompassAndLocationScreen;
|
||||||
@@ -1183,7 +1173,7 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE
|
fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE
|
||||||
this->frameCount = numframes; // Save frame count for use in custom overlay
|
this->frameCount = numframes; // ✅ Save frame count for use in custom overlay
|
||||||
LOG_DEBUG("Finished build frames. numframes: %d", numframes);
|
LOG_DEBUG("Finished build frames. numframes: %d", numframes);
|
||||||
|
|
||||||
ui->setFrames(normalFrames, numframes);
|
ui->setFrames(normalFrames, numframes);
|
||||||
@@ -1203,6 +1193,10 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
case FOCUS_FAULT:
|
case FOCUS_FAULT:
|
||||||
ui->switchToFrame(fsi.positions.fault);
|
ui->switchToFrame(fsi.positions.fault);
|
||||||
break;
|
break;
|
||||||
|
case FOCUS_TEXTMESSAGE:
|
||||||
|
hasUnreadMessage = false; // ✅ Clear when message is *viewed*
|
||||||
|
ui->switchToFrame(fsi.positions.textMessage);
|
||||||
|
break;
|
||||||
case FOCUS_MODULE:
|
case FOCUS_MODULE:
|
||||||
// Whichever frame was marked by MeshModule::requestFocus(), if any
|
// Whichever frame was marked by MeshModule::requestFocus(), if any
|
||||||
// If no module requested focus, will show the first frame instead
|
// If no module requested focus, will show the first frame instead
|
||||||
@@ -1245,11 +1239,8 @@ void Screen::setFrameImmediateDraw(FrameCallback *drawFrames)
|
|||||||
void Screen::toggleFrameVisibility(const std::string &frameName)
|
void Screen::toggleFrameVisibility(const std::string &frameName)
|
||||||
{
|
{
|
||||||
#ifndef USE_EINK
|
#ifndef USE_EINK
|
||||||
if (frameName == "nodelist_nodes") {
|
if (frameName == "nodelist") {
|
||||||
hiddenFrames.nodelist_nodes = !hiddenFrames.nodelist_nodes;
|
hiddenFrames.nodelist = !hiddenFrames.nodelist;
|
||||||
}
|
|
||||||
if (frameName == "nodelist_location") {
|
|
||||||
hiddenFrames.nodelist_location = !hiddenFrames.nodelist_location;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
@@ -1264,11 +1255,9 @@ void Screen::toggleFrameVisibility(const std::string &frameName)
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#if HAS_GPS
|
#if HAS_GPS
|
||||||
#ifdef USE_EINK
|
|
||||||
if (frameName == "nodelist_bearings") {
|
if (frameName == "nodelist_bearings") {
|
||||||
hiddenFrames.nodelist_bearings = !hiddenFrames.nodelist_bearings;
|
hiddenFrames.nodelist_bearings = !hiddenFrames.nodelist_bearings;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
if (frameName == "gps") {
|
if (frameName == "gps") {
|
||||||
hiddenFrames.gps = !hiddenFrames.gps;
|
hiddenFrames.gps = !hiddenFrames.gps;
|
||||||
}
|
}
|
||||||
@@ -1290,10 +1279,8 @@ void Screen::toggleFrameVisibility(const std::string &frameName)
|
|||||||
bool Screen::isFrameHidden(const std::string &frameName) const
|
bool Screen::isFrameHidden(const std::string &frameName) const
|
||||||
{
|
{
|
||||||
#ifndef USE_EINK
|
#ifndef USE_EINK
|
||||||
if (frameName == "nodelist_nodes")
|
if (frameName == "nodelist")
|
||||||
return hiddenFrames.nodelist_nodes;
|
return hiddenFrames.nodelist;
|
||||||
if (frameName == "nodelist_location")
|
|
||||||
return hiddenFrames.nodelist_location;
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
if (frameName == "nodelist_lastheard")
|
if (frameName == "nodelist_lastheard")
|
||||||
@@ -1304,10 +1291,8 @@ bool Screen::isFrameHidden(const std::string &frameName) const
|
|||||||
return hiddenFrames.nodelist_distance;
|
return hiddenFrames.nodelist_distance;
|
||||||
#endif
|
#endif
|
||||||
#if HAS_GPS
|
#if HAS_GPS
|
||||||
#ifdef USE_EINK
|
|
||||||
if (frameName == "nodelist_bearings")
|
if (frameName == "nodelist_bearings")
|
||||||
return hiddenFrames.nodelist_bearings;
|
return hiddenFrames.nodelist_bearings;
|
||||||
#endif
|
|
||||||
if (frameName == "gps")
|
if (frameName == "gps")
|
||||||
return hiddenFrames.gps;
|
return hiddenFrames.gps;
|
||||||
#endif
|
#endif
|
||||||
@@ -1323,6 +1308,37 @@ bool Screen::isFrameHidden(const std::string &frameName) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dismisses the currently displayed screen frame, if possible
|
||||||
|
// Relevant for text message, waypoint, others in future?
|
||||||
|
// Triggered with a CardKB keycombo
|
||||||
|
void Screen::hideCurrentFrame()
|
||||||
|
{
|
||||||
|
uint8_t currentFrame = ui->getUiState()->currentFrame;
|
||||||
|
bool dismissed = false;
|
||||||
|
if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) {
|
||||||
|
LOG_INFO("Hide Text Message");
|
||||||
|
devicestate.has_rx_text_message = false;
|
||||||
|
memset(&devicestate.rx_text_message, 0, sizeof(devicestate.rx_text_message));
|
||||||
|
} else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) {
|
||||||
|
LOG_DEBUG("Hide Waypoint");
|
||||||
|
devicestate.has_rx_waypoint = false;
|
||||||
|
hiddenFrames.waypoint = true;
|
||||||
|
dismissed = true;
|
||||||
|
} else if (currentFrame == framesetInfo.positions.wifi) {
|
||||||
|
LOG_DEBUG("Hide WiFi Screen");
|
||||||
|
hiddenFrames.wifi = true;
|
||||||
|
dismissed = true;
|
||||||
|
} else if (currentFrame == framesetInfo.positions.lora) {
|
||||||
|
LOG_INFO("Hide LoRa");
|
||||||
|
hiddenFrames.lora = true;
|
||||||
|
dismissed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dismissed) {
|
||||||
|
setFrames(FOCUS_DEFAULT); // You could also use FOCUS_PRESERVE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Screen::handleStartFirmwareUpdateScreen()
|
void Screen::handleStartFirmwareUpdateScreen()
|
||||||
{
|
{
|
||||||
LOG_DEBUG("Show firmware screen");
|
LOG_DEBUG("Show firmware screen");
|
||||||
@@ -1375,6 +1391,28 @@ void Screen::decreaseBrightness()
|
|||||||
/* TO DO: add little popup in center of screen saying what brightness level it is set to*/
|
/* TO DO: add little popup in center of screen saying what brightness level it is set to*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Screen::setFunctionSymbol(std::string sym)
|
||||||
|
{
|
||||||
|
if (std::find(functionSymbol.begin(), functionSymbol.end(), sym) == functionSymbol.end()) {
|
||||||
|
functionSymbol.push_back(sym);
|
||||||
|
functionSymbolString = "";
|
||||||
|
for (auto symbol : functionSymbol) {
|
||||||
|
functionSymbolString = symbol + " " + functionSymbolString;
|
||||||
|
}
|
||||||
|
setFastFramerate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::removeFunctionSymbol(std::string sym)
|
||||||
|
{
|
||||||
|
functionSymbol.erase(std::remove(functionSymbol.begin(), functionSymbol.end(), sym), functionSymbol.end());
|
||||||
|
functionSymbolString = "";
|
||||||
|
for (auto symbol : functionSymbol) {
|
||||||
|
functionSymbolString = symbol + " " + functionSymbolString;
|
||||||
|
}
|
||||||
|
setFastFramerate();
|
||||||
|
}
|
||||||
|
|
||||||
void Screen::handleOnPress()
|
void Screen::handleOnPress()
|
||||||
{
|
{
|
||||||
// If screen was off, just wake it, otherwise advance to next frame
|
// If screen was off, just wake it, otherwise advance to next frame
|
||||||
@@ -1386,17 +1424,23 @@ void Screen::handleOnPress()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Screen::showFrame(FrameDirection direction)
|
void Screen::handleShowPrevFrame()
|
||||||
{
|
{
|
||||||
// Only advance frames when UI is stable
|
// If screen was off, just wake it, otherwise go back to previous frame
|
||||||
|
// If we are in a transition, the press must have bounced, drop it.
|
||||||
if (ui->getUiState()->frameState == FIXED) {
|
if (ui->getUiState()->frameState == FIXED) {
|
||||||
|
ui->previousFrame();
|
||||||
|
lastScreenTransition = millis();
|
||||||
|
setFastFramerate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (direction == FrameDirection::NEXT) {
|
void Screen::handleShowNextFrame()
|
||||||
ui->nextFrame();
|
{
|
||||||
} else {
|
// If screen was off, just wake it, otherwise advance to next frame
|
||||||
ui->previousFrame();
|
// If we are in a transition, the press must have bounced, drop it.
|
||||||
}
|
if (ui->getUiState()->frameState == FIXED) {
|
||||||
|
ui->nextFrame();
|
||||||
lastScreenTransition = millis();
|
lastScreenTransition = millis();
|
||||||
setFastFramerate();
|
setFastFramerate();
|
||||||
}
|
}
|
||||||
@@ -1422,6 +1466,7 @@ void Screen::setFastFramerate()
|
|||||||
|
|
||||||
int Screen::handleStatusUpdate(const meshtastic::Status *arg)
|
int Screen::handleStatusUpdate(const meshtastic::Status *arg)
|
||||||
{
|
{
|
||||||
|
// LOG_DEBUG("Screen got status update %d", arg->getStatusType());
|
||||||
switch (arg->getStatusType()) {
|
switch (arg->getStatusType()) {
|
||||||
case STATUS_TYPE_NODE:
|
case STATUS_TYPE_NODE:
|
||||||
if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
|
if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
|
||||||
@@ -1539,11 +1584,11 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
|||||||
screen->showSimpleBanner(banner, 3000);
|
screen->showSimpleBanner(banner, 3000);
|
||||||
} else if (!channel.settings.has_module_settings || !channel.settings.module_settings.is_muted) {
|
} else if (!channel.settings.has_module_settings || !channel.settings.module_settings.is_muted) {
|
||||||
if (longName && longName[0]) {
|
if (longName && longName[0]) {
|
||||||
if (currentResolution == ScreenResolution::UltraLow) {
|
#if defined(M5STACK_UNITC6L)
|
||||||
strcpy(banner, "New Message");
|
strcpy(banner, "New Message");
|
||||||
} else {
|
#else
|
||||||
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
|
snprintf(banner, sizeof(banner), "New Message from\n%s", longName);
|
||||||
}
|
#endif
|
||||||
} else {
|
} else {
|
||||||
strcpy(banner, "New Message");
|
strcpy(banner, "New Message");
|
||||||
}
|
}
|
||||||
@@ -1579,26 +1624,16 @@ int Screen::handleUIFrameEvent(const UIFrameEvent *event)
|
|||||||
|
|
||||||
if (showingNormalScreen) {
|
if (showingNormalScreen) {
|
||||||
// Regenerate the frameset, potentially honoring a module's internal requestFocus() call
|
// Regenerate the frameset, potentially honoring a module's internal requestFocus() call
|
||||||
if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET) {
|
if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET)
|
||||||
setFrames(FOCUS_MODULE);
|
setFrames(FOCUS_MODULE);
|
||||||
}
|
|
||||||
|
|
||||||
// Regenerate the frameset, while attempting to maintain focus on the current frame
|
// Regenerate the frameset, while Attempt to maintain focus on the current frame
|
||||||
else if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND) {
|
else if (event->action == UIFrameEvent::Action::REGENERATE_FRAMESET_BACKGROUND)
|
||||||
setFrames(FOCUS_PRESERVE);
|
setFrames(FOCUS_PRESERVE);
|
||||||
}
|
|
||||||
|
|
||||||
// Don't regenerate the frameset, just re-draw whatever is on screen ASAP
|
// Don't regenerate the frameset, just re-draw whatever is on screen ASAP
|
||||||
else if (event->action == UIFrameEvent::Action::REDRAW_ONLY) {
|
else if (event->action == UIFrameEvent::Action::REDRAW_ONLY)
|
||||||
setFastFramerate();
|
setFastFramerate();
|
||||||
}
|
|
||||||
|
|
||||||
// Jump directly to the Text Message screen
|
|
||||||
else if (event->action == UIFrameEvent::Action::SWITCH_TO_TEXTMESSAGE) {
|
|
||||||
setFrames(FOCUS_PRESERVE); // preserve current frame ordering
|
|
||||||
ui->switchToFrame(framesetInfo.positions.textMessage);
|
|
||||||
setFastFramerate(); // force redraw ASAP
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1636,48 +1671,7 @@ int Screen::handleInputEvent(const InputEvent *event)
|
|||||||
menuHandler::handleMenuSwitch(dispdev);
|
menuHandler::handleMenuSwitch(dispdev);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// UP/DOWN in message screen scrolls through message threads
|
|
||||||
if (ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
|
|
||||||
|
|
||||||
if (event->inputEvent == INPUT_BROKER_UP) {
|
|
||||||
if (messageStore.getMessages().empty()) {
|
|
||||||
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
|
|
||||||
} else {
|
|
||||||
graphics::MessageRenderer::scrollUp();
|
|
||||||
setFastFramerate(); // match existing behavior
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event->inputEvent == INPUT_BROKER_DOWN) {
|
|
||||||
if (messageStore.getMessages().empty()) {
|
|
||||||
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
|
|
||||||
} else {
|
|
||||||
graphics::MessageRenderer::scrollDown();
|
|
||||||
setFastFramerate();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// UP/DOWN in node list screens scrolls through node pages
|
|
||||||
if (ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_nodes ||
|
|
||||||
ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_location ||
|
|
||||||
ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_lastheard ||
|
|
||||||
ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal ||
|
|
||||||
ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_distance ||
|
|
||||||
ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_bearings) {
|
|
||||||
if (event->inputEvent == INPUT_BROKER_UP) {
|
|
||||||
graphics::NodeListRenderer::scrollUp();
|
|
||||||
setFastFramerate();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event->inputEvent == INPUT_BROKER_DOWN) {
|
|
||||||
graphics::NodeListRenderer::scrollDown();
|
|
||||||
setFastFramerate();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Use left or right input from a keyboard to move between frames,
|
// Use left or right input from a keyboard to move between frames,
|
||||||
// so long as a mesh module isn't using these events for some other purpose
|
// so long as a mesh module isn't using these events for some other purpose
|
||||||
if (showingNormalScreen) {
|
if (showingNormalScreen) {
|
||||||
@@ -1691,39 +1685,16 @@ int Screen::handleInputEvent(const InputEvent *event)
|
|||||||
|
|
||||||
// If no modules are using the input, move between frames
|
// If no modules are using the input, move between frames
|
||||||
if (!inputIntercepted) {
|
if (!inputIntercepted) {
|
||||||
#if defined(INPUTDRIVER_ENCODER_TYPE) && INPUTDRIVER_ENCODER_TYPE == 2
|
|
||||||
bool handledEncoderScroll = false;
|
|
||||||
const bool isTextMessageFrame = (framesetInfo.positions.textMessage != 255 &&
|
|
||||||
this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage &&
|
|
||||||
!messageStore.getMessages().empty());
|
|
||||||
if (isTextMessageFrame) {
|
|
||||||
if (event->inputEvent == INPUT_BROKER_UP_LONG) {
|
|
||||||
graphics::MessageRenderer::nudgeScroll(-1);
|
|
||||||
handledEncoderScroll = true;
|
|
||||||
} else if (event->inputEvent == INPUT_BROKER_DOWN_LONG) {
|
|
||||||
graphics::MessageRenderer::nudgeScroll(1);
|
|
||||||
handledEncoderScroll = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (handledEncoderScroll) {
|
|
||||||
setFastFramerate();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_ALT_PRESS) {
|
if (event->inputEvent == INPUT_BROKER_LEFT || event->inputEvent == INPUT_BROKER_ALT_PRESS) {
|
||||||
showFrame(FrameDirection::PREVIOUS);
|
showPrevFrame();
|
||||||
} 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);
|
showNextFrame();
|
||||||
} 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();
|
||||||
} else if (event->inputEvent == INPUT_BROKER_DOWN_LONG) {
|
} else if (event->inputEvent == INPUT_BROKER_DOWN_LONG) {
|
||||||
// Long press down button for fast frame switching
|
// Long press down button for fast frame switching
|
||||||
showNextFrame();
|
showNextFrame();
|
||||||
} else if ((event->inputEvent == INPUT_BROKER_UP || event->inputEvent == INPUT_BROKER_DOWN) &&
|
|
||||||
this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
|
|
||||||
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
|
|
||||||
} else if (event->inputEvent == INPUT_BROKER_SELECT) {
|
} else if (event->inputEvent == INPUT_BROKER_SELECT) {
|
||||||
if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
|
if (this->ui->getUiState()->currentFrame == framesetInfo.positions.home) {
|
||||||
menuHandler::homeBaseMenu();
|
menuHandler::homeBaseMenu();
|
||||||
@@ -1738,21 +1709,20 @@ int Screen::handleInputEvent(const InputEvent *event)
|
|||||||
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
|
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.lora) {
|
||||||
menuHandler::loraMenu();
|
menuHandler::loraMenu();
|
||||||
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
|
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.textMessage) {
|
||||||
if (!messageStore.getMessages().empty()) {
|
if (devicestate.rx_text_message.from) {
|
||||||
menuHandler::messageResponseMenu();
|
menuHandler::messageResponseMenu();
|
||||||
} else {
|
} else {
|
||||||
if (currentResolution == ScreenResolution::UltraLow) {
|
#if defined(M5STACK_UNITC6L)
|
||||||
menuHandler::textMessageMenu();
|
menuHandler::textMessageMenu();
|
||||||
} else {
|
#else
|
||||||
menuHandler::textMessageBaseMenu();
|
menuHandler::textMessageBaseMenu();
|
||||||
}
|
#endif
|
||||||
}
|
}
|
||||||
} else if (framesetInfo.positions.firstFavorite != 255 &&
|
} else if (framesetInfo.positions.firstFavorite != 255 &&
|
||||||
this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&
|
this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&
|
||||||
this->ui->getUiState()->currentFrame <= framesetInfo.positions.lastFavorite) {
|
this->ui->getUiState()->currentFrame <= framesetInfo.positions.lastFavorite) {
|
||||||
menuHandler::favoriteBaseMenu();
|
menuHandler::favoriteBaseMenu();
|
||||||
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_nodes ||
|
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist ||
|
||||||
this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_location ||
|
|
||||||
this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_lastheard ||
|
this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_lastheard ||
|
||||||
this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal ||
|
this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_hopsignal ||
|
||||||
this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_distance ||
|
this->ui->getUiState()->currentFrame == framesetInfo.positions.nodelist_distance ||
|
||||||
@@ -1763,7 +1733,7 @@ int Screen::handleInputEvent(const InputEvent *event)
|
|||||||
menuHandler::wifiBaseMenu();
|
menuHandler::wifiBaseMenu();
|
||||||
}
|
}
|
||||||
} else if (event->inputEvent == INPUT_BROKER_BACK) {
|
} else if (event->inputEvent == INPUT_BROKER_BACK) {
|
||||||
showFrame(FrameDirection::PREVIOUS);
|
showPrevFrame();
|
||||||
} else if (event->inputEvent == INPUT_BROKER_CANCEL) {
|
} else if (event->inputEvent == INPUT_BROKER_CANCEL) {
|
||||||
setOn(false);
|
setOn(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ class Screen
|
|||||||
FOCUS_DEFAULT, // No specific frame
|
FOCUS_DEFAULT, // No specific frame
|
||||||
FOCUS_PRESERVE, // Return to the previous frame
|
FOCUS_PRESERVE, // Return to the previous frame
|
||||||
FOCUS_FAULT,
|
FOCUS_FAULT,
|
||||||
|
FOCUS_TEXTMESSAGE,
|
||||||
FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus
|
FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus
|
||||||
FOCUS_CLOCK,
|
FOCUS_CLOCK,
|
||||||
FOCUS_SYSTEM,
|
FOCUS_SYSTEM,
|
||||||
@@ -54,6 +55,8 @@ class Screen
|
|||||||
void startFirmwareUpdateScreen() {}
|
void startFirmwareUpdateScreen() {}
|
||||||
void increaseBrightness() {}
|
void increaseBrightness() {}
|
||||||
void decreaseBrightness() {}
|
void decreaseBrightness() {}
|
||||||
|
void setFunctionSymbol(std::string) {}
|
||||||
|
void removeFunctionSymbol(std::string) {}
|
||||||
void startAlert(const char *) {}
|
void startAlert(const char *) {}
|
||||||
void showSimpleBanner(const char *message, uint32_t durationMs = 0) {}
|
void showSimpleBanner(const char *message, uint32_t durationMs = 0) {}
|
||||||
void showOverlayBanner(BannerOverlayOptions) {}
|
void showOverlayBanner(BannerOverlayOptions) {}
|
||||||
@@ -169,8 +172,6 @@ class Point
|
|||||||
namespace graphics
|
namespace graphics
|
||||||
{
|
{
|
||||||
|
|
||||||
enum class FrameDirection { NEXT, PREVIOUS };
|
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
class Screen;
|
class Screen;
|
||||||
|
|
||||||
@@ -210,6 +211,8 @@ class Screen : public concurrency::OSThread
|
|||||||
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
||||||
CallbackObserver<Screen, const meshtastic::Status *> nodeStatusObserver =
|
CallbackObserver<Screen, const meshtastic::Status *> nodeStatusObserver =
|
||||||
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
CallbackObserver<Screen, const meshtastic::Status *>(this, &Screen::handleStatusUpdate);
|
||||||
|
CallbackObserver<Screen, const meshtastic_MeshPacket *> textMessageObserver =
|
||||||
|
CallbackObserver<Screen, const meshtastic_MeshPacket *>(this, &Screen::handleTextMessage);
|
||||||
CallbackObserver<Screen, const UIFrameEvent *> uiFrameEventObserver =
|
CallbackObserver<Screen, const UIFrameEvent *> uiFrameEventObserver =
|
||||||
CallbackObserver<Screen, const UIFrameEvent *>(this, &Screen::handleUIFrameEvent); // Sent by Mesh Modules
|
CallbackObserver<Screen, const UIFrameEvent *>(this, &Screen::handleUIFrameEvent); // Sent by Mesh Modules
|
||||||
CallbackObserver<Screen, const InputEvent *> inputObserver =
|
CallbackObserver<Screen, const InputEvent *> inputObserver =
|
||||||
@@ -220,10 +223,6 @@ class Screen : public concurrency::OSThread
|
|||||||
public:
|
public:
|
||||||
OLEDDisplay *getDisplayDevice() { return dispdev; }
|
OLEDDisplay *getDisplayDevice() { return dispdev; }
|
||||||
explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY);
|
explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY);
|
||||||
|
|
||||||
// Screen dimension accessors
|
|
||||||
inline int getHeight() const { return displayHeight; }
|
|
||||||
inline int getWidth() const { return displayWidth; }
|
|
||||||
size_t frameCount = 0; // Total number of active frames
|
size_t frameCount = 0; // Total number of active frames
|
||||||
~Screen();
|
~Screen();
|
||||||
|
|
||||||
@@ -232,6 +231,7 @@ class Screen : public concurrency::OSThread
|
|||||||
FOCUS_DEFAULT, // No specific frame
|
FOCUS_DEFAULT, // No specific frame
|
||||||
FOCUS_PRESERVE, // Return to the previous frame
|
FOCUS_PRESERVE, // Return to the previous frame
|
||||||
FOCUS_FAULT,
|
FOCUS_FAULT,
|
||||||
|
FOCUS_TEXTMESSAGE,
|
||||||
FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus
|
FOCUS_MODULE, // Note: target module should call requestFocus(), otherwise no info about which module to focus
|
||||||
FOCUS_CLOCK,
|
FOCUS_CLOCK,
|
||||||
FOCUS_SYSTEM,
|
FOCUS_SYSTEM,
|
||||||
@@ -279,7 +279,6 @@ class Screen : public concurrency::OSThread
|
|||||||
void onPress() { enqueueCmd(ScreenCmd{.cmd = Cmd::ON_PRESS}); }
|
void onPress() { enqueueCmd(ScreenCmd{.cmd = Cmd::ON_PRESS}); }
|
||||||
void showPrevFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_PREV_FRAME}); }
|
void showPrevFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_PREV_FRAME}); }
|
||||||
void showNextFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_NEXT_FRAME}); }
|
void showNextFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_NEXT_FRAME}); }
|
||||||
void showFrame(FrameDirection direction);
|
|
||||||
|
|
||||||
// generic alert start
|
// generic alert start
|
||||||
void startAlert(FrameCallback _alertFrame)
|
void startAlert(FrameCallback _alertFrame)
|
||||||
@@ -347,6 +346,9 @@ class Screen : public concurrency::OSThread
|
|||||||
void increaseBrightness();
|
void increaseBrightness();
|
||||||
void decreaseBrightness();
|
void decreaseBrightness();
|
||||||
|
|
||||||
|
void setFunctionSymbol(std::string sym);
|
||||||
|
void removeFunctionSymbol(std::string sym);
|
||||||
|
|
||||||
/// Stops showing the boot screen.
|
/// Stops showing the boot screen.
|
||||||
void stopBootScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BOOT_SCREEN}); }
|
void stopBootScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BOOT_SCREEN}); }
|
||||||
|
|
||||||
@@ -577,7 +579,7 @@ class Screen : public concurrency::OSThread
|
|||||||
|
|
||||||
// Handle observer events
|
// Handle observer events
|
||||||
int handleStatusUpdate(const meshtastic::Status *arg);
|
int handleStatusUpdate(const meshtastic::Status *arg);
|
||||||
int handleTextMessage(const meshtastic_MeshPacket *packet);
|
int handleTextMessage(const meshtastic_MeshPacket *arg);
|
||||||
int handleUIFrameEvent(const UIFrameEvent *arg);
|
int handleUIFrameEvent(const UIFrameEvent *arg);
|
||||||
int handleInputEvent(const InputEvent *arg);
|
int handleInputEvent(const InputEvent *arg);
|
||||||
int handleAdminMessage(AdminModule_ObserverData *arg);
|
int handleAdminMessage(AdminModule_ObserverData *arg);
|
||||||
@@ -588,6 +590,9 @@ class Screen : public concurrency::OSThread
|
|||||||
/// Draws our SSL cert screen during boot (called from WebServer)
|
/// Draws our SSL cert screen during boot (called from WebServer)
|
||||||
void setSSLFrames();
|
void setSSLFrames();
|
||||||
|
|
||||||
|
// Dismiss the currently focussed frame, if possible (e.g. text message, waypoint)
|
||||||
|
void hideCurrentFrame();
|
||||||
|
|
||||||
// Menu-driven Show / Hide Toggle
|
// Menu-driven Show / Hide Toggle
|
||||||
void toggleFrameVisibility(const std::string &frameName);
|
void toggleFrameVisibility(const std::string &frameName);
|
||||||
bool isFrameHidden(const std::string &frameName) const;
|
bool isFrameHidden(const std::string &frameName) const;
|
||||||
@@ -635,6 +640,8 @@ class Screen : public concurrency::OSThread
|
|||||||
// Implementations of various commands, called from doTask().
|
// Implementations of various commands, called from doTask().
|
||||||
void handleSetOn(bool on, FrameCallback einkScreensaver = NULL);
|
void handleSetOn(bool on, FrameCallback einkScreensaver = NULL);
|
||||||
void handleOnPress();
|
void handleOnPress();
|
||||||
|
void handleShowNextFrame();
|
||||||
|
void handleShowPrevFrame();
|
||||||
void handleStartFirmwareUpdateScreen();
|
void handleStartFirmwareUpdateScreen();
|
||||||
|
|
||||||
// Info collected by setFrames method.
|
// Info collected by setFrames method.
|
||||||
@@ -654,8 +661,7 @@ class Screen : public concurrency::OSThread
|
|||||||
uint8_t gps = 255;
|
uint8_t gps = 255;
|
||||||
uint8_t home = 255;
|
uint8_t home = 255;
|
||||||
uint8_t textMessage = 255;
|
uint8_t textMessage = 255;
|
||||||
uint8_t nodelist_nodes = 255;
|
uint8_t nodelist = 255;
|
||||||
uint8_t nodelist_location = 255;
|
|
||||||
uint8_t nodelist_lastheard = 255;
|
uint8_t nodelist_lastheard = 255;
|
||||||
uint8_t nodelist_hopsignal = 255;
|
uint8_t nodelist_hopsignal = 255;
|
||||||
uint8_t nodelist_distance = 255;
|
uint8_t nodelist_distance = 255;
|
||||||
@@ -678,8 +684,7 @@ class Screen : public concurrency::OSThread
|
|||||||
bool home = false;
|
bool home = false;
|
||||||
bool clock = false;
|
bool clock = false;
|
||||||
#ifndef USE_EINK
|
#ifndef USE_EINK
|
||||||
bool nodelist_nodes = false;
|
bool nodelist = false;
|
||||||
bool nodelist_location = false;
|
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
bool nodelist_lastheard = false;
|
bool nodelist_lastheard = false;
|
||||||
@@ -687,9 +692,7 @@ class Screen : public concurrency::OSThread
|
|||||||
bool nodelist_distance = false;
|
bool nodelist_distance = false;
|
||||||
#endif
|
#endif
|
||||||
#if HAS_GPS
|
#if HAS_GPS
|
||||||
#ifdef USE_EINK
|
|
||||||
bool nodelist_bearings = false;
|
bool nodelist_bearings = false;
|
||||||
#endif
|
|
||||||
bool gps = false;
|
bool gps = false;
|
||||||
#endif
|
#endif
|
||||||
bool lora = false;
|
bool lora = false;
|
||||||
|
|||||||
@@ -15,49 +15,27 @@
|
|||||||
namespace graphics
|
namespace graphics
|
||||||
{
|
{
|
||||||
|
|
||||||
ScreenResolution determineScreenResolution(int16_t screenheight, int16_t screenwidth)
|
void determineResolution(int16_t screenheight, int16_t screenwidth)
|
||||||
{
|
{
|
||||||
|
|
||||||
#ifdef FORCE_LOW_RES
|
#ifdef FORCE_LOW_RES
|
||||||
return ScreenResolution::Low;
|
isHighResolution = false;
|
||||||
#else
|
return;
|
||||||
// Unit C6L and other ultra low res screens
|
|
||||||
if (screenwidth <= 64 || screenheight <= 48) {
|
|
||||||
return ScreenResolution::UltraLow;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Standard OLED screens
|
|
||||||
if (screenwidth > 128 && screenheight <= 64) {
|
|
||||||
return ScreenResolution::Low;
|
|
||||||
}
|
|
||||||
|
|
||||||
// High Resolutions screens like T114, TDeck, TLora Pager, etc
|
|
||||||
if (screenwidth > 128) {
|
|
||||||
return ScreenResolution::High;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default to low resolution
|
|
||||||
return ScreenResolution::Low;
|
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
void decomposeTime(uint32_t rtc_sec, int &hour, int &minute, int &second)
|
if (screenwidth > 128) {
|
||||||
{
|
isHighResolution = true;
|
||||||
hour = 0;
|
}
|
||||||
minute = 0;
|
|
||||||
second = 0;
|
if (screenwidth > 128 && screenheight <= 64) {
|
||||||
if (rtc_sec == 0)
|
isHighResolution = false;
|
||||||
return;
|
}
|
||||||
uint32_t hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY;
|
|
||||||
hour = hms / SEC_PER_HOUR;
|
|
||||||
minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
|
||||||
second = hms % SEC_PER_MIN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Shared External State ===
|
// === Shared External State ===
|
||||||
bool hasUnreadMessage = false;
|
bool hasUnreadMessage = false;
|
||||||
bool isMuted = false;
|
bool isMuted = false;
|
||||||
ScreenResolution currentResolution = ScreenResolution::Low;
|
bool isHighResolution = false;
|
||||||
|
|
||||||
// === Internal State ===
|
// === Internal State ===
|
||||||
bool isBoltVisibleShared = true;
|
bool isBoltVisibleShared = true;
|
||||||
@@ -113,7 +91,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
display->setColor(BLACK);
|
display->setColor(BLACK);
|
||||||
display->fillRect(0, 0, screenW, highlightHeight + 2);
|
display->fillRect(0, 0, screenW, highlightHeight + 2);
|
||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
display->drawLine(0, 20, screenW, 20);
|
display->drawLine(0, 20, screenW, 20);
|
||||||
} else {
|
} else {
|
||||||
display->drawLine(0, 14, screenW, 14);
|
display->drawLine(0, 14, screenW, 14);
|
||||||
@@ -151,7 +129,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool useHorizontalBattery = (currentResolution == ScreenResolution::High && screenW >= screenH);
|
bool useHorizontalBattery = (isHighResolution && screenW >= screenH);
|
||||||
const int textY = y + (highlightHeight - FONT_HEIGHT_SMALL) / 2;
|
const int textY = y + (highlightHeight - FONT_HEIGHT_SMALL) / 2;
|
||||||
|
|
||||||
int batteryX = 1;
|
int batteryX = 1;
|
||||||
@@ -161,7 +139,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging
|
if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging
|
||||||
batteryX += 1;
|
batteryX += 1;
|
||||||
batteryY += 2;
|
batteryY += 2;
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
display->drawXbm(batteryX, batteryY, 19, 12, imgUSB_HighResolution);
|
display->drawXbm(batteryX, batteryY, 19, 12, imgUSB_HighResolution);
|
||||||
batteryX += 20; // Icon + 1 pixel
|
batteryX += 20; // Icon + 1 pixel
|
||||||
} else {
|
} else {
|
||||||
@@ -222,8 +200,8 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
if (rtc_sec > 0) {
|
if (rtc_sec > 0) {
|
||||||
// === Build Time String ===
|
// === Build Time String ===
|
||||||
long hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY;
|
long hms = (rtc_sec % SEC_PER_DAY + SEC_PER_DAY) % SEC_PER_DAY;
|
||||||
int hour, minute, second;
|
int hour = hms / SEC_PER_HOUR;
|
||||||
graphics::decomposeTime(rtc_sec, hour, minute, second);
|
int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
||||||
snprintf(timeStr, sizeof(timeStr), "%d:%02d", hour, minute);
|
snprintf(timeStr, sizeof(timeStr), "%d:%02d", hour, minute);
|
||||||
|
|
||||||
// === Build Date String ===
|
// === Build Date String ===
|
||||||
@@ -231,7 +209,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, false);
|
UIRenderer::formatDateTime(datetimeStr, sizeof(datetimeStr), rtc_sec, display, false);
|
||||||
char dateLine[40];
|
char dateLine[40];
|
||||||
|
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
snprintf(dateLine, sizeof(dateLine), "%s", datetimeStr);
|
snprintf(dateLine, sizeof(dateLine), "%s", datetimeStr);
|
||||||
} else {
|
} else {
|
||||||
if (hasUnreadMessage) {
|
if (hasUnreadMessage) {
|
||||||
@@ -307,7 +285,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
display->drawXbm(iconX, iconY, mail_width, mail_height, mail);
|
display->drawXbm(iconX, iconY, mail_width, mail_height, mail);
|
||||||
}
|
}
|
||||||
} else if (isMuted) {
|
} else if (isMuted) {
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
int iconX = iconRightEdge - mute_symbol_big_width;
|
int iconX = iconRightEdge - mute_symbol_big_width;
|
||||||
int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2;
|
int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2;
|
||||||
|
|
||||||
@@ -384,7 +362,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
|
|||||||
display->drawXbm(iconX, iconY, mail_width, mail_height, mail);
|
display->drawXbm(iconX, iconY, mail_width, mail_height, mail);
|
||||||
}
|
}
|
||||||
} else if (isMuted) {
|
} else if (isMuted) {
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
int iconX = iconRightEdge - mute_symbol_big_width;
|
int iconX = iconRightEdge - mute_symbol_big_width;
|
||||||
int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2;
|
int iconY = textY + (FONT_HEIGHT_SMALL - mute_symbol_big_height) / 2;
|
||||||
display->drawXbm(iconX, iconY, mute_symbol_big_width, mute_symbol_big_height, mute_symbol_big);
|
display->drawXbm(iconX, iconY, mute_symbol_big_width, mute_symbol_big_height, mute_symbol_big);
|
||||||
@@ -403,7 +381,7 @@ const int *getTextPositions(OLEDDisplay *display)
|
|||||||
{
|
{
|
||||||
static int textPositions[7]; // Static array that persists beyond function scope
|
static int textPositions[7]; // Static array that persists beyond function scope
|
||||||
|
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
textPositions[0] = textZeroLine;
|
textPositions[0] = textZeroLine;
|
||||||
textPositions[1] = textFirstLine_medium;
|
textPositions[1] = textFirstLine_medium;
|
||||||
textPositions[2] = textSecondLine_medium;
|
textPositions[2] = textSecondLine_medium;
|
||||||
@@ -436,12 +414,8 @@ void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (drawConnectionState) {
|
if (drawConnectionState) {
|
||||||
const int scale = (currentResolution == ScreenResolution::High) ? 2 : 1;
|
if (isHighResolution) {
|
||||||
display->setColor(BLACK);
|
const int scale = 2;
|
||||||
display->fillRect(0, SCREEN_HEIGHT - (1 * scale) - (connection_icon_height * scale), (connection_icon_width * scale),
|
|
||||||
(connection_icon_height * scale) + (2 * scale));
|
|
||||||
display->setColor(WHITE);
|
|
||||||
if (currentResolution == ScreenResolution::High) {
|
|
||||||
const int bytesPerRow = (connection_icon_width + 7) / 8;
|
const int bytesPerRow = (connection_icon_width + 7) / 8;
|
||||||
int iconX = 0;
|
int iconX = 0;
|
||||||
int iconY = SCREEN_HEIGHT - (connection_icon_height * 2);
|
int iconY = SCREEN_HEIGHT - (connection_icon_height * 2);
|
||||||
|
|||||||
@@ -42,11 +42,8 @@ namespace graphics
|
|||||||
// Shared state (declare inside namespace)
|
// Shared state (declare inside namespace)
|
||||||
extern bool hasUnreadMessage;
|
extern bool hasUnreadMessage;
|
||||||
extern bool isMuted;
|
extern bool isMuted;
|
||||||
enum class ScreenResolution : uint8_t { UltraLow = 0, Low = 1, High = 2 };
|
extern bool isHighResolution;
|
||||||
extern ScreenResolution currentResolution;
|
void determineResolution(int16_t screenheight, int16_t screenwidth);
|
||||||
ScreenResolution determineScreenResolution(int16_t screenheight, int16_t screenwidth);
|
|
||||||
|
|
||||||
void decomposeTime(uint32_t rtc_sec, int &hour, int &minute, int &second);
|
|
||||||
|
|
||||||
// Rounded highlight (used for inverted headers)
|
// Rounded highlight (used for inverted headers)
|
||||||
void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w, int16_t h, int16_t r);
|
void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w, int16_t h, int16_t r);
|
||||||
|
|||||||
@@ -354,6 +354,8 @@ void VirtualKeyboard::drawInputArea(OLEDDisplay *display, int16_t offsetX, int16
|
|||||||
if (screenHeight <= 64) {
|
if (screenHeight <= 64) {
|
||||||
textY = boxY + (boxHeight - inputLineH) / 2;
|
textY = boxY + (boxHeight - inputLineH) / 2;
|
||||||
} else {
|
} else {
|
||||||
|
const int innerLeft = boxX + 1;
|
||||||
|
const int innerRight = boxX + boxWidth - 2;
|
||||||
const int innerTop = boxY + 1;
|
const int innerTop = boxY + 1;
|
||||||
const int innerBottom = boxY + boxHeight - 2;
|
const int innerBottom = boxY + boxHeight - 2;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#if HAS_SCREEN
|
#if HAS_SCREEN
|
||||||
#include "ClockRenderer.h"
|
#include "ClockRenderer.h"
|
||||||
|
#include "NodeDB.h"
|
||||||
|
#include "UIRenderer.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
#include "gps/GeoCoord.h"
|
||||||
#include "gps/RTC.h"
|
#include "gps/RTC.h"
|
||||||
#include "graphics/ScreenFonts.h"
|
#include "graphics/ScreenFonts.h"
|
||||||
#include "graphics/SharedUIDisplay.h"
|
#include "graphics/SharedUIDisplay.h"
|
||||||
#include "graphics/draw/UIRenderer.h"
|
#include "graphics/draw/UIRenderer.h"
|
||||||
|
#include "graphics/emotes.h"
|
||||||
#include "graphics/images.h"
|
#include "graphics/images.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
|
||||||
@@ -18,31 +23,6 @@ namespace graphics
|
|||||||
namespace ClockRenderer
|
namespace ClockRenderer
|
||||||
{
|
{
|
||||||
|
|
||||||
// Segment bitmaps for numerals 0-9 stored in flash to save RAM.
|
|
||||||
// Each row is a digit, each column is a segment state (1 = on, 0 = off).
|
|
||||||
// Segment layout reference:
|
|
||||||
//
|
|
||||||
// ___1___
|
|
||||||
// 6 | | 2
|
|
||||||
// |_7___|
|
|
||||||
// 5 | | 3
|
|
||||||
// |___4_|
|
|
||||||
//
|
|
||||||
// Segment order: [1, 2, 3, 4, 5, 6, 7]
|
|
||||||
//
|
|
||||||
static const uint8_t PROGMEM digitSegments[10][7] = {
|
|
||||||
{1, 1, 1, 1, 1, 1, 0}, // 0
|
|
||||||
{0, 1, 1, 0, 0, 0, 0}, // 1
|
|
||||||
{1, 1, 0, 1, 1, 0, 1}, // 2
|
|
||||||
{1, 1, 1, 1, 0, 0, 1}, // 3
|
|
||||||
{0, 1, 1, 0, 0, 1, 1}, // 4
|
|
||||||
{1, 0, 1, 1, 0, 1, 1}, // 5
|
|
||||||
{1, 0, 1, 1, 1, 1, 1}, // 6
|
|
||||||
{1, 1, 1, 0, 0, 1, 0}, // 7
|
|
||||||
{1, 1, 1, 1, 1, 1, 1}, // 8
|
|
||||||
{1, 1, 1, 1, 0, 1, 1} // 9
|
|
||||||
};
|
|
||||||
|
|
||||||
void drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale)
|
void drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale)
|
||||||
{
|
{
|
||||||
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
|
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
|
||||||
@@ -50,7 +30,7 @@ void drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale)
|
|||||||
|
|
||||||
uint16_t cellHeight = (segmentWidth * 2) + (segmentHeight * 3) + 8;
|
uint16_t cellHeight = (segmentWidth * 2) + (segmentHeight * 3) + 8;
|
||||||
|
|
||||||
uint16_t topAndBottomX = x + static_cast<uint16_t>(4 * scale);
|
uint16_t topAndBottomX = x + (4 * scale);
|
||||||
|
|
||||||
uint16_t quarterCellHeight = cellHeight / 4;
|
uint16_t quarterCellHeight = cellHeight / 4;
|
||||||
|
|
||||||
@@ -63,16 +43,34 @@ void drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale)
|
|||||||
|
|
||||||
void drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale)
|
void drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale)
|
||||||
{
|
{
|
||||||
// Read 7-segment pattern for the digit from flash
|
// the numbers 0-9, each expressed as an array of seven boolean (0|1) values encoding the on/off state of
|
||||||
uint8_t seg[7];
|
// segment {innerIndex + 1}
|
||||||
for (uint8_t i = 0; i < 7; i++) {
|
// e.g., to display the numeral '0', segments 1-6 are on, and segment 7 is off.
|
||||||
seg[i] = pgm_read_byte(&digitSegments[number][i]);
|
uint8_t numbers[10][7] = {
|
||||||
}
|
{1, 1, 1, 1, 1, 1, 0}, // 0 Display segment key
|
||||||
|
{0, 1, 1, 0, 0, 0, 0}, // 1 1
|
||||||
|
{1, 1, 0, 1, 1, 0, 1}, // 2 ___
|
||||||
|
{1, 1, 1, 1, 0, 0, 1}, // 3 6 | | 2
|
||||||
|
{0, 1, 1, 0, 0, 1, 1}, // 4 |_7̲_|
|
||||||
|
{1, 0, 1, 1, 0, 1, 1}, // 5 5 | | 3
|
||||||
|
{1, 0, 1, 1, 1, 1, 1}, // 6 |___|
|
||||||
|
{1, 1, 1, 0, 0, 1, 0}, // 7
|
||||||
|
{1, 1, 1, 1, 1, 1, 1}, // 8 4
|
||||||
|
{1, 1, 1, 1, 0, 1, 1}, // 9
|
||||||
|
};
|
||||||
|
|
||||||
|
// the width and height of each segment's central rectangle:
|
||||||
|
// _____________________
|
||||||
|
// ⋰| (only this part, |⋱
|
||||||
|
// ⋰ | not including | ⋱
|
||||||
|
// ⋱ | the triangles | ⋰
|
||||||
|
// ⋱| on the ends) |⋰
|
||||||
|
// ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
|
||||||
|
|
||||||
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
|
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
|
||||||
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
|
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
|
||||||
|
|
||||||
// Precompute segment positions
|
// segment x and y coordinates
|
||||||
uint16_t segmentOneX = x + segmentHeight + 2;
|
uint16_t segmentOneX = x + segmentHeight + 2;
|
||||||
uint16_t segmentOneY = y;
|
uint16_t segmentOneY = y;
|
||||||
|
|
||||||
@@ -94,21 +92,33 @@ void drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t n
|
|||||||
uint16_t segmentSevenX = segmentOneX;
|
uint16_t segmentSevenX = segmentOneX;
|
||||||
uint16_t segmentSevenY = segmentTwoY + segmentWidth + 2;
|
uint16_t segmentSevenY = segmentTwoY + segmentWidth + 2;
|
||||||
|
|
||||||
// Draw only the active segments
|
if (numbers[number][0]) {
|
||||||
if (seg[0])
|
graphics::ClockRenderer::drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight);
|
||||||
drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight);
|
}
|
||||||
if (seg[1])
|
|
||||||
drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight);
|
if (numbers[number][1]) {
|
||||||
if (seg[2])
|
graphics::ClockRenderer::drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight);
|
||||||
drawVerticalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight);
|
}
|
||||||
if (seg[3])
|
|
||||||
drawHorizontalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight);
|
if (numbers[number][2]) {
|
||||||
if (seg[4])
|
graphics::ClockRenderer::drawVerticalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight);
|
||||||
drawVerticalSegment(display, segmentFiveX, segmentFiveY, segmentWidth, segmentHeight);
|
}
|
||||||
if (seg[5])
|
|
||||||
drawVerticalSegment(display, segmentSixX, segmentSixY, segmentWidth, segmentHeight);
|
if (numbers[number][3]) {
|
||||||
if (seg[6])
|
graphics::ClockRenderer::drawHorizontalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight);
|
||||||
drawHorizontalSegment(display, segmentSevenX, segmentSevenY, segmentWidth, segmentHeight);
|
}
|
||||||
|
|
||||||
|
if (numbers[number][4]) {
|
||||||
|
graphics::ClockRenderer::drawVerticalSegment(display, segmentFiveX, segmentFiveY, segmentWidth, segmentHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numbers[number][5]) {
|
||||||
|
graphics::ClockRenderer::drawVerticalSegment(display, segmentSixX, segmentSixY, segmentWidth, segmentHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numbers[number][6]) {
|
||||||
|
graphics::ClockRenderer::drawHorizontalSegment(display, segmentSevenX, segmentSevenY, segmentWidth, segmentHeight);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height)
|
void drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height)
|
||||||
@@ -137,6 +147,42 @@ void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int heig
|
|||||||
display->fillTriangle(x, y + width, x + height - 1, y + width, x + halfHeight, y + width + halfHeight);
|
display->fillTriangle(x, y + width, x + height - 1, y + width, x + halfHeight, y + width + halfHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode, float scale)
|
||||||
|
{
|
||||||
|
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
|
||||||
|
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
|
||||||
|
|
||||||
|
if (digitalMode) {
|
||||||
|
uint16_t radius = (segmentWidth + (segmentHeight * 2) + 4) / 2;
|
||||||
|
uint16_t centerX = (x + segmentHeight + 2) + (radius / 2);
|
||||||
|
uint16_t centerY = (y + segmentHeight + 2) + (radius / 2);
|
||||||
|
|
||||||
|
display->drawCircle(centerX, centerY, radius);
|
||||||
|
display->drawCircle(centerX, centerY, radius + 1);
|
||||||
|
display->drawLine(centerX, centerY, centerX, centerY - radius + 3);
|
||||||
|
display->drawLine(centerX, centerY, centerX + radius - 3, centerY);
|
||||||
|
} else {
|
||||||
|
uint16_t segmentOneX = x + segmentHeight + 2;
|
||||||
|
uint16_t segmentOneY = y;
|
||||||
|
|
||||||
|
uint16_t segmentTwoX = segmentOneX + segmentWidth + 2;
|
||||||
|
uint16_t segmentTwoY = segmentOneY + segmentHeight + 2;
|
||||||
|
|
||||||
|
uint16_t segmentThreeX = segmentOneX;
|
||||||
|
uint16_t segmentThreeY = segmentTwoY + segmentWidth + 2;
|
||||||
|
|
||||||
|
uint16_t segmentFourX = x;
|
||||||
|
uint16_t segmentFourY = y + segmentHeight + 2;
|
||||||
|
|
||||||
|
drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight);
|
||||||
|
drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight);
|
||||||
|
drawHorizontalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight);
|
||||||
|
drawVerticalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// Draw a digital clock
|
||||||
void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
display->clear();
|
display->clear();
|
||||||
@@ -146,6 +192,7 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
const char *titleStr = "";
|
const char *titleStr = "";
|
||||||
// === Header ===
|
// === Header ===
|
||||||
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
|
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
|
||||||
|
int line = 0;
|
||||||
|
|
||||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
|
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
|
||||||
char timeString[16];
|
char timeString[16];
|
||||||
@@ -190,7 +237,7 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
float target_width = display->getWidth() * screenwidth_target_ratio;
|
float target_width = display->getWidth() * screenwidth_target_ratio;
|
||||||
float target_height =
|
float target_height =
|
||||||
display->getHeight() -
|
display->getHeight() -
|
||||||
((currentResolution == ScreenResolution::High)
|
(isHighResolution
|
||||||
? 46
|
? 46
|
||||||
: 33); // Be careful adjusting this number, we have to account for header and the text under the time
|
: 33); // Be careful adjusting this number, we have to account for header and the text under the time
|
||||||
|
|
||||||
@@ -221,9 +268,10 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
scaleInitialized = true;
|
scaleInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate hours:minutes string width
|
|
||||||
size_t len = strlen(timeString);
|
size_t len = strlen(timeString);
|
||||||
uint16_t timeStringWidth = len * 5;
|
|
||||||
|
// calculate hours:minutes string width
|
||||||
|
uint16_t timeStringWidth = len * 5; // base spacing between characters
|
||||||
|
|
||||||
for (size_t i = 0; i < len; i++) {
|
for (size_t i = 0; i < len; i++) {
|
||||||
char character = timeString[i];
|
char character = timeString[i];
|
||||||
@@ -262,16 +310,9 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
|
|
||||||
// draw seconds string + AM/PM
|
// draw seconds string + AM/PM
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
int xOffset = -1;
|
int xOffset = (isHighResolution) ? 0 : -1;
|
||||||
if (currentResolution == ScreenResolution::High) {
|
|
||||||
xOffset = 0;
|
|
||||||
}
|
|
||||||
if (hour >= 10) {
|
if (hour >= 10) {
|
||||||
if (currentResolution == ScreenResolution::High) {
|
xOffset += (isHighResolution) ? 32 : 18;
|
||||||
xOffset += 32;
|
|
||||||
} else {
|
|
||||||
xOffset += 18;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.display.use_12h_clock) {
|
if (config.display.use_12h_clock) {
|
||||||
@@ -279,7 +320,7 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifndef USE_EINK
|
#ifndef USE_EINK
|
||||||
xOffset = (currentResolution == ScreenResolution::High) ? 18 : 10;
|
xOffset = (isHighResolution) ? 18 : 10;
|
||||||
if (scale >= 2.0f) {
|
if (scale >= 2.0f) {
|
||||||
xOffset -= (int)(4.5f * scale);
|
xOffset -= (int)(4.5f * scale);
|
||||||
}
|
}
|
||||||
@@ -298,13 +339,19 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
const char *titleStr = "";
|
const char *titleStr = "";
|
||||||
// === Header ===
|
// === Header ===
|
||||||
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
|
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
|
||||||
|
int line = 0;
|
||||||
|
|
||||||
// clock face center coordinates
|
// clock face center coordinates
|
||||||
int16_t centerX = display->getWidth() / 2;
|
int16_t centerX = display->getWidth() / 2;
|
||||||
int16_t centerY = display->getHeight() / 2;
|
int16_t centerY = display->getHeight() / 2;
|
||||||
|
|
||||||
// clock face radius
|
// clock face radius
|
||||||
int16_t radius = (std::min(display->getWidth(), display->getHeight()) / 2) * 0.9;
|
int16_t radius = 0;
|
||||||
|
if (display->getHeight() < display->getWidth()) {
|
||||||
|
radius = (display->getHeight() / 2) * 0.9;
|
||||||
|
} else {
|
||||||
|
radius = (display->getWidth() / 2) * 0.9;
|
||||||
|
}
|
||||||
#ifdef T_WATCH_S3
|
#ifdef T_WATCH_S3
|
||||||
radius = (display->getWidth() / 2) * 0.8;
|
radius = (display->getWidth() / 2) * 0.8;
|
||||||
#endif
|
#endif
|
||||||
@@ -319,8 +366,17 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
// tick mark outer y coordinate; (first nested circle)
|
// tick mark outer y coordinate; (first nested circle)
|
||||||
int16_t tickMarkOuterNoonY = secondHandNoonY;
|
int16_t tickMarkOuterNoonY = secondHandNoonY;
|
||||||
|
|
||||||
double secondsTickMarkInnerNoonY = noonY + ((currentResolution == ScreenResolution::High) ? 8 : 4);
|
// seconds tick mark inner y coordinate; (second nested circle)
|
||||||
double hoursTickMarkInnerNoonY = noonY + ((currentResolution == ScreenResolution::High) ? 16 : 6);
|
double secondsTickMarkInnerNoonY = (double)noonY + 4;
|
||||||
|
if (isHighResolution) {
|
||||||
|
secondsTickMarkInnerNoonY = (double)noonY + 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// hours tick mark inner y coordinate; (third nested circle)
|
||||||
|
double hoursTickMarkInnerNoonY = (double)noonY + 6;
|
||||||
|
if (isHighResolution) {
|
||||||
|
hoursTickMarkInnerNoonY = (double)noonY + 16;
|
||||||
|
}
|
||||||
|
|
||||||
// minute hand y coordinate
|
// minute hand y coordinate
|
||||||
int16_t minuteHandNoonY = secondsTickMarkInnerNoonY + 4;
|
int16_t minuteHandNoonY = secondsTickMarkInnerNoonY + 4;
|
||||||
@@ -330,7 +386,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
|
|
||||||
// hour hand radius and y coordinate
|
// hour hand radius and y coordinate
|
||||||
int16_t hourHandRadius = radius * 0.35;
|
int16_t hourHandRadius = radius * 0.35;
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
hourHandRadius = radius * 0.55;
|
hourHandRadius = radius * 0.55;
|
||||||
}
|
}
|
||||||
int16_t hourHandNoonY = centerY - hourHandRadius;
|
int16_t hourHandNoonY = centerY - hourHandRadius;
|
||||||
@@ -340,13 +396,19 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
|
|
||||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
|
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
|
||||||
if (rtc_sec > 0) {
|
if (rtc_sec > 0) {
|
||||||
int hour, minute, second;
|
long hms = rtc_sec % SEC_PER_DAY;
|
||||||
decomposeTime(rtc_sec, hour, minute, second);
|
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
|
||||||
|
|
||||||
|
// Tear apart hms into h:m:s
|
||||||
|
int hour = hms / SEC_PER_HOUR;
|
||||||
|
int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
||||||
|
int second = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
|
||||||
|
|
||||||
|
bool isPM = hour >= 12;
|
||||||
if (config.display.use_12h_clock) {
|
if (config.display.use_12h_clock) {
|
||||||
bool isPM = hour >= 12;
|
isPM = hour >= 12;
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
int yOffset = (currentResolution == ScreenResolution::High) ? 1 : 0;
|
int yOffset = isHighResolution ? 1 : 0;
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
yOffset += 3;
|
yOffset += 3;
|
||||||
#endif
|
#endif
|
||||||
@@ -437,13 +499,12 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
|
display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
|
||||||
#else
|
#else
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
// draw hour number
|
// draw hour number
|
||||||
display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
|
display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (currentResolution == ScreenResolution::High &&
|
if (isHighResolution && (hourInt == 3 || hourInt == 6 || hourInt == 9 || hourInt == 12)) {
|
||||||
(hourInt == 3 || hourInt == 6 || hourInt == 9 || hourInt == 12)) {
|
|
||||||
// draw hour number
|
// draw hour number
|
||||||
display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
|
display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
|
||||||
}
|
}
|
||||||
@@ -455,7 +516,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
double startX = sineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + noonX;
|
double startX = sineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + noonX;
|
||||||
double startY = cosineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + centerY;
|
double startY = cosineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + centerY;
|
||||||
|
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
// draw minute tick mark
|
// draw minute tick mark
|
||||||
display->drawLine(startX, startY, endX, endY);
|
display->drawLine(startX, startY, endX, endY);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY,
|
|||||||
// This could draw a "N" indicator or north arrow
|
// This could draw a "N" indicator or north arrow
|
||||||
// For now, we'll draw a simple north indicator
|
// For now, we'll draw a simple north indicator
|
||||||
// const float radius = 17.0f;
|
// const float radius = 17.0f;
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
radius += 4;
|
radius += 4;
|
||||||
}
|
}
|
||||||
Point north(0, -radius);
|
Point north(0, -radius);
|
||||||
@@ -59,7 +59,7 @@ void drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t compassY,
|
|||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||||
display->setColor(BLACK);
|
display->setColor(BLACK);
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
display->fillRect(north.x - 8, north.y - 1, display->getStringWidth("N") + 3, FONT_HEIGHT_SMALL - 6);
|
display->fillRect(north.x - 8, north.y - 1, display->getStringWidth("N") + 3, FONT_HEIGHT_SMALL - 6);
|
||||||
} else {
|
} else {
|
||||||
display->fillRect(north.x - 4, north.y - 1, display->getStringWidth("N") + 2, FONT_HEIGHT_SMALL - 6);
|
display->fillRect(north.x - 4, north.y - 1, display->getStringWidth("N") + 2, FONT_HEIGHT_SMALL - 6);
|
||||||
|
|||||||
@@ -282,13 +282,13 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
std::string uptime = UIRenderer::drawTimeDelta(days, hours, minutes, seconds);
|
std::string uptime = UIRenderer::drawTimeDelta(days, hours, minutes, seconds);
|
||||||
|
|
||||||
// Line 1 (Still)
|
// Line 1 (Still)
|
||||||
if (currentResolution != graphics::ScreenResolution::UltraLow) {
|
#if !defined(M5STACK_UNITC6L)
|
||||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
|
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
|
||||||
if (config.display.heading_bold)
|
if (config.display.heading_bold)
|
||||||
display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
|
display->drawString(x - 1 + SCREEN_WIDTH - display->getStringWidth(uptime.c_str()), y, uptime.c_str());
|
||||||
|
|
||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
}
|
#endif
|
||||||
// Setup string to assemble analogClock string
|
// Setup string to assemble analogClock string
|
||||||
std::string analogClock = "";
|
std::string analogClock = "";
|
||||||
|
|
||||||
@@ -301,8 +301,9 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
|
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
|
||||||
|
|
||||||
// Tear apart hms into h:m:s
|
// Tear apart hms into h:m:s
|
||||||
int hour, min, sec;
|
int hour = hms / SEC_PER_HOUR;
|
||||||
graphics::decomposeTime(rtc_sec, hour, min, sec);
|
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
|
||||||
|
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
|
||||||
|
|
||||||
char timebuf[12];
|
char timebuf[12];
|
||||||
|
|
||||||
@@ -378,7 +379,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
int line = 1;
|
int line = 1;
|
||||||
|
|
||||||
// === Set Title
|
// === Set Title
|
||||||
const char *titleStr = (currentResolution == ScreenResolution::High) ? "LoRa Info" : "LoRa";
|
const char *titleStr = (isHighResolution) ? "LoRa Info" : "LoRa";
|
||||||
|
|
||||||
// === Header ===
|
// === Header ===
|
||||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||||
@@ -390,11 +391,11 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
char shortnameble[35];
|
char shortnameble[35];
|
||||||
getMacAddr(dmac);
|
getMacAddr(dmac);
|
||||||
snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]);
|
snprintf(screen->ourId, sizeof(screen->ourId), "%02x%02x", dmac[4], dmac[5]);
|
||||||
if (currentResolution == ScreenResolution::UltraLow) {
|
#if defined(M5STACK_UNITC6L)
|
||||||
snprintf(shortnameble, sizeof(shortnameble), "%s", screen->ourId);
|
snprintf(shortnameble, sizeof(shortnameble), "%s", screen->ourId);
|
||||||
} else {
|
#else
|
||||||
snprintf(shortnameble, sizeof(shortnameble), "BLE: %s", screen->ourId);
|
snprintf(shortnameble, sizeof(shortnameble), "BLE: %s", screen->ourId);
|
||||||
}
|
#endif
|
||||||
int textWidth = display->getStringWidth(shortnameble);
|
int textWidth = display->getStringWidth(shortnameble);
|
||||||
int nameX = (SCREEN_WIDTH - textWidth);
|
int nameX = (SCREEN_WIDTH - textWidth);
|
||||||
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
|
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
|
||||||
@@ -413,11 +414,11 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
char regionradiopreset[25];
|
char regionradiopreset[25];
|
||||||
const char *region = myRegion ? myRegion->name : NULL;
|
const char *region = myRegion ? myRegion->name : NULL;
|
||||||
if (region != nullptr) {
|
if (region != nullptr) {
|
||||||
if (currentResolution == ScreenResolution::UltraLow) {
|
#if defined(M5STACK_UNITC6L)
|
||||||
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s", region);
|
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s", region);
|
||||||
} else {
|
#else
|
||||||
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode);
|
snprintf(regionradiopreset, sizeof(regionradiopreset), "%s/%s", region, mode);
|
||||||
}
|
#endif
|
||||||
}
|
}
|
||||||
textWidth = display->getStringWidth(regionradiopreset);
|
textWidth = display->getStringWidth(regionradiopreset);
|
||||||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||||
@@ -429,17 +430,17 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
float freq = RadioLibInterface::instance->getFreq();
|
float freq = RadioLibInterface::instance->getFreq();
|
||||||
snprintf(freqStr, sizeof(freqStr), "%.3f", freq);
|
snprintf(freqStr, sizeof(freqStr), "%.3f", freq);
|
||||||
if (config.lora.channel_num == 0) {
|
if (config.lora.channel_num == 0) {
|
||||||
if (currentResolution == ScreenResolution::UltraLow) {
|
#if defined(M5STACK_UNITC6L)
|
||||||
snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz", freqStr);
|
snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz", freqStr);
|
||||||
} else {
|
#else
|
||||||
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz", freqStr);
|
snprintf(frequencyslot, sizeof(frequencyslot), "Freq: %sMHz", freqStr);
|
||||||
}
|
#endif
|
||||||
} else {
|
} else {
|
||||||
if (currentResolution == ScreenResolution::UltraLow) {
|
#if defined(M5STACK_UNITC6L)
|
||||||
snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz (%d)", freqStr, config.lora.channel_num);
|
snprintf(frequencyslot, sizeof(frequencyslot), "%sMHz (%d)", freqStr, config.lora.channel_num);
|
||||||
} else {
|
#else
|
||||||
snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num);
|
snprintf(frequencyslot, sizeof(frequencyslot), "Freq/Ch: %sMHz (%d)", freqStr, config.lora.channel_num);
|
||||||
}
|
#endif
|
||||||
}
|
}
|
||||||
size_t len = strlen(frequencyslot);
|
size_t len = strlen(frequencyslot);
|
||||||
if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) {
|
if (len >= 4 && strcmp(frequencyslot + len - 4, " (0)") == 0) {
|
||||||
@@ -455,13 +456,12 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
char chUtilPercentage[10];
|
char chUtilPercentage[10];
|
||||||
snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent());
|
snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent());
|
||||||
|
|
||||||
int chUtil_x = (currentResolution == ScreenResolution::High) ? display->getStringWidth(chUtil) + 10
|
int chUtil_x = (isHighResolution) ? display->getStringWidth(chUtil) + 10 : display->getStringWidth(chUtil) + 5;
|
||||||
: display->getStringWidth(chUtil) + 5;
|
|
||||||
int chUtil_y = getTextPositions(display)[line] + 3;
|
int chUtil_y = getTextPositions(display)[line] + 3;
|
||||||
|
|
||||||
int chutil_bar_width = (currentResolution == ScreenResolution::High) ? 100 : 50;
|
int chutil_bar_width = (isHighResolution) ? 100 : 50;
|
||||||
int chutil_bar_height = (currentResolution == ScreenResolution::High) ? 12 : 7;
|
int chutil_bar_height = (isHighResolution) ? 12 : 7;
|
||||||
int extraoffset = (currentResolution == ScreenResolution::High) ? 6 : 3;
|
int extraoffset = (isHighResolution) ? 6 : 3;
|
||||||
int chutil_percent = airTime->channelUtilizationPercent();
|
int chutil_percent = airTime->channelUtilizationPercent();
|
||||||
|
|
||||||
int centerofscreen = SCREEN_WIDTH / 2;
|
int centerofscreen = SCREEN_WIDTH / 2;
|
||||||
@@ -530,18 +530,15 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
|
|||||||
int line = 1;
|
int line = 1;
|
||||||
const int barHeight = 6;
|
const int barHeight = 6;
|
||||||
const int labelX = x;
|
const int labelX = x;
|
||||||
int barsOffset = (currentResolution == ScreenResolution::High) ? 24 : 0;
|
int barsOffset = (isHighResolution) ? 24 : 0;
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
#ifndef T_DECK_PRO
|
|
||||||
barsOffset -= 12;
|
barsOffset -= 12;
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
const int barX = x + 45 + barsOffset;
|
||||||
|
#else
|
||||||
|
const int barX = x + 40 + barsOffset;
|
||||||
#endif
|
#endif
|
||||||
int barX = x + barsOffset;
|
|
||||||
if (currentResolution == ScreenResolution::UltraLow) {
|
|
||||||
barX += 45;
|
|
||||||
} else {
|
|
||||||
barX += 40;
|
|
||||||
}
|
|
||||||
auto drawUsageRow = [&](const char *label, uint32_t used, uint32_t total, bool isHeap = false) {
|
auto drawUsageRow = [&](const char *label, uint32_t used, uint32_t total, bool isHeap = false) {
|
||||||
if (total == 0)
|
if (total == 0)
|
||||||
return;
|
return;
|
||||||
@@ -549,7 +546,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
|
|||||||
int percent = (used * 100) / total;
|
int percent = (used * 100) / total;
|
||||||
|
|
||||||
char combinedStr[24];
|
char combinedStr[24];
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
snprintf(combinedStr, sizeof(combinedStr), "%s%3d%% %u/%uKB", (percent > 80) ? "! " : "", percent, used / 1024,
|
snprintf(combinedStr, sizeof(combinedStr), "%s%3d%% %u/%uKB", (percent > 80) ? "! " : "", percent, used / 1024,
|
||||||
total / 1024);
|
total / 1024);
|
||||||
} else {
|
} else {
|
||||||
@@ -577,7 +574,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
|
|||||||
#endif
|
#endif
|
||||||
// Value string
|
// Value string
|
||||||
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||||
display->drawString(SCREEN_WIDTH, getTextPositions(display)[line], combinedStr);
|
display->drawString(SCREEN_WIDTH - 2, getTextPositions(display)[line], combinedStr);
|
||||||
};
|
};
|
||||||
|
|
||||||
// === Memory values ===
|
// === Memory values ===
|
||||||
@@ -629,33 +626,25 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
|
|||||||
line += 1;
|
line += 1;
|
||||||
|
|
||||||
char appversionstr[35];
|
char appversionstr[35];
|
||||||
|
snprintf(appversionstr, sizeof(appversionstr), "Ver: %s", optstr(APP_VERSION));
|
||||||
char appversionstr_formatted[40];
|
char appversionstr_formatted[40];
|
||||||
|
char *lastDot = strrchr(appversionstr, '.');
|
||||||
const char *ver = optstr(APP_VERSION);
|
#if defined(M5STACK_UNITC6L)
|
||||||
char verbuf[32];
|
if (lastDot != nullptr) {
|
||||||
strncpy(verbuf, ver, sizeof(verbuf) - 1);
|
*lastDot = '\0'; // truncate string
|
||||||
verbuf[sizeof(verbuf) - 1] = '\0';
|
|
||||||
|
|
||||||
char *lastDot = strrchr(verbuf, '.');
|
|
||||||
|
|
||||||
if (currentResolution == ScreenResolution::UltraLow) {
|
|
||||||
if (lastDot != nullptr) {
|
|
||||||
*lastDot = '\0';
|
|
||||||
}
|
|
||||||
snprintf(appversionstr, sizeof(appversionstr), "Ver: %s", verbuf);
|
|
||||||
} else {
|
|
||||||
if (lastDot) {
|
|
||||||
size_t prefixLen = (size_t)(lastDot - verbuf);
|
|
||||||
snprintf(appversionstr_formatted, sizeof(appversionstr_formatted), "Ver: %.*s", (int)prefixLen, verbuf);
|
|
||||||
strncat(appversionstr_formatted, " (", sizeof(appversionstr_formatted) - strlen(appversionstr_formatted) - 1);
|
|
||||||
strncat(appversionstr_formatted, lastDot + 1, sizeof(appversionstr_formatted) - strlen(appversionstr_formatted) - 1);
|
|
||||||
strncat(appversionstr_formatted, ")", sizeof(appversionstr_formatted) - strlen(appversionstr_formatted) - 1);
|
|
||||||
strncpy(appversionstr, appversionstr_formatted, sizeof(appversionstr) - 1);
|
|
||||||
appversionstr[sizeof(appversionstr) - 1] = '\0';
|
|
||||||
} else {
|
|
||||||
snprintf(appversionstr, sizeof(appversionstr), "Ver: %s", verbuf);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
if (lastDot) {
|
||||||
|
size_t prefixLen = lastDot - appversionstr;
|
||||||
|
strncpy(appversionstr_formatted, appversionstr, prefixLen);
|
||||||
|
appversionstr_formatted[prefixLen] = '\0';
|
||||||
|
strncat(appversionstr_formatted, " (", sizeof(appversionstr_formatted) - strlen(appversionstr_formatted) - 1);
|
||||||
|
strncat(appversionstr_formatted, lastDot + 1, sizeof(appversionstr_formatted) - strlen(appversionstr_formatted) - 1);
|
||||||
|
strncat(appversionstr_formatted, ")", sizeof(appversionstr_formatted) - strlen(appversionstr_formatted) - 1);
|
||||||
|
strncpy(appversionstr, appversionstr_formatted, sizeof(appversionstr) - 1);
|
||||||
|
appversionstr[sizeof(appversionstr) - 1] = '\0';
|
||||||
|
}
|
||||||
|
#endif
|
||||||
int textWidth = display->getStringWidth(appversionstr);
|
int textWidth = display->getStringWidth(appversionstr);
|
||||||
int nameX = (SCREEN_WIDTH - textWidth) / 2;
|
int nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||||
|
|
||||||
@@ -674,7 +663,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
|
|||||||
const char *clientWord = nullptr;
|
const char *clientWord = nullptr;
|
||||||
|
|
||||||
// Determine if narrow or wide screen
|
// Determine if narrow or wide screen
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
clientWord = "Client";
|
clientWord = "Client";
|
||||||
} else {
|
} else {
|
||||||
clientWord = "App";
|
clientWord = "App";
|
||||||
@@ -715,23 +704,11 @@ void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int1
|
|||||||
int iconX = SCREEN_WIDTH - chirpy_width - (chirpy_width / 3);
|
int iconX = SCREEN_WIDTH - chirpy_width - (chirpy_width / 3);
|
||||||
int iconY = (SCREEN_HEIGHT - chirpy_height) / 2;
|
int iconY = (SCREEN_HEIGHT - chirpy_height) / 2;
|
||||||
int textX_offset = 10;
|
int textX_offset = 10;
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
|
iconX = SCREEN_WIDTH - chirpy_width_hirez - (chirpy_width_hirez / 3);
|
||||||
|
iconY = (SCREEN_HEIGHT - chirpy_height_hirez) / 2;
|
||||||
textX_offset = textX_offset * 4;
|
textX_offset = textX_offset * 4;
|
||||||
const int scale = 2;
|
display->drawXbm(iconX, iconY, chirpy_width_hirez, chirpy_height_hirez, chirpy_hirez);
|
||||||
const int bytesPerRow = (chirpy_width + 7) / 8;
|
|
||||||
|
|
||||||
for (int yy = 0; yy < chirpy_height; ++yy) {
|
|
||||||
iconX = SCREEN_WIDTH - (chirpy_width * 2) - ((chirpy_width * 2) / 3);
|
|
||||||
iconY = (SCREEN_HEIGHT - (chirpy_height * 2)) / 2;
|
|
||||||
const uint8_t *rowPtr = chirpy + yy * bytesPerRow;
|
|
||||||
for (int xx = 0; xx < chirpy_width; ++xx) {
|
|
||||||
const uint8_t byteVal = pgm_read_byte(rowPtr + (xx >> 3));
|
|
||||||
const uint8_t bitMask = 1U << (xx & 7); // XBM is LSB-first
|
|
||||||
if (byteVal & bitMask) {
|
|
||||||
display->fillRect(iconX + xx * scale, iconY + yy * scale, scale, scale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
display->drawXbm(iconX, iconY, chirpy_width, chirpy_height, chirpy);
|
display->drawXbm(iconX, iconY, chirpy_width, chirpy_height, chirpy);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "graphics/draw/CompassRenderer.h"
|
#include "graphics/draw/CompassRenderer.h"
|
||||||
#include "graphics/draw/DebugRenderer.h"
|
#include "graphics/draw/DebugRenderer.h"
|
||||||
#include "graphics/draw/NodeListRenderer.h"
|
#include "graphics/draw/NodeListRenderer.h"
|
||||||
|
#include "graphics/draw/ScreenRenderer.h"
|
||||||
#include "graphics/draw/UIRenderer.h"
|
#include "graphics/draw/UIRenderer.h"
|
||||||
|
|
||||||
namespace graphics
|
namespace graphics
|
||||||
@@ -29,6 +30,8 @@ using namespace ClockRenderer;
|
|||||||
using namespace CompassRenderer;
|
using namespace CompassRenderer;
|
||||||
using namespace DebugRenderer;
|
using namespace DebugRenderer;
|
||||||
using namespace NodeListRenderer;
|
using namespace NodeListRenderer;
|
||||||
|
using namespace ScreenRenderer;
|
||||||
|
using namespace UIRenderer;
|
||||||
|
|
||||||
} // namespace DrawRenderers
|
} // namespace DrawRenderers
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -19,7 +19,6 @@ class menuHandler
|
|||||||
clock_face_picker,
|
clock_face_picker,
|
||||||
clock_menu,
|
clock_menu,
|
||||||
position_base_menu,
|
position_base_menu,
|
||||||
node_base_menu,
|
|
||||||
gps_toggle_menu,
|
gps_toggle_menu,
|
||||||
gps_format_menu,
|
gps_format_menu,
|
||||||
compass_point_north_menu,
|
compass_point_north_menu,
|
||||||
@@ -44,10 +43,6 @@ class menuHandler
|
|||||||
key_verification_final_prompt,
|
key_verification_final_prompt,
|
||||||
trace_route_menu,
|
trace_route_menu,
|
||||||
throttle_message,
|
throttle_message,
|
||||||
message_response_menu,
|
|
||||||
message_viewmode_menu,
|
|
||||||
reply_menu,
|
|
||||||
delete_messages_menu,
|
|
||||||
node_name_length_menu,
|
node_name_length_menu,
|
||||||
FrameToggles,
|
FrameToggles,
|
||||||
DisplayUnits
|
DisplayUnits
|
||||||
@@ -66,9 +61,6 @@ class menuHandler
|
|||||||
static void TwelveHourPicker();
|
static void TwelveHourPicker();
|
||||||
static void ClockFacePicker();
|
static void ClockFacePicker();
|
||||||
static void messageResponseMenu();
|
static void messageResponseMenu();
|
||||||
static void messageViewModeMenu();
|
|
||||||
static void replyMenu();
|
|
||||||
static void deleteMessagesMenu();
|
|
||||||
static void homeBaseMenu();
|
static void homeBaseMenu();
|
||||||
static void textMessageBaseMenu();
|
static void textMessageBaseMenu();
|
||||||
static void systemBaseMenu();
|
static void systemBaseMenu();
|
||||||
@@ -107,24 +99,5 @@ class menuHandler
|
|||||||
static void BluetoothToggleMenu();
|
static void BluetoothToggleMenu();
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Generic Menu Options designations */
|
|
||||||
enum class OptionsAction { Back, Select };
|
|
||||||
|
|
||||||
template <typename T> struct MenuOption {
|
|
||||||
const char *label;
|
|
||||||
OptionsAction action;
|
|
||||||
bool hasValue;
|
|
||||||
T value;
|
|
||||||
|
|
||||||
MenuOption(const char *labelIn, OptionsAction actionIn, T valueIn)
|
|
||||||
: label(labelIn), action(actionIn), hasValue(true), value(valueIn)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuOption(const char *labelIn, OptionsAction actionIn) : label(labelIn), action(actionIn), hasValue(false), value() {}
|
|
||||||
};
|
|
||||||
|
|
||||||
using RadioPresetOption = MenuOption<meshtastic_Config_LoRaConfig_ModemPreset>;
|
|
||||||
|
|
||||||
} // namespace graphics
|
} // namespace graphics
|
||||||
#endif
|
#endif
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "MessageStore.h" // for StoredMessage
|
|
||||||
#if HAS_SCREEN
|
|
||||||
#include "OLEDDisplay.h"
|
#include "OLEDDisplay.h"
|
||||||
#include "OLEDDisplayUi.h"
|
#include "OLEDDisplayUi.h"
|
||||||
#include "graphics/emotes.h"
|
#include "graphics/emotes.h"
|
||||||
#include "mesh/generated/meshtastic/mesh.pb.h" // for meshtastic_MeshPacket
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -14,27 +10,6 @@ namespace graphics
|
|||||||
namespace MessageRenderer
|
namespace MessageRenderer
|
||||||
{
|
{
|
||||||
|
|
||||||
// Thread filter modes
|
|
||||||
enum class ThreadMode { ALL, CHANNEL, DIRECT };
|
|
||||||
|
|
||||||
// Setter for switching thread mode
|
|
||||||
void setThreadMode(ThreadMode mode, int channel = -1, uint32_t peer = 0);
|
|
||||||
|
|
||||||
// Getter for current mode
|
|
||||||
ThreadMode getThreadMode();
|
|
||||||
|
|
||||||
// Getter for current channel (valid if mode == CHANNEL)
|
|
||||||
int getThreadChannel();
|
|
||||||
|
|
||||||
// Getter for current peer (valid if mode == DIRECT)
|
|
||||||
uint32_t getThreadPeer();
|
|
||||||
|
|
||||||
// Registry accessors for menuHandler
|
|
||||||
const std::vector<int> &getSeenChannels();
|
|
||||||
const std::vector<uint32_t> &getSeenPeers();
|
|
||||||
|
|
||||||
void clearThreadRegistries();
|
|
||||||
|
|
||||||
// Text and emote rendering
|
// Text and emote rendering
|
||||||
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);
|
||||||
|
|
||||||
@@ -45,27 +20,11 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth);
|
std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth);
|
||||||
|
|
||||||
// Function to calculate heights for each line
|
// Function to calculate heights for each line
|
||||||
std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, const Emote *emotes,
|
std::vector<int> calculateLineHeights(const std::vector<std::string> &lines, const Emote *emotes);
|
||||||
const std::vector<bool> &isHeaderVec);
|
|
||||||
|
|
||||||
// Reset scroll state when new messages arrive
|
// Function to render the message content
|
||||||
void resetScrollState();
|
void renderMessageContent(OLEDDisplay *display, const std::vector<std::string> &lines, const std::vector<int> &rowHeights, int x,
|
||||||
|
int yOffset, int scrollBottom, const Emote *emotes, int numEmotes, bool isInverted, bool isBold);
|
||||||
// Manual scroll control for encoder-style inputs
|
|
||||||
void nudgeScroll(int8_t direction);
|
|
||||||
|
|
||||||
// Helper to auto-select the correct thread mode from a message
|
|
||||||
void setThreadFor(const StoredMessage &sm, const meshtastic_MeshPacket &packet);
|
|
||||||
|
|
||||||
// Handles a new incoming/outgoing message: banner, wake, thread select, scroll reset
|
|
||||||
void handleNewMessage(OLEDDisplay *display, const StoredMessage &sm, const meshtastic_MeshPacket &packet);
|
|
||||||
|
|
||||||
// Clear Message Line Cache from Message Renderer
|
|
||||||
void clearMessageCache();
|
|
||||||
|
|
||||||
void scrollUp();
|
|
||||||
void scrollDown();
|
|
||||||
|
|
||||||
} // namespace MessageRenderer
|
} // namespace MessageRenderer
|
||||||
} // namespace graphics
|
} // namespace graphics
|
||||||
#endif
|
|
||||||
@@ -23,6 +23,7 @@ extern graphics::Screen *screen;
|
|||||||
|
|
||||||
#if defined(M5STACK_UNITC6L)
|
#if defined(M5STACK_UNITC6L)
|
||||||
static uint32_t lastSwitchTime = 0;
|
static uint32_t lastSwitchTime = 0;
|
||||||
|
#else
|
||||||
#endif
|
#endif
|
||||||
namespace graphics
|
namespace graphics
|
||||||
{
|
{
|
||||||
@@ -45,119 +46,79 @@ void drawScaledXBitmap16x16(int x, int y, int width, int height, const uint8_t *
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Static variables for dynamic cycling
|
// Static variables for dynamic cycling
|
||||||
static ListMode_Node currentMode_Nodes = MODE_LAST_HEARD;
|
static NodeListMode currentMode = MODE_LAST_HEARD;
|
||||||
static ListMode_Location currentMode_Location = MODE_DISTANCE;
|
|
||||||
static int scrollIndex = 0;
|
static int scrollIndex = 0;
|
||||||
// Popup overlay state
|
|
||||||
static uint32_t popupTime = 0;
|
|
||||||
static int popupTotal = 0;
|
|
||||||
static int popupStart = 0;
|
|
||||||
static int popupEnd = 0;
|
|
||||||
static int popupPage = 1;
|
|
||||||
static int popupMaxPage = 1;
|
|
||||||
|
|
||||||
static const uint32_t POPUP_DURATION_MS = 1000; // 1 second visible
|
|
||||||
|
|
||||||
// =============================
|
|
||||||
// Scrolling Logic
|
|
||||||
// =============================
|
|
||||||
void scrollUp()
|
|
||||||
{
|
|
||||||
if (scrollIndex > 0)
|
|
||||||
scrollIndex--;
|
|
||||||
|
|
||||||
popupTime = millis(); // show popup
|
|
||||||
}
|
|
||||||
|
|
||||||
void scrollDown()
|
|
||||||
{
|
|
||||||
scrollIndex++;
|
|
||||||
popupTime = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================
|
// =============================
|
||||||
// Utility Functions
|
// Utility Functions
|
||||||
// =============================
|
// =============================
|
||||||
|
|
||||||
const char *getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int columnWidth)
|
const char *getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node)
|
||||||
{
|
{
|
||||||
static char nodeName[25]; // single static buffer we return
|
const char *name = NULL;
|
||||||
nodeName[0] = '\0';
|
static char nodeName[16] = "?";
|
||||||
|
if (config.display.use_long_node_name == true) {
|
||||||
auto writeFallbackId = [&] {
|
if (node->has_user && strlen(node->user.long_name) > 0) {
|
||||||
std::snprintf(nodeName, sizeof(nodeName), "(%04X)", static_cast<uint16_t>(node ? (node->num & 0xFFFF) : 0));
|
name = node->user.long_name;
|
||||||
};
|
} else {
|
||||||
|
snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
|
||||||
// 1) Choose target candidate (long vs short) only if present
|
}
|
||||||
const char *raw = nullptr;
|
|
||||||
if (node && node->has_user) {
|
|
||||||
raw = config.display.use_long_node_name ? node->user.long_name : node->user.short_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) Sanitize (empty if raw is null/empty)
|
|
||||||
std::string s = (raw && *raw) ? sanitizeString(raw) : std::string{};
|
|
||||||
|
|
||||||
// 3) Fallback if sanitize yields empty; otherwise copy safely (truncate if needed)
|
|
||||||
if (s.empty() || s == "¿" || s.find_first_not_of("¿") == std::string::npos) {
|
|
||||||
writeFallbackId();
|
|
||||||
} else {
|
} else {
|
||||||
// %.*s ensures null-termination and safe truncation to buffer size - 1
|
if (node->has_user && strlen(node->user.short_name) > 0) {
|
||||||
std::snprintf(nodeName, sizeof(nodeName), "%.*s", static_cast<int>(sizeof(nodeName) - 1), s.c_str());
|
name = node->user.short_name;
|
||||||
|
} else {
|
||||||
|
snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4) Width-based truncation + ellipsis (long-name mode only)
|
// Use sanitizeString() function and copy directly into nodeName
|
||||||
if (config.display.use_long_node_name && display) {
|
std::string sanitized_name = sanitizeString(name ? name : "");
|
||||||
int availWidth = columnWidth - ((currentResolution == ScreenResolution::High) ? 65 : 38);
|
|
||||||
|
if (!sanitized_name.empty()) {
|
||||||
|
strncpy(nodeName, sanitized_name.c_str(), sizeof(nodeName) - 1);
|
||||||
|
nodeName[sizeof(nodeName) - 1] = '\0';
|
||||||
|
} else {
|
||||||
|
snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.display.use_long_node_name == true) {
|
||||||
|
int availWidth = (SCREEN_WIDTH / 2) - 65;
|
||||||
if (availWidth < 0)
|
if (availWidth < 0)
|
||||||
availWidth = 0;
|
availWidth = 0;
|
||||||
|
|
||||||
const size_t beforeLen = std::strlen(nodeName);
|
size_t origLen = strlen(nodeName);
|
||||||
|
while (nodeName[0] && display->getStringWidth(nodeName) > availWidth) {
|
||||||
// Trim from the end until it fits or is empty
|
nodeName[strlen(nodeName) - 1] = '\0';
|
||||||
size_t len = beforeLen;
|
|
||||||
while (len && display->getStringWidth(nodeName) > availWidth) {
|
|
||||||
nodeName[--len] = '\0';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If truncated, append "..." (respect buffer size)
|
// If we actually truncated, append "..." (ensure space remains in buffer)
|
||||||
if (len < beforeLen) {
|
if (strlen(nodeName) < origLen) {
|
||||||
// Make sure there's room for "..." and '\0'
|
size_t len = strlen(nodeName);
|
||||||
const size_t capForText = sizeof(nodeName) - 1; // leaving space for '\0'
|
size_t maxLen = sizeof(nodeName) - 4; // 3 for "..." and 1 for '\0'
|
||||||
const size_t needed = 3; // "..."
|
if (len > maxLen) {
|
||||||
if (len > capForText - needed) {
|
nodeName[maxLen] = '\0';
|
||||||
len = capForText - needed;
|
len = maxLen;
|
||||||
nodeName[len] = '\0';
|
|
||||||
}
|
}
|
||||||
std::strcat(nodeName, "...");
|
strcat(nodeName, "...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeName;
|
return nodeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *getCurrentModeTitle_Nodes(int screenWidth)
|
const char *getCurrentModeTitle(int screenWidth)
|
||||||
{
|
{
|
||||||
switch (currentMode_Nodes) {
|
switch (currentMode) {
|
||||||
case MODE_LAST_HEARD:
|
case MODE_LAST_HEARD:
|
||||||
return "Last Heard";
|
return "Last Heard";
|
||||||
case MODE_HOP_SIGNAL:
|
case MODE_HOP_SIGNAL:
|
||||||
#ifdef USE_EINK
|
#ifdef USE_EINK
|
||||||
return "Hops/Sig";
|
return "Hops/Sig";
|
||||||
#else
|
#else
|
||||||
return (currentResolution == ScreenResolution::High) ? "Hops/Signal" : "Hops/Sig";
|
return (isHighResolution) ? "Hops/Signal" : "Hops/Sig";
|
||||||
#endif
|
#endif
|
||||||
default:
|
|
||||||
return "Nodes";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *getCurrentModeTitle_Location(int screenWidth)
|
|
||||||
{
|
|
||||||
switch (currentMode_Location) {
|
|
||||||
case MODE_DISTANCE:
|
case MODE_DISTANCE:
|
||||||
return "Distance";
|
return "Distance";
|
||||||
case MODE_BEARING:
|
|
||||||
return "Bearings";
|
|
||||||
default:
|
default:
|
||||||
return "Nodes";
|
return "Nodes";
|
||||||
}
|
}
|
||||||
@@ -176,8 +137,10 @@ int calculateMaxScroll(int totalEntries, int visibleRows)
|
|||||||
|
|
||||||
void drawColumnSeparator(OLEDDisplay *display, int16_t x, int16_t yStart, int16_t yEnd)
|
void drawColumnSeparator(OLEDDisplay *display, int16_t x, int16_t yStart, int16_t yEnd)
|
||||||
{
|
{
|
||||||
|
int columnWidth = display->getWidth() / 2;
|
||||||
|
int separatorX = x + columnWidth - 2;
|
||||||
for (int y = yStart; y <= yEnd; y += 2) {
|
for (int y = yStart; y <= yEnd; y += 2) {
|
||||||
display->setPixel(x, y);
|
display->setPixel(separatorX, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,8 +152,7 @@ void drawScrollbar(OLEDDisplay *display, int visibleNodeRows, int totalEntries,
|
|||||||
int scrollbarX = display->getWidth() - 2;
|
int scrollbarX = display->getWidth() - 2;
|
||||||
int scrollbarHeight = display->getHeight() - scrollStartY - 10;
|
int scrollbarHeight = display->getHeight() - scrollStartY - 10;
|
||||||
int thumbHeight = std::max(4, (scrollbarHeight * visibleNodeRows * columns) / totalEntries);
|
int thumbHeight = std::max(4, (scrollbarHeight * visibleNodeRows * columns) / totalEntries);
|
||||||
int perPage = visibleNodeRows * columns;
|
int maxScroll = calculateMaxScroll(totalEntries, visibleNodeRows);
|
||||||
int maxScroll = std::max(0, (totalEntries - 1) / perPage);
|
|
||||||
int thumbY = scrollStartY + (scrollIndex * (scrollbarHeight - thumbHeight)) / std::max(1, maxScroll);
|
int thumbY = scrollStartY + (scrollIndex * (scrollbarHeight - thumbHeight)) / std::max(1, maxScroll);
|
||||||
|
|
||||||
for (int i = 0; i < thumbHeight; i++) {
|
for (int i = 0; i < thumbHeight; i++) {
|
||||||
@@ -205,9 +167,9 @@ void drawScrollbar(OLEDDisplay *display, int visibleNodeRows, int totalEntries,
|
|||||||
void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
||||||
{
|
{
|
||||||
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||||
int timeOffset = (currentResolution == ScreenResolution::High) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7);
|
int timeOffset = (isHighResolution) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7);
|
||||||
|
|
||||||
const char *nodeName = getSafeNodeName(display, node, columnWidth);
|
const char *nodeName = getSafeNodeName(display, node);
|
||||||
|
|
||||||
char timeStr[10];
|
char timeStr[10];
|
||||||
uint32_t seconds = sinceLastSeen(node);
|
uint32_t seconds = sinceLastSeen(node);
|
||||||
@@ -226,9 +188,9 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
|||||||
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
display->drawString(x + ((currentResolution == ScreenResolution::High) ? 6 : 3), y, nodeName);
|
display->drawString(x + ((isHighResolution) ? 6 : 3), y, nodeName);
|
||||||
if (node->is_favorite) {
|
if (node->is_favorite) {
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
||||||
} else {
|
} else {
|
||||||
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
||||||
@@ -247,19 +209,19 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
|||||||
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||||
|
|
||||||
int nameMaxWidth = columnWidth - 25;
|
int nameMaxWidth = columnWidth - 25;
|
||||||
int barsOffset = (currentResolution == ScreenResolution::High) ? (isLeftCol ? 20 : 24) : (isLeftCol ? 15 : 19);
|
int barsOffset = (isHighResolution) ? (isLeftCol ? 20 : 24) : (isLeftCol ? 15 : 19);
|
||||||
int hopOffset = (currentResolution == ScreenResolution::High) ? (isLeftCol ? 21 : 29) : (isLeftCol ? 13 : 17);
|
int hopOffset = (isHighResolution) ? (isLeftCol ? 21 : 29) : (isLeftCol ? 13 : 17);
|
||||||
|
|
||||||
int barsXOffset = columnWidth - barsOffset;
|
int barsXOffset = columnWidth - barsOffset;
|
||||||
|
|
||||||
const char *nodeName = getSafeNodeName(display, node, columnWidth);
|
const char *nodeName = getSafeNodeName(display, node);
|
||||||
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
display->drawStringMaxWidth(x + ((currentResolution == ScreenResolution::High) ? 6 : 3), y, nameMaxWidth, nodeName);
|
display->drawStringMaxWidth(x + ((isHighResolution) ? 6 : 3), y, nameMaxWidth, nodeName);
|
||||||
if (node->is_favorite) {
|
if (node->is_favorite) {
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
||||||
} else {
|
} else {
|
||||||
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
||||||
@@ -294,10 +256,9 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
|||||||
void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
||||||
{
|
{
|
||||||
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||||
int nameMaxWidth =
|
int nameMaxWidth = columnWidth - (isHighResolution ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
|
||||||
columnWidth - ((currentResolution == ScreenResolution::High) ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
|
|
||||||
|
|
||||||
const char *nodeName = getSafeNodeName(display, node, columnWidth);
|
const char *nodeName = getSafeNodeName(display, node);
|
||||||
char distStr[10] = "";
|
char distStr[10] = "";
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
@@ -350,9 +311,9 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
|||||||
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
display->drawStringMaxWidth(x + ((currentResolution == ScreenResolution::High) ? 6 : 3), y, nameMaxWidth, nodeName);
|
display->drawStringMaxWidth(x + ((isHighResolution) ? 6 : 3), y, nameMaxWidth, nodeName);
|
||||||
if (node->is_favorite) {
|
if (node->is_favorite) {
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
||||||
} else {
|
} else {
|
||||||
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
||||||
@@ -360,24 +321,26 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (strlen(distStr) > 0) {
|
if (strlen(distStr) > 0) {
|
||||||
int offset = (currentResolution == ScreenResolution::High)
|
int offset = (isHighResolution) ? (isLeftCol ? 7 : 10) // Offset for Wide Screens (Left Column:Right Column)
|
||||||
? (isLeftCol ? 7 : 10) // Offset for Wide Screens (Left Column:Right Column)
|
: (isLeftCol ? 4 : 7); // Offset for Narrow Screens (Left Column:Right Column)
|
||||||
: (isLeftCol ? 4 : 7); // Offset for Narrow Screens (Left Column:Right Column)
|
|
||||||
int rightEdge = x + columnWidth - offset;
|
int rightEdge = x + columnWidth - offset;
|
||||||
int textWidth = display->getStringWidth(distStr);
|
int textWidth = display->getStringWidth(distStr);
|
||||||
display->drawString(rightEdge - textWidth, y, distStr);
|
display->drawString(rightEdge - textWidth, y, distStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawEntryDynamic_Nodes(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
void drawEntryDynamic(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
||||||
{
|
{
|
||||||
switch (currentMode_Nodes) {
|
switch (currentMode) {
|
||||||
case MODE_LAST_HEARD:
|
case MODE_LAST_HEARD:
|
||||||
drawEntryLastHeard(display, node, x, y, columnWidth);
|
drawEntryLastHeard(display, node, x, y, columnWidth);
|
||||||
break;
|
break;
|
||||||
case MODE_HOP_SIGNAL:
|
case MODE_HOP_SIGNAL:
|
||||||
drawEntryHopSignal(display, node, x, y, columnWidth);
|
drawEntryHopSignal(display, node, x, y, columnWidth);
|
||||||
break;
|
break;
|
||||||
|
case MODE_DISTANCE:
|
||||||
|
drawNodeDistance(display, node, x, y, columnWidth);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -388,16 +351,15 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
|||||||
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||||
|
|
||||||
// Adjust max text width depending on column and screen width
|
// Adjust max text width depending on column and screen width
|
||||||
int nameMaxWidth =
|
int nameMaxWidth = columnWidth - (isHighResolution ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
|
||||||
columnWidth - ((currentResolution == ScreenResolution::High) ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
|
|
||||||
|
|
||||||
const char *nodeName = getSafeNodeName(display, node, columnWidth);
|
const char *nodeName = getSafeNodeName(display, node);
|
||||||
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
display->drawStringMaxWidth(x + ((currentResolution == ScreenResolution::High) ? 6 : 3), y, nameMaxWidth, nodeName);
|
display->drawStringMaxWidth(x + ((isHighResolution) ? 6 : 3), y, nameMaxWidth, nodeName);
|
||||||
if (node->is_favorite) {
|
if (node->is_favorite) {
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
||||||
} else {
|
} else {
|
||||||
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
||||||
@@ -412,7 +374,7 @@ void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||||
int arrowXOffset = (currentResolution == ScreenResolution::High) ? (isLeftCol ? 22 : 24) : (isLeftCol ? 12 : 18);
|
int arrowXOffset = (isHighResolution) ? (isLeftCol ? 22 : 24) : (isLeftCol ? 12 : 18);
|
||||||
|
|
||||||
int centerX = x + columnWidth - arrowXOffset;
|
int centerX = x + columnWidth - arrowXOffset;
|
||||||
int centerY = y + FONT_HEIGHT_SMALL / 2;
|
int centerY = y + FONT_HEIGHT_SMALL / 2;
|
||||||
@@ -469,6 +431,11 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
locationScreen = true;
|
locationScreen = true;
|
||||||
else if (strcmp(title, "Distance") == 0)
|
else if (strcmp(title, "Distance") == 0)
|
||||||
locationScreen = true;
|
locationScreen = true;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
|
int columnWidth = display->getWidth();
|
||||||
|
#else
|
||||||
|
int columnWidth = display->getWidth() / 2;
|
||||||
|
#endif
|
||||||
display->clear();
|
display->clear();
|
||||||
|
|
||||||
// Draw the battery/time header
|
// Draw the battery/time header
|
||||||
@@ -477,74 +444,39 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
// Space below header
|
// Space below header
|
||||||
y += COMMON_HEADER_HEIGHT;
|
y += COMMON_HEADER_HEIGHT;
|
||||||
|
|
||||||
int totalColumns = 1; // Default to 1 column
|
|
||||||
|
|
||||||
if (config.display.use_long_node_name) {
|
|
||||||
if (SCREEN_WIDTH <= 240) {
|
|
||||||
totalColumns = 1;
|
|
||||||
} else if (SCREEN_WIDTH > 240) {
|
|
||||||
totalColumns = 2;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (SCREEN_WIDTH <= 64) {
|
|
||||||
totalColumns = 1;
|
|
||||||
} else if (SCREEN_WIDTH > 64 && SCREEN_WIDTH <= 240) {
|
|
||||||
totalColumns = 2;
|
|
||||||
} else {
|
|
||||||
totalColumns = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int columnWidth = display->getWidth() / totalColumns;
|
|
||||||
|
|
||||||
int totalEntries = nodeDB->getNumMeshNodes();
|
int totalEntries = nodeDB->getNumMeshNodes();
|
||||||
int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;
|
int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;
|
||||||
int numskipped = 0;
|
int numskipped = 0;
|
||||||
int visibleNodeRows = totalRowsAvailable;
|
int visibleNodeRows = totalRowsAvailable;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
// Build filtered + ordered list
|
int totalColumns = 1;
|
||||||
std::vector<int> drawList;
|
#else
|
||||||
drawList.reserve(totalEntries);
|
int totalColumns = 2;
|
||||||
for (int i = 0; i < totalEntries; i++) {
|
#endif
|
||||||
auto *n = nodeDB->getMeshNodeByIndex(i);
|
|
||||||
|
|
||||||
if (!n)
|
|
||||||
continue;
|
|
||||||
if (n->num == nodeDB->getNodeNum())
|
|
||||||
continue;
|
|
||||||
if (locationScreen && !n->has_position)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
drawList.push_back(n->num);
|
|
||||||
}
|
|
||||||
totalEntries = drawList.size();
|
|
||||||
int perPage = visibleNodeRows * totalColumns;
|
|
||||||
|
|
||||||
int maxScroll = 0;
|
|
||||||
if (perPage > 0) {
|
|
||||||
maxScroll = std::max(0, (totalEntries - 1) / perPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (scrollIndex > maxScroll)
|
|
||||||
scrollIndex = maxScroll;
|
|
||||||
int startIndex = scrollIndex * visibleNodeRows * totalColumns;
|
int startIndex = scrollIndex * visibleNodeRows * totalColumns;
|
||||||
|
if (nodeDB->getMeshNodeByIndex(startIndex)->num == nodeDB->getNodeNum()) {
|
||||||
|
startIndex++; // skip own node
|
||||||
|
}
|
||||||
int endIndex = std::min(startIndex + visibleNodeRows * totalColumns, totalEntries);
|
int endIndex = std::min(startIndex + visibleNodeRows * totalColumns, totalEntries);
|
||||||
|
|
||||||
int yOffset = 0;
|
int yOffset = 0;
|
||||||
int col = 0;
|
int col = 0;
|
||||||
int lastNodeY = y;
|
int lastNodeY = y;
|
||||||
int shownCount = 0;
|
int shownCount = 0;
|
||||||
int rowCount = 0;
|
int rowCount = 0;
|
||||||
|
|
||||||
for (int idx = startIndex; idx < endIndex; idx++) {
|
for (int i = startIndex; i < endIndex; ++i) {
|
||||||
uint32_t nodeNum = drawList[idx];
|
if (locationScreen && !nodeDB->getMeshNodeByIndex(i)->has_position) {
|
||||||
auto *node = nodeDB->getMeshNode(nodeNum);
|
numskipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
int xPos = x + (col * columnWidth);
|
int xPos = x + (col * columnWidth);
|
||||||
int yPos = y + yOffset;
|
int yPos = y + yOffset;
|
||||||
|
renderer(display, nodeDB->getMeshNodeByIndex(i), xPos, yPos, columnWidth);
|
||||||
|
|
||||||
renderer(display, node, xPos, yPos, columnWidth);
|
if (extras) {
|
||||||
|
extras(display, nodeDB->getMeshNodeByIndex(i), xPos, yPos, columnWidth, heading, lat, lon);
|
||||||
if (extras)
|
}
|
||||||
extras(display, node, xPos, yPos, columnWidth, heading, lat, lon);
|
|
||||||
|
|
||||||
lastNodeY = std::max(lastNodeY, yPos + FONT_HEIGHT_SMALL);
|
lastNodeY = std::max(lastNodeY, yPos + FONT_HEIGHT_SMALL);
|
||||||
yOffset += rowYOffset;
|
yOffset += rowYOffset;
|
||||||
@@ -563,73 +495,17 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
// This should correct the scrollbar
|
// This should correct the scrollbar
|
||||||
totalEntries -= numskipped;
|
totalEntries -= numskipped;
|
||||||
|
|
||||||
|
#if !defined(M5STACK_UNITC6L)
|
||||||
// Draw column separator
|
// Draw column separator
|
||||||
if (currentResolution != ScreenResolution::UltraLow && shownCount > 0) {
|
if (shownCount > 0) {
|
||||||
const int firstNodeY = y + 3;
|
const int firstNodeY = y + 3;
|
||||||
for (int horizontal_offset = 1; horizontal_offset < totalColumns; horizontal_offset++) {
|
drawColumnSeparator(display, x, firstNodeY, lastNodeY);
|
||||||
drawColumnSeparator(display, columnWidth * horizontal_offset, firstNodeY, lastNodeY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
const int scrollStartY = y + 3;
|
const int scrollStartY = y + 3;
|
||||||
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, totalColumns, scrollStartY);
|
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
|
||||||
graphics::drawCommonFooter(display, x, y);
|
graphics::drawCommonFooter(display, x, y);
|
||||||
|
|
||||||
// Scroll Popup Overlay
|
|
||||||
if (millis() - popupTime < POPUP_DURATION_MS) {
|
|
||||||
popupTotal = totalEntries;
|
|
||||||
|
|
||||||
int perPage = visibleNodeRows * totalColumns;
|
|
||||||
|
|
||||||
popupStart = startIndex + 1;
|
|
||||||
popupEnd = std::min(startIndex + perPage, totalEntries);
|
|
||||||
|
|
||||||
popupPage = (scrollIndex + 1);
|
|
||||||
popupMaxPage = std::max(1, (totalEntries + perPage - 1) / perPage);
|
|
||||||
|
|
||||||
char buf[32];
|
|
||||||
snprintf(buf, sizeof(buf), "%d-%d/%d Pg %d/%d", popupStart, popupEnd, popupTotal, popupPage, popupMaxPage);
|
|
||||||
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
||||||
|
|
||||||
// Box padding
|
|
||||||
int padding = 2;
|
|
||||||
int textW = display->getStringWidth(buf);
|
|
||||||
int textH = FONT_HEIGHT_SMALL;
|
|
||||||
int boxWidth = textW + padding * 3;
|
|
||||||
int boxHeight = textH + padding * 2;
|
|
||||||
|
|
||||||
// Center of usable screen area:
|
|
||||||
int headerHeight = FONT_HEIGHT_SMALL - 1;
|
|
||||||
int footerHeight = FONT_HEIGHT_SMALL + 2;
|
|
||||||
|
|
||||||
int usableTop = headerHeight;
|
|
||||||
int usableBottom = display->getHeight() - footerHeight;
|
|
||||||
int usableHeight = usableBottom - usableTop;
|
|
||||||
|
|
||||||
// Center point inside usable area
|
|
||||||
int boxLeft = (display->getWidth() - boxWidth) / 2;
|
|
||||||
int boxTop = usableTop + (usableHeight - boxHeight) / 2;
|
|
||||||
|
|
||||||
// Draw Box
|
|
||||||
display->setColor(BLACK);
|
|
||||||
display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2);
|
|
||||||
display->fillRect(boxLeft, boxTop - 2, boxWidth, 1);
|
|
||||||
display->fillRect(boxLeft, boxTop + boxHeight + 1, boxWidth, 1);
|
|
||||||
display->fillRect(boxLeft - 2, boxTop, 1, boxHeight);
|
|
||||||
display->fillRect(boxLeft + boxWidth + 1, boxTop, 1, boxHeight);
|
|
||||||
display->setColor(WHITE);
|
|
||||||
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
|
|
||||||
display->setColor(BLACK);
|
|
||||||
display->fillRect(boxLeft, boxTop, 1, 1);
|
|
||||||
display->fillRect(boxLeft + boxWidth - 1, boxTop, 1, 1);
|
|
||||||
display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1);
|
|
||||||
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
|
|
||||||
display->setColor(WHITE);
|
|
||||||
|
|
||||||
// Text
|
|
||||||
display->drawString(boxLeft + padding, boxTop + padding, buf);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================
|
// =============================
|
||||||
@@ -637,11 +513,10 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
// =============================
|
// =============================
|
||||||
|
|
||||||
#ifndef USE_EINK
|
#ifndef USE_EINK
|
||||||
// Node list for Last Heard and Hop Signal views
|
void drawDynamicNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
void drawDynamicListScreen_Nodes(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
||||||
{
|
{
|
||||||
// Static variables to track mode and duration
|
// Static variables to track mode and duration
|
||||||
static ListMode_Node lastRenderedMode = MODE_COUNT_NODE;
|
static NodeListMode lastRenderedMode = MODE_COUNT;
|
||||||
static unsigned long modeStartTime = 0;
|
static unsigned long modeStartTime = 0;
|
||||||
|
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
@@ -654,65 +529,23 @@ void drawDynamicListScreen_Nodes(OLEDDisplay *display, OLEDDisplayUiState *state
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// On very first call (on boot or state enter)
|
// On very first call (on boot or state enter)
|
||||||
if (lastRenderedMode == MODE_COUNT_NODE) {
|
if (lastRenderedMode == MODE_COUNT) {
|
||||||
currentMode_Nodes = MODE_LAST_HEARD;
|
currentMode = MODE_LAST_HEARD;
|
||||||
modeStartTime = now;
|
modeStartTime = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time to switch to next mode?
|
// Time to switch to next mode?
|
||||||
if (now - modeStartTime >= getModeCycleIntervalMs()) {
|
if (now - modeStartTime >= getModeCycleIntervalMs()) {
|
||||||
currentMode_Nodes = static_cast<ListMode_Node>((currentMode_Nodes + 1) % MODE_COUNT_NODE);
|
currentMode = static_cast<NodeListMode>((currentMode + 1) % MODE_COUNT);
|
||||||
modeStartTime = now;
|
modeStartTime = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render screen based on currentMode
|
// Render screen based on currentMode
|
||||||
const char *title = getCurrentModeTitle_Nodes(display->getWidth());
|
const char *title = getCurrentModeTitle(display->getWidth());
|
||||||
drawNodeListScreen(display, state, x, y, title, drawEntryDynamic_Nodes);
|
drawNodeListScreen(display, state, x, y, title, drawEntryDynamic);
|
||||||
|
|
||||||
// Track the last mode to avoid reinitializing modeStartTime
|
// Track the last mode to avoid reinitializing modeStartTime
|
||||||
lastRenderedMode = currentMode_Nodes;
|
lastRenderedMode = currentMode;
|
||||||
}
|
|
||||||
|
|
||||||
// Node list for Distance and Bearings views
|
|
||||||
void drawDynamicListScreen_Location(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
||||||
{
|
|
||||||
// Static variables to track mode and duration
|
|
||||||
static ListMode_Location lastRenderedMode = MODE_COUNT_LOCATION;
|
|
||||||
static unsigned long modeStartTime = 0;
|
|
||||||
|
|
||||||
unsigned long now = millis();
|
|
||||||
|
|
||||||
#if defined(M5STACK_UNITC6L)
|
|
||||||
display->clear();
|
|
||||||
if (now - lastSwitchTime >= 3000) {
|
|
||||||
display->display();
|
|
||||||
lastSwitchTime = now;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// On very first call (on boot or state enter)
|
|
||||||
if (lastRenderedMode == MODE_COUNT_LOCATION) {
|
|
||||||
currentMode_Location = MODE_DISTANCE;
|
|
||||||
modeStartTime = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Time to switch to next mode?
|
|
||||||
if (now - modeStartTime >= getModeCycleIntervalMs()) {
|
|
||||||
currentMode_Location = static_cast<ListMode_Location>((currentMode_Location + 1) % MODE_COUNT_LOCATION);
|
|
||||||
modeStartTime = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render screen based on currentMode
|
|
||||||
const char *title = getCurrentModeTitle_Location(display->getWidth());
|
|
||||||
|
|
||||||
// Render screen based on currentMode_Location
|
|
||||||
if (currentMode_Location == MODE_DISTANCE) {
|
|
||||||
drawNodeListScreen(display, state, x, y, title, drawNodeDistance);
|
|
||||||
} else if (currentMode_Location == MODE_BEARING) {
|
|
||||||
drawNodeListWithCompasses(display, state, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track the last mode to avoid reinitializing modeStartTime
|
|
||||||
lastRenderedMode = currentMode_Location;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -733,12 +566,14 @@ void drawHopSignalScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
|||||||
#endif
|
#endif
|
||||||
drawNodeListScreen(display, state, x, y, title, drawEntryHopSignal);
|
drawNodeListScreen(display, state, x, y, title, drawEntryHopSignal);
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawDistanceScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
void drawDistanceScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
const char *title = "Distance";
|
const char *title = "Distance";
|
||||||
drawNodeListScreen(display, state, x, y, title, drawNodeDistance);
|
drawNodeListScreen(display, state, x, y, title, drawNodeDistance);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
float heading = 0;
|
float heading = 0;
|
||||||
|
|||||||
@@ -23,11 +23,8 @@ namespace NodeListRenderer
|
|||||||
typedef void (*EntryRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, int16_t, int);
|
typedef void (*EntryRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, int16_t, int);
|
||||||
typedef void (*NodeExtrasRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, int16_t, int, float, double, double);
|
typedef void (*NodeExtrasRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, int16_t, int, float, double, double);
|
||||||
|
|
||||||
// Node list mode enumeration for Last Heard and Hop Signal views
|
// Node list mode enumeration
|
||||||
enum ListMode_Node { MODE_LAST_HEARD = 0, MODE_HOP_SIGNAL = 1, MODE_COUNT_NODE = 2 };
|
enum NodeListMode { MODE_LAST_HEARD = 0, MODE_HOP_SIGNAL = 1, MODE_DISTANCE = 2, MODE_COUNT = 3 };
|
||||||
|
|
||||||
// Node list mode enumeration for Distance and Bearings views
|
|
||||||
enum ListMode_Location { MODE_DISTANCE = 0, MODE_BEARING = 1, MODE_COUNT_LOCATION = 2 };
|
|
||||||
|
|
||||||
// Main node list screen function
|
// Main node list screen function
|
||||||
void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *title,
|
void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *title,
|
||||||
@@ -38,7 +35,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth);
|
void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth);
|
||||||
void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth);
|
void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth);
|
||||||
void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth);
|
void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth);
|
||||||
void drawEntryDynamic_Nodes(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth);
|
void drawEntryDynamic(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth);
|
||||||
void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth);
|
void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth);
|
||||||
|
|
||||||
// Extras renderers
|
// Extras renderers
|
||||||
@@ -49,20 +46,14 @@ void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
|||||||
void drawLastHeardScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
void drawLastHeardScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
void drawHopSignalScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
void drawHopSignalScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
void drawDistanceScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
void drawDistanceScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
void drawDynamicListScreen_Nodes(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
void drawDynamicNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
void drawDynamicListScreen_Location(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
|
||||||
void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
const char *getCurrentModeTitle_Nodes(int screenWidth);
|
const char *getCurrentModeTitle(int screenWidth);
|
||||||
const char *getCurrentModeTitle_Location(int screenWidth);
|
const char *getSafeNodeName(meshtastic_NodeInfoLite *node);
|
||||||
const char *getSafeNodeName(meshtastic_NodeInfoLite *node, int columnWidth);
|
|
||||||
void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields);
|
void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields);
|
||||||
|
|
||||||
// Scrolling controls
|
|
||||||
void scrollUp();
|
|
||||||
void scrollDown();
|
|
||||||
|
|
||||||
// Bitmap drawing function
|
// Bitmap drawing function
|
||||||
void drawScaledXBitmap16x16(int x, int y, int width, int height, const uint8_t *bitmapXBM, OLEDDisplay *display);
|
void drawScaledXBitmap16x16(int x, int y, int width, int height, const uint8_t *bitmapXBM, OLEDDisplay *display);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
|
||||||
#if HAS_SCREEN
|
#if HAS_SCREEN
|
||||||
|
|
||||||
#include "DisplayFormatters.h"
|
#include "DisplayFormatters.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "NotificationRenderer.h"
|
#include "NotificationRenderer.h"
|
||||||
@@ -38,7 +38,7 @@ extern bool hasUnreadMessage;
|
|||||||
|
|
||||||
namespace graphics
|
namespace graphics
|
||||||
{
|
{
|
||||||
int bannerSignalBars = -1;
|
|
||||||
InputEvent NotificationRenderer::inEvent;
|
InputEvent NotificationRenderer::inEvent;
|
||||||
int8_t NotificationRenderer::curSelected = 0;
|
int8_t NotificationRenderer::curSelected = 0;
|
||||||
char NotificationRenderer::alertBannerMessage[256] = {0};
|
char NotificationRenderer::alertBannerMessage[256] = {0};
|
||||||
@@ -321,7 +321,7 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
|
|||||||
}
|
}
|
||||||
if (i == curSelected) {
|
if (i == curSelected) {
|
||||||
selectedNodenum = nodeDB->getMeshNodeByIndex(i + 1)->num;
|
selectedNodenum = nodeDB->getMeshNodeByIndex(i + 1)->num;
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
strncpy(scratchLineBuffer[scratchLineNum], "> ", 3);
|
strncpy(scratchLineBuffer[scratchLineNum], "> ", 3);
|
||||||
strncpy(scratchLineBuffer[scratchLineNum] + 2, temp_name, 36);
|
strncpy(scratchLineBuffer[scratchLineNum] + 2, temp_name, 36);
|
||||||
strncpy(scratchLineBuffer[scratchLineNum] + strlen(temp_name) + 2, " <", 3);
|
strncpy(scratchLineBuffer[scratchLineNum] + strlen(temp_name) + 2, " <", 3);
|
||||||
@@ -449,7 +449,7 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
|
|||||||
|
|
||||||
for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) {
|
for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) {
|
||||||
if (i == curSelected) {
|
if (i == curSelected) {
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
strncpy(lineBuffer, "> ", 3);
|
strncpy(lineBuffer, "> ", 3);
|
||||||
strncpy(lineBuffer + 2, optionsArrayPtr[i], 36);
|
strncpy(lineBuffer + 2, optionsArrayPtr[i], 36);
|
||||||
strncpy(lineBuffer + strlen(optionsArrayPtr[i]) + 2, " <", 3);
|
strncpy(lineBuffer + strlen(optionsArrayPtr[i]) + 2, " <", 3);
|
||||||
@@ -477,7 +477,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
|||||||
|
|
||||||
bool is_picker = false;
|
bool is_picker = false;
|
||||||
uint16_t lineCount = 0;
|
uint16_t lineCount = 0;
|
||||||
// Layout Configuration
|
// === Layout Configuration ===
|
||||||
constexpr uint16_t hPadding = 5;
|
constexpr uint16_t hPadding = 5;
|
||||||
constexpr uint16_t vPadding = 2;
|
constexpr uint16_t vPadding = 2;
|
||||||
bool needs_bell = false;
|
bool needs_bell = false;
|
||||||
@@ -491,32 +491,13 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
|||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
|
||||||
// Track widest line INCLUDING bars (but don't change per-line widths)
|
|
||||||
uint16_t widestLineWithBars = 0;
|
|
||||||
|
|
||||||
while (lines[lineCount] != nullptr) {
|
while (lines[lineCount] != nullptr) {
|
||||||
auto newlinePointer = strchr(lines[lineCount], '\n');
|
auto newlinePointer = strchr(lines[lineCount], '\n');
|
||||||
if (newlinePointer)
|
if (newlinePointer)
|
||||||
lineLengths[lineCount] = (newlinePointer - lines[lineCount]); // Check for newlines first
|
lineLengths[lineCount] = (newlinePointer - lines[lineCount]); // Check for newlines first
|
||||||
else // if the newline wasn't found, then pull string length from strlen
|
else // if the newline wasn't found, then pull string length from strlen
|
||||||
lineLengths[lineCount] = strlen(lines[lineCount]);
|
lineLengths[lineCount] = strlen(lines[lineCount]);
|
||||||
|
|
||||||
lineWidths[lineCount] = display->getStringWidth(lines[lineCount], lineLengths[lineCount], true);
|
lineWidths[lineCount] = display->getStringWidth(lines[lineCount], lineLengths[lineCount], true);
|
||||||
|
|
||||||
// Consider extra width for signal bars on lines that contain "Signal:"
|
|
||||||
uint16_t potentialWidth = lineWidths[lineCount];
|
|
||||||
if (graphics::bannerSignalBars >= 0 && strncmp(lines[lineCount], "Signal:", 7) == 0) {
|
|
||||||
const int totalBars = 5;
|
|
||||||
const int barWidth = 3;
|
|
||||||
const int barSpacing = 2;
|
|
||||||
const int gap = 6; // space between text and bars
|
|
||||||
int barsWidth = totalBars * barWidth + (totalBars - 1) * barSpacing + gap;
|
|
||||||
potentialWidth += barsWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (potentialWidth > widestLineWithBars)
|
|
||||||
widestLineWithBars = potentialWidth;
|
|
||||||
|
|
||||||
if (!is_picker) {
|
if (!is_picker) {
|
||||||
needs_bell |= (strstr(alertBannerMessage, "Alert Received") != nullptr);
|
needs_bell |= (strstr(alertBannerMessage, "Alert Received") != nullptr);
|
||||||
if (lineWidths[lineCount] > maxWidth)
|
if (lineWidths[lineCount] > maxWidth)
|
||||||
@@ -526,16 +507,12 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
|||||||
}
|
}
|
||||||
// count lines
|
// count lines
|
||||||
|
|
||||||
// Ensure box accounts for signal bars if present
|
|
||||||
if (widestLineWithBars > maxWidth)
|
|
||||||
maxWidth = widestLineWithBars;
|
|
||||||
|
|
||||||
uint16_t boxWidth = hPadding * 2 + maxWidth;
|
uint16_t boxWidth = hPadding * 2 + maxWidth;
|
||||||
|
#if defined(M5STACK_UNITC6L)
|
||||||
if (needs_bell) {
|
if (needs_bell) {
|
||||||
if ((currentResolution == ScreenResolution::High) && boxWidth <= 150)
|
if (isHighResolution && boxWidth <= 150)
|
||||||
boxWidth += 26;
|
boxWidth += 26;
|
||||||
if ((currentResolution == ScreenResolution::Low || currentResolution == ScreenResolution::UltraLow) && boxWidth <= 100)
|
if (!isHighResolution && boxWidth <= 100)
|
||||||
boxWidth += 20;
|
boxWidth += 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,17 +521,14 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
|||||||
uint8_t visibleTotalLines = std::min<uint8_t>(lineCount, (screenHeight - vPadding * 2) / effectiveLineHeight);
|
uint8_t visibleTotalLines = std::min<uint8_t>(lineCount, (screenHeight - vPadding * 2) / effectiveLineHeight);
|
||||||
uint16_t contentHeight = visibleTotalLines * effectiveLineHeight;
|
uint16_t contentHeight = visibleTotalLines * effectiveLineHeight;
|
||||||
uint16_t boxHeight = contentHeight + vPadding * 2;
|
uint16_t boxHeight = contentHeight + vPadding * 2;
|
||||||
if (visibleTotalLines == 1) {
|
if (visibleTotalLines == 1)
|
||||||
boxHeight += (currentResolution == ScreenResolution::High) ? 4 : 3;
|
boxHeight += (isHighResolution ? 4 : 3);
|
||||||
}
|
|
||||||
|
|
||||||
int16_t boxLeft = (display->width() / 2) - (boxWidth / 2);
|
int16_t boxLeft = (display->width() / 2) - (boxWidth / 2);
|
||||||
if (totalLines > visibleTotalLines) {
|
if (totalLines > visibleTotalLines)
|
||||||
boxWidth += (currentResolution == ScreenResolution::High) ? 4 : 2;
|
boxWidth += (isHighResolution ? 4 : 2);
|
||||||
}
|
|
||||||
int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
|
int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
|
||||||
boxHeight += (currentResolution == ScreenResolution::High) ? 2 : 1;
|
|
||||||
#if defined(M5STACK_UNITC6L)
|
|
||||||
if (visibleTotalLines == 1) {
|
if (visibleTotalLines == 1) {
|
||||||
boxTop += 25;
|
boxTop += 25;
|
||||||
}
|
}
|
||||||
@@ -565,9 +539,127 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
|||||||
if (boxTop < 0)
|
if (boxTop < 0)
|
||||||
boxTop = 0;
|
boxTop = 0;
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
// Draw Box
|
// === Draw Box ===
|
||||||
|
display->setColor(BLACK);
|
||||||
|
display->fillRect(boxLeft, boxTop, boxWidth, boxHeight);
|
||||||
|
display->setColor(WHITE);
|
||||||
|
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
|
||||||
|
display->fillRect(boxLeft, boxTop - 2, boxWidth, 1);
|
||||||
|
display->fillRect(boxLeft - 2, boxTop, 1, boxHeight);
|
||||||
|
display->fillRect(boxLeft + boxWidth + 1, boxTop, 1, boxHeight);
|
||||||
|
display->setColor(BLACK);
|
||||||
|
display->fillRect(boxLeft, boxTop, 1, 1);
|
||||||
|
display->fillRect(boxLeft + boxWidth - 1, boxTop, 1, 1);
|
||||||
|
display->fillRect(boxLeft, boxTop + boxHeight - 1, 1, 1);
|
||||||
|
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
|
||||||
|
display->setColor(WHITE);
|
||||||
|
int16_t lineY = boxTop + vPadding;
|
||||||
|
int swingRange = 8;
|
||||||
|
static int swingOffset = 0;
|
||||||
|
static bool swingRight = true;
|
||||||
|
static unsigned long lastSwingTime = 0;
|
||||||
|
unsigned long now = millis();
|
||||||
|
int swingSpeedMs = 10 / (swingRange * 2);
|
||||||
|
if (now - lastSwingTime >= (unsigned long)swingSpeedMs) {
|
||||||
|
lastSwingTime = now;
|
||||||
|
if (swingRight) {
|
||||||
|
swingOffset++;
|
||||||
|
if (swingOffset >= swingRange)
|
||||||
|
swingRight = false;
|
||||||
|
} else {
|
||||||
|
swingOffset--;
|
||||||
|
if (swingOffset <= 0)
|
||||||
|
swingRight = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < lineCount; i++) {
|
||||||
|
bool isTitle = (i == 0);
|
||||||
|
int globalOptionIndex = (i - 1) + firstOptionToShow;
|
||||||
|
bool isSelectedOption = (!isTitle && globalOptionIndex >= 0 && globalOptionIndex == curSelected);
|
||||||
|
|
||||||
|
uint16_t visibleWidth = 64 - hPadding * 2;
|
||||||
|
if (totalLines > visibleTotalLines)
|
||||||
|
visibleWidth -= 6;
|
||||||
|
char lineBuffer[lineLengths[i] + 1];
|
||||||
|
strncpy(lineBuffer, lines[i], lineLengths[i]);
|
||||||
|
lineBuffer[lineLengths[i]] = '\0';
|
||||||
|
|
||||||
|
if (isTitle) {
|
||||||
|
if (visibleTotalLines == 1) {
|
||||||
|
display->setColor(BLACK);
|
||||||
|
display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight);
|
||||||
|
display->setColor(WHITE);
|
||||||
|
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer);
|
||||||
|
} else {
|
||||||
|
display->setColor(WHITE);
|
||||||
|
display->fillRect(boxLeft, boxTop, boxWidth, effectiveLineHeight);
|
||||||
|
display->setColor(BLACK);
|
||||||
|
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, boxTop, lineBuffer);
|
||||||
|
display->setColor(WHITE);
|
||||||
|
if (needs_bell) {
|
||||||
|
int bellY = boxTop + (FONT_HEIGHT_SMALL - 8) / 2;
|
||||||
|
display->drawXbm(boxLeft + (boxWidth - lineWidths[i]) / 2 - 10, bellY, 8, 8, bell_alert);
|
||||||
|
display->drawXbm(boxLeft + (boxWidth + lineWidths[i]) / 2 + 2, bellY, 8, 8, bell_alert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lineY = boxTop + effectiveLineHeight + 1;
|
||||||
|
} else if (isSelectedOption) {
|
||||||
|
display->setColor(WHITE);
|
||||||
|
display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight);
|
||||||
|
display->setColor(BLACK);
|
||||||
|
if (lineLengths[i] > 15 && lineWidths[i] > visibleWidth) {
|
||||||
|
int textX = boxLeft + hPadding + swingOffset;
|
||||||
|
display->drawString(textX, lineY - 1, lineBuffer);
|
||||||
|
} else {
|
||||||
|
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY - 1, lineBuffer);
|
||||||
|
}
|
||||||
|
display->setColor(WHITE);
|
||||||
|
lineY += effectiveLineHeight;
|
||||||
|
} else {
|
||||||
|
display->setColor(BLACK);
|
||||||
|
display->fillRect(boxLeft, lineY, boxWidth, effectiveLineHeight);
|
||||||
|
display->setColor(WHITE);
|
||||||
|
display->drawString(boxLeft + (boxWidth - lineWidths[i]) / 2, lineY, lineBuffer);
|
||||||
|
lineY += effectiveLineHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (totalLines > visibleTotalLines) {
|
||||||
|
const uint8_t scrollBarWidth = 5;
|
||||||
|
int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2;
|
||||||
|
int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight;
|
||||||
|
uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight;
|
||||||
|
float ratio = (float)visibleTotalLines / totalLines;
|
||||||
|
uint16_t indicatorHeight = std::max((int)(scrollBarHeight * ratio), 4);
|
||||||
|
float scrollRatio = (float)(firstOptionToShow + lineCount - visibleTotalLines) / (totalLines - visibleTotalLines);
|
||||||
|
uint16_t indicatorY = scrollBarY + scrollRatio * (scrollBarHeight - indicatorHeight);
|
||||||
|
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
|
||||||
|
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (needs_bell) {
|
||||||
|
if (isHighResolution && boxWidth <= 150)
|
||||||
|
boxWidth += 26;
|
||||||
|
if (!isHighResolution && boxWidth <= 100)
|
||||||
|
boxWidth += 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t screenHeight = display->height();
|
||||||
|
uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3;
|
||||||
|
uint8_t visibleTotalLines = std::min<uint8_t>(lineCount, (screenHeight - vPadding * 2) / effectiveLineHeight);
|
||||||
|
uint16_t contentHeight = visibleTotalLines * effectiveLineHeight;
|
||||||
|
uint16_t boxHeight = contentHeight + vPadding * 2;
|
||||||
|
if (visibleTotalLines == 1) {
|
||||||
|
boxHeight += (isHighResolution) ? 4 : 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t boxLeft = (display->width() / 2) - (boxWidth / 2);
|
||||||
|
if (totalLines > visibleTotalLines) {
|
||||||
|
boxWidth += (isHighResolution) ? 4 : 2;
|
||||||
|
}
|
||||||
|
int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
|
||||||
|
|
||||||
|
// === Draw Box ===
|
||||||
display->setColor(BLACK);
|
display->setColor(BLACK);
|
||||||
display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2);
|
display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2);
|
||||||
display->fillRect(boxLeft, boxTop - 2, boxWidth, 1);
|
display->fillRect(boxLeft, boxTop - 2, boxWidth, 1);
|
||||||
@@ -583,7 +675,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
|||||||
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
|
display->fillRect(boxLeft + boxWidth - 1, boxTop + boxHeight - 1, 1, 1);
|
||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
|
|
||||||
// Draw Content
|
// === Draw Content ===
|
||||||
int16_t lineY = boxTop + vPadding;
|
int16_t lineY = boxTop + vPadding;
|
||||||
for (int i = 0; i < lineCount; i++) {
|
for (int i = 0; i < lineCount; i++) {
|
||||||
int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2;
|
int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2;
|
||||||
@@ -612,47 +704,17 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
|||||||
lineY += (effectiveLineHeight - 2 - background_yOffset);
|
lineY += (effectiveLineHeight - 2 - background_yOffset);
|
||||||
} else {
|
} else {
|
||||||
// Pop-up
|
// Pop-up
|
||||||
// If this is the Signal line, center text + bars as one group
|
display->drawString(textX, lineY, lineBuffer);
|
||||||
bool isSignalLine = (graphics::bannerSignalBars >= 0 && strstr(lineBuffer, "Signal:") != nullptr);
|
|
||||||
if (isSignalLine) {
|
|
||||||
const int totalBars = 5;
|
|
||||||
const int barWidth = 3;
|
|
||||||
const int barSpacing = 2;
|
|
||||||
const int barHeightStep = 2;
|
|
||||||
const int gap = 6;
|
|
||||||
|
|
||||||
int textWidth = display->getStringWidth(lineBuffer, strlen(lineBuffer), true);
|
|
||||||
int barsWidth = totalBars * barWidth + (totalBars - 1) * barSpacing + gap;
|
|
||||||
int totalWidth = textWidth + barsWidth;
|
|
||||||
int groupStartX = boxLeft + (boxWidth - totalWidth) / 2;
|
|
||||||
|
|
||||||
display->drawString(groupStartX, lineY, lineBuffer);
|
|
||||||
|
|
||||||
int baseX = groupStartX + textWidth + gap;
|
|
||||||
int baseY = lineY + effectiveLineHeight - 1;
|
|
||||||
for (int b = 0; b < totalBars; b++) {
|
|
||||||
int barHeight = (b + 1) * barHeightStep;
|
|
||||||
int x = baseX + b * (barWidth + barSpacing);
|
|
||||||
int y = baseY - barHeight;
|
|
||||||
|
|
||||||
if (b < graphics::bannerSignalBars) {
|
|
||||||
display->fillRect(x, y, barWidth, barHeight);
|
|
||||||
} else {
|
|
||||||
display->drawRect(x, y, barWidth, barHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
display->drawString(textX, lineY, lineBuffer);
|
|
||||||
}
|
|
||||||
lineY += (effectiveLineHeight);
|
lineY += (effectiveLineHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll Bar (Thicker, inside box, not over title)
|
// === Scroll Bar (Thicker, inside box, not over title) ===
|
||||||
if (totalLines > visibleTotalLines) {
|
if (totalLines > visibleTotalLines) {
|
||||||
const uint8_t scrollBarWidth = 5;
|
const uint8_t scrollBarWidth = 5;
|
||||||
|
|
||||||
int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2;
|
int16_t scrollBarX = boxLeft + boxWidth - scrollBarWidth - 2;
|
||||||
int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight;
|
int16_t scrollBarY = boxTop + vPadding + effectiveLineHeight; // start after title line
|
||||||
uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight;
|
uint16_t scrollBarHeight = boxHeight - vPadding * 2 - effectiveLineHeight;
|
||||||
|
|
||||||
float ratio = (float)visibleTotalLines / totalLines;
|
float ratio = (float)visibleTotalLines / totalLines;
|
||||||
@@ -663,6 +725,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
|||||||
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
|
display->drawRect(scrollBarX, scrollBarY, scrollBarWidth, scrollBarHeight);
|
||||||
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
|
display->fillRect(scrollBarX + 1, indicatorY, scrollBarWidth - 2, indicatorHeight);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw the last text message we received
|
/// Draw the last text message we received
|
||||||
|
|||||||
@@ -6,7 +6,10 @@
|
|||||||
#include "NodeListRenderer.h"
|
#include "NodeListRenderer.h"
|
||||||
#include "UIRenderer.h"
|
#include "UIRenderer.h"
|
||||||
#include "airtime.h"
|
#include "airtime.h"
|
||||||
|
#include "configuration.h"
|
||||||
#include "gps/GeoCoord.h"
|
#include "gps/GeoCoord.h"
|
||||||
|
#include "graphics/Screen.h"
|
||||||
|
#include "graphics/ScreenFonts.h"
|
||||||
#include "graphics/SharedUIDisplay.h"
|
#include "graphics/SharedUIDisplay.h"
|
||||||
#include "graphics/TimeFormatters.h"
|
#include "graphics/TimeFormatters.h"
|
||||||
#include "graphics/images.h"
|
#include "graphics/images.h"
|
||||||
@@ -26,16 +29,6 @@ namespace graphics
|
|||||||
NodeNum UIRenderer::currentFavoriteNodeNum = 0;
|
NodeNum UIRenderer::currentFavoriteNodeNum = 0;
|
||||||
std::vector<meshtastic_NodeInfoLite *> graphics::UIRenderer::favoritedNodes;
|
std::vector<meshtastic_NodeInfoLite *> graphics::UIRenderer::favoritedNodes;
|
||||||
|
|
||||||
static inline void drawSatelliteIcon(OLEDDisplay *display, int16_t x, int16_t y)
|
|
||||||
{
|
|
||||||
int yOffset = (currentResolution == ScreenResolution::High) ? -5 : 1;
|
|
||||||
if (currentResolution == ScreenResolution::High) {
|
|
||||||
NodeListRenderer::drawScaledXBitmap16x16(x, y + yOffset, imgSatellite_width, imgSatellite_height, imgSatellite, display);
|
|
||||||
} else {
|
|
||||||
display->drawXbm(x + 1, y + yOffset, imgSatellite_width, imgSatellite_height, imgSatellite);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void graphics::UIRenderer::rebuildFavoritedNodes()
|
void graphics::UIRenderer::rebuildFavoritedNodes()
|
||||||
{
|
{
|
||||||
favoritedNodes.clear();
|
favoritedNodes.clear();
|
||||||
@@ -63,7 +56,7 @@ extern uint32_t dopThresholds[5];
|
|||||||
void UIRenderer::drawGps(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps)
|
void UIRenderer::drawGps(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps)
|
||||||
{
|
{
|
||||||
// Draw satellite image
|
// Draw satellite image
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
NodeListRenderer::drawScaledXBitmap16x16(x, y - 2, imgSatellite_width, imgSatellite_height, imgSatellite, display);
|
NodeListRenderer::drawScaledXBitmap16x16(x, y - 2, imgSatellite_width, imgSatellite_height, imgSatellite, display);
|
||||||
} else {
|
} else {
|
||||||
display->drawXbm(x + 1, y + 1, imgSatellite_width, imgSatellite_height, imgSatellite);
|
display->drawXbm(x + 1, y + 1, imgSatellite_width, imgSatellite_height, imgSatellite);
|
||||||
@@ -83,7 +76,7 @@ void UIRenderer::drawGps(OLEDDisplay *display, int16_t x, int16_t y, const mesht
|
|||||||
} else {
|
} else {
|
||||||
snprintf(textString, sizeof(textString), "%u sats", gps->getNumSatellites());
|
snprintf(textString, sizeof(textString), "%u sats", gps->getNumSatellites());
|
||||||
}
|
}
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
display->drawString(x + 18, y, textString);
|
display->drawString(x + 18, y, textString);
|
||||||
} else {
|
} else {
|
||||||
display->drawString(x + 11, y, textString);
|
display->drawString(x + 11, y, textString);
|
||||||
@@ -251,16 +244,16 @@ void UIRenderer::drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y,
|
|||||||
|
|
||||||
// Draw nodes status
|
// Draw nodes status
|
||||||
void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::NodeStatus *nodeStatus, int node_offset,
|
void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::NodeStatus *nodeStatus, int node_offset,
|
||||||
bool show_total, const char *additional_words)
|
bool show_total, String additional_words)
|
||||||
{
|
{
|
||||||
char usersString[20];
|
char usersString[20];
|
||||||
int nodes_online = (nodeStatus->getNumOnline() > 0) ? nodeStatus->getNumOnline() + node_offset : 0;
|
int nodes_online = (nodeStatus->getNumOnline() > 0) ? nodeStatus->getNumOnline() + node_offset : 0;
|
||||||
|
|
||||||
snprintf(usersString, sizeof(usersString), "%d %s", nodes_online, additional_words);
|
snprintf(usersString, sizeof(usersString), "%d %s", nodes_online, additional_words.c_str());
|
||||||
|
|
||||||
if (show_total) {
|
if (show_total) {
|
||||||
int nodes_total = (nodeStatus->getNumTotal() > 0) ? nodeStatus->getNumTotal() + node_offset : 0;
|
int nodes_total = (nodeStatus->getNumTotal() > 0) ? nodeStatus->getNumTotal() + node_offset : 0;
|
||||||
snprintf(usersString, sizeof(usersString), "%d/%d %s", nodes_online, nodes_total, additional_words);
|
snprintf(usersString, sizeof(usersString), "%d/%d %s", nodes_online, nodes_total, additional_words.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
|
||||||
@@ -268,19 +261,19 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
|
|||||||
defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796)) && \
|
defined(HACKADAY_COMMUNICATOR) || defined(USE_ST7796)) && \
|
||||||
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
!defined(DISPLAY_FORCE_SMALL_FONTS)
|
||||||
|
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
NodeListRenderer::drawScaledXBitmap16x16(x, y - 1, 8, 8, imgUser, display);
|
NodeListRenderer::drawScaledXBitmap16x16(x, y - 1, 8, 8, imgUser, display);
|
||||||
} else {
|
} else {
|
||||||
display->drawFastImage(x, y + 3, 8, 8, imgUser);
|
display->drawFastImage(x, y + 3, 8, 8, imgUser);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
NodeListRenderer::drawScaledXBitmap16x16(x, y - 1, 8, 8, imgUser, display);
|
NodeListRenderer::drawScaledXBitmap16x16(x, y - 1, 8, 8, imgUser, display);
|
||||||
} else {
|
} else {
|
||||||
display->drawFastImage(x, y + 1, 8, 8, imgUser);
|
display->drawFastImage(x, y + 1, 8, 8, imgUser);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
int string_offset = (currentResolution == ScreenResolution::High) ? 9 : 0;
|
int string_offset = (isHighResolution) ? 9 : 0;
|
||||||
display->drawString(x + 10 + string_offset, y - 2, usersString);
|
display->drawString(x + 10 + string_offset, y - 2, usersString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,12 +321,11 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
|||||||
int line = 1; // which slot to use next
|
int line = 1; // which slot to use next
|
||||||
std::string usernameStr;
|
std::string usernameStr;
|
||||||
// === 1. Long Name (always try to show first) ===
|
// === 1. Long Name (always try to show first) ===
|
||||||
const char *username;
|
#if defined(M5STACK_UNITC6L)
|
||||||
if (currentResolution == ScreenResolution::UltraLow) {
|
const char *username = (node->has_user && node->user.long_name[0]) ? node->user.short_name : nullptr;
|
||||||
username = (node->has_user && node->user.long_name[0]) ? node->user.short_name : nullptr;
|
#else
|
||||||
} else {
|
const char *username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr;
|
||||||
username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr;
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
if (username) {
|
if (username) {
|
||||||
usernameStr = sanitizeString(username); // Sanitize the incoming long_name just in case
|
usernameStr = sanitizeString(username); // Sanitize the incoming long_name just in case
|
||||||
@@ -509,7 +501,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
|||||||
const int margin = 4;
|
const int margin = 4;
|
||||||
// --------- PATCH FOR EINK NAV BAR (ONLY CHANGE BELOW) -----------
|
// --------- PATCH FOR EINK NAV BAR (ONLY CHANGE BELOW) -----------
|
||||||
#if defined(USE_EINK)
|
#if defined(USE_EINK)
|
||||||
const int iconSize = (currentResolution == ScreenResolution::High) ? 16 : 8;
|
const int iconSize = (isHighResolution) ? 16 : 8;
|
||||||
const int navBarHeight = iconSize + 6;
|
const int navBarHeight = iconSize + 6;
|
||||||
#else
|
#else
|
||||||
const int navBarHeight = 0;
|
const int navBarHeight = 0;
|
||||||
@@ -567,11 +559,11 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
|
|
||||||
// === Header ===
|
// === Header ===
|
||||||
if (currentResolution == ScreenResolution::UltraLow) {
|
#if defined(M5STACK_UNITC6L)
|
||||||
graphics::drawCommonHeader(display, x, y, "Home");
|
graphics::drawCommonHeader(display, x, y, "Home");
|
||||||
} else {
|
#else
|
||||||
graphics::drawCommonHeader(display, x, y, "");
|
graphics::drawCommonHeader(display, x, y, "");
|
||||||
}
|
#endif
|
||||||
|
|
||||||
// === Content below header ===
|
// === Content below header ===
|
||||||
|
|
||||||
@@ -586,15 +578,15 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
config.display.heading_bold = false;
|
config.display.heading_bold = false;
|
||||||
|
|
||||||
// Display Region and Channel Utilization
|
// Display Region and Channel Utilization
|
||||||
if (currentResolution == ScreenResolution::UltraLow) {
|
#if defined(M5STACK_UNITC6L)
|
||||||
drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
|
drawNodes(display, x, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
|
||||||
} else {
|
#else
|
||||||
drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
|
drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
|
||||||
}
|
#endif
|
||||||
char uptimeStr[32] = "";
|
char uptimeStr[32] = "";
|
||||||
if (currentResolution != ScreenResolution::UltraLow) {
|
#if !defined(M5STACK_UNITC6L)
|
||||||
getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr));
|
getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr));
|
||||||
}
|
#endif
|
||||||
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
|
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
|
||||||
|
|
||||||
// === Second Row: Satellites and Voltage ===
|
// === Second Row: Satellites and Voltage ===
|
||||||
@@ -608,8 +600,15 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
} else {
|
} else {
|
||||||
displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off";
|
displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off";
|
||||||
}
|
}
|
||||||
drawSatelliteIcon(display, x, getTextPositions(display)[line]);
|
int yOffset = (isHighResolution) ? 3 : 1;
|
||||||
int xOffset = (currentResolution == ScreenResolution::High) ? 6 : 0;
|
if (isHighResolution) {
|
||||||
|
NodeListRenderer::drawScaledXBitmap16x16(x, getTextPositions(display)[line] + yOffset - 5, imgSatellite_width,
|
||||||
|
imgSatellite_height, imgSatellite, display);
|
||||||
|
} else {
|
||||||
|
display->drawXbm(x + 1, getTextPositions(display)[line] + yOffset, imgSatellite_width, imgSatellite_height,
|
||||||
|
imgSatellite);
|
||||||
|
}
|
||||||
|
int xOffset = (isHighResolution) ? 6 : 0;
|
||||||
display->drawString(x + 11 + xOffset, getTextPositions(display)[line], displayLine);
|
display->drawString(x + 11 + xOffset, getTextPositions(display)[line], displayLine);
|
||||||
} else {
|
} else {
|
||||||
UIRenderer::drawGps(display, 0, getTextPositions(display)[line], gpsStatus);
|
UIRenderer::drawGps(display, 0, getTextPositions(display)[line], gpsStatus);
|
||||||
@@ -648,22 +647,21 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
char chUtilPercentage[10];
|
char chUtilPercentage[10];
|
||||||
snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent());
|
snprintf(chUtilPercentage, sizeof(chUtilPercentage), "%2.0f%%", airTime->channelUtilizationPercent());
|
||||||
|
|
||||||
int chUtil_x = (currentResolution == ScreenResolution::High) ? display->getStringWidth(chUtil) + 10
|
int chUtil_x = (isHighResolution) ? display->getStringWidth(chUtil) + 10 : display->getStringWidth(chUtil) + 5;
|
||||||
: display->getStringWidth(chUtil) + 5;
|
|
||||||
int chUtil_y = getTextPositions(display)[line] + 3;
|
int chUtil_y = getTextPositions(display)[line] + 3;
|
||||||
|
|
||||||
int chutil_bar_width = (currentResolution == ScreenResolution::High) ? 100 : 50;
|
int chutil_bar_width = (isHighResolution) ? 100 : 50;
|
||||||
if (!config.bluetooth.enabled) {
|
if (!config.bluetooth.enabled) {
|
||||||
#if defined(USE_EINK)
|
#if defined(USE_EINK)
|
||||||
chutil_bar_width = (currentResolution == ScreenResolution::High) ? 50 : 30;
|
chutil_bar_width = (isHighResolution) ? 50 : 30;
|
||||||
#else
|
#else
|
||||||
chutil_bar_width = (currentResolution == ScreenResolution::High) ? 80 : 40;
|
chutil_bar_width = (isHighResolution) ? 80 : 40;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
int chutil_bar_height = (currentResolution == ScreenResolution::High) ? 12 : 7;
|
int chutil_bar_height = (isHighResolution) ? 12 : 7;
|
||||||
int extraoffset = (currentResolution == ScreenResolution::High) ? 6 : 3;
|
int extraoffset = (isHighResolution) ? 6 : 3;
|
||||||
if (!config.bluetooth.enabled) {
|
if (!config.bluetooth.enabled) {
|
||||||
extraoffset = (currentResolution == ScreenResolution::High) ? 6 : 1;
|
extraoffset = (isHighResolution) ? 6 : 1;
|
||||||
}
|
}
|
||||||
int chutil_percent = airTime->channelUtilizationPercent();
|
int chutil_percent = airTime->channelUtilizationPercent();
|
||||||
|
|
||||||
@@ -723,7 +721,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
// === Fourth & Fifth Rows: Node Identity ===
|
// === Fourth & Fifth Rows: Node Identity ===
|
||||||
int textWidth = 0;
|
int textWidth = 0;
|
||||||
int nameX = 0;
|
int nameX = 0;
|
||||||
int yOffset = (currentResolution == ScreenResolution::High) ? 0 : 5;
|
int yOffset = (isHighResolution) ? 0 : 5;
|
||||||
std::string longNameStr;
|
std::string longNameStr;
|
||||||
|
|
||||||
if (ourNode && ourNode->has_user && strlen(ourNode->user.long_name) > 0) {
|
if (ourNode && ourNode->has_user && strlen(ourNode->user.long_name) > 0) {
|
||||||
@@ -761,7 +759,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
|
|
||||||
// Start Functions to write date/time to the screen
|
// Start Functions to write date/time to the screen
|
||||||
// Helper function to check if a year is a leap year
|
// Helper function to check if a year is a leap year
|
||||||
constexpr bool isLeapYear(int year)
|
bool isLeapYear(int year)
|
||||||
{
|
{
|
||||||
return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
|
return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0));
|
||||||
}
|
}
|
||||||
@@ -992,8 +990,15 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
|||||||
} else {
|
} else {
|
||||||
displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off";
|
displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off";
|
||||||
}
|
}
|
||||||
drawSatelliteIcon(display, x, getTextPositions(display)[line]);
|
int yOffset = (isHighResolution) ? 3 : 1;
|
||||||
int xOffset = (currentResolution == ScreenResolution::High) ? 6 : 0;
|
if (isHighResolution) {
|
||||||
|
NodeListRenderer::drawScaledXBitmap16x16(x, getTextPositions(display)[line] + yOffset - 5, imgSatellite_width,
|
||||||
|
imgSatellite_height, imgSatellite, display);
|
||||||
|
} else {
|
||||||
|
display->drawXbm(x + 1, getTextPositions(display)[line] + yOffset, imgSatellite_width, imgSatellite_height,
|
||||||
|
imgSatellite);
|
||||||
|
}
|
||||||
|
int xOffset = (isHighResolution) ? 6 : 0;
|
||||||
display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine);
|
display->drawString(x + 11 + xOffset, getTextPositions(display)[line++], displayLine);
|
||||||
} else {
|
} else {
|
||||||
// Onboard GPS
|
// Onboard GPS
|
||||||
@@ -1151,7 +1156,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
|||||||
void UIRenderer::drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
void UIRenderer::drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
static const uint8_t xbm[] = USERPREFS_OEM_IMAGE_DATA;
|
static const uint8_t xbm[] = USERPREFS_OEM_IMAGE_DATA;
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
display->drawXbm(x + (SCREEN_WIDTH - USERPREFS_OEM_IMAGE_WIDTH) / 2,
|
display->drawXbm(x + (SCREEN_WIDTH - USERPREFS_OEM_IMAGE_WIDTH) / 2,
|
||||||
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - USERPREFS_OEM_IMAGE_HEIGHT) / 2 + 2, USERPREFS_OEM_IMAGE_WIDTH,
|
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - USERPREFS_OEM_IMAGE_HEIGHT) / 2 + 2, USERPREFS_OEM_IMAGE_WIDTH,
|
||||||
USERPREFS_OEM_IMAGE_HEIGHT, xbm);
|
USERPREFS_OEM_IMAGE_HEIGHT, xbm);
|
||||||
@@ -1176,7 +1181,7 @@ void UIRenderer::drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, O
|
|||||||
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
const char *title = USERPREFS_OEM_TEXT;
|
const char *title = USERPREFS_OEM_TEXT;
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
|
display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
|
||||||
}
|
}
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
@@ -1220,15 +1225,15 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
lastFrameChangeTime = millis();
|
lastFrameChangeTime = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
const int iconSize = (currentResolution == ScreenResolution::High) ? 16 : 8;
|
const int iconSize = isHighResolution ? 16 : 8;
|
||||||
const int spacing = (currentResolution == ScreenResolution::High) ? 8 : 4;
|
const int spacing = isHighResolution ? 8 : 4;
|
||||||
const int bigOffset = (currentResolution == ScreenResolution::High) ? 1 : 0;
|
const int bigOffset = isHighResolution ? 1 : 0;
|
||||||
|
|
||||||
const size_t totalIcons = screen->indicatorIcons.size();
|
const size_t totalIcons = screen->indicatorIcons.size();
|
||||||
if (totalIcons == 0)
|
if (totalIcons == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const int navPadding = (currentResolution == ScreenResolution::High) ? 24 : 12; // padding per side
|
const int navPadding = isHighResolution ? 24 : 12; // padding per side
|
||||||
|
|
||||||
int usableWidth = SCREEN_WIDTH - (navPadding * 2);
|
int usableWidth = SCREEN_WIDTH - (navPadding * 2);
|
||||||
if (usableWidth < iconSize)
|
if (usableWidth < iconSize)
|
||||||
@@ -1295,7 +1300,7 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
display->setColor(BLACK);
|
display->setColor(BLACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (isHighResolution) {
|
||||||
NodeListRenderer::drawScaledXBitmap16x16(x, y, 8, 8, icon, display);
|
NodeListRenderer::drawScaledXBitmap16x16(x, y, 8, 8, icon, display);
|
||||||
} else {
|
} else {
|
||||||
display->drawXbm(x, y, iconSize, iconSize, icon);
|
display->drawXbm(x, y, iconSize, iconSize, icon);
|
||||||
@@ -1310,7 +1315,7 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
auto drawArrow = [&](bool rightSide) {
|
auto drawArrow = [&](bool rightSide) {
|
||||||
display->setColor(WHITE);
|
display->setColor(WHITE);
|
||||||
|
|
||||||
const int offset = (currentResolution == ScreenResolution::High) ? 3 : 1;
|
const int offset = isHighResolution ? 3 : 1;
|
||||||
const int halfH = rectHeight / 2;
|
const int halfH = rectHeight / 2;
|
||||||
|
|
||||||
const int top = (y - 2) + (rectHeight - halfH) / 2;
|
const int top = (y - 2) + (rectHeight - halfH) / 2;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class UIRenderer
|
|||||||
public:
|
public:
|
||||||
// Common UI elements
|
// Common UI elements
|
||||||
static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::NodeStatus *nodeStatus,
|
static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::NodeStatus *nodeStatus,
|
||||||
int node_offset = 0, bool show_total = true, const char *additional_words = "");
|
int node_offset = 0, bool show_total = true, String additional_words = "");
|
||||||
|
|
||||||
// GPS status functions
|
// GPS status functions
|
||||||
static void drawGps(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
|
static void drawGps(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
|
||||||
@@ -43,6 +43,9 @@ class UIRenderer
|
|||||||
static void drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
|
static void drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
|
||||||
static void drawGpsPowerStatus(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
|
static void drawGpsPowerStatus(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gpsStatus);
|
||||||
|
|
||||||
|
// Layout and utility functions
|
||||||
|
static void drawScrollbar(OLEDDisplay *display, int visibleItems, int totalItems, int scrollIndex, int x, int startY);
|
||||||
|
|
||||||
// Overlay and special screens
|
// Overlay and special screens
|
||||||
static void drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *text);
|
static void drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *text);
|
||||||
|
|
||||||
@@ -80,6 +83,8 @@ class UIRenderer
|
|||||||
static std::string drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds);
|
static std::string drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds);
|
||||||
static int formatDateTime(char *buffer, size_t bufferSize, uint32_t rtc_sec, OLEDDisplay *display, bool showTime);
|
static int formatDateTime(char *buffer, size_t bufferSize, uint32_t rtc_sec, OLEDDisplay *display, bool showTime);
|
||||||
|
|
||||||
|
// Message filtering
|
||||||
|
static bool shouldDrawMessage(const meshtastic_MeshPacket *packet);
|
||||||
// Check if the display can render a string (detect special chars; emoji)
|
// Check if the display can render a string (detect special chars; emoji)
|
||||||
static bool haveGlyphs(const char *str);
|
static bool haveGlyphs(const char *str);
|
||||||
}; // namespace UIRenderer
|
}; // namespace UIRenderer
|
||||||
|
|||||||
@@ -13,81 +13,41 @@ const Emote emotes[] = {
|
|||||||
{"\U0001F44E", thumbdown, thumbs_width, thumbs_height}, // 👎 Thumbs Down
|
{"\U0001F44E", thumbdown, thumbs_width, thumbs_height}, // 👎 Thumbs Down
|
||||||
|
|
||||||
// --- Smileys (Multiple Unicode Aliases) ---
|
// --- Smileys (Multiple Unicode Aliases) ---
|
||||||
{"\U0001F60A", smiling_eyes, smiling_eyes_width, smiling_eyes_height}, // 😊 Smiling Eyes
|
{"\U0001F60A", Smiling_Eyes, Smiling_Eyes_width, Smiling_Eyes_height}, // 😊 Smiling Eyes
|
||||||
{"\U0001F600", grinning, grinning_width, grinning_height}, // 😀 Grinning Face
|
{"\U0001F600", Grinning, Grinning_width, Grinning_height}, // 😀 Grinning Face
|
||||||
{"\U0001F642", slightly_smiling, slightly_smiling_width, slightly_smiling_height}, // 🙂 Slightly Smiling Face
|
{"\U0001F642", Slightly_Smiling, Slightly_Smiling_width, Slightly_Smiling_height}, // 🙂 Slightly Smiling Face
|
||||||
{"\U0001F609", winking_face, winking_face_width, winking_face_height}, // 😉 Winking Face
|
{"\U0001F609", Winking_Face, Winking_Face_width, Winking_Face_height}, // 😉 Winking Face
|
||||||
{"\U0001F601", grinning_smiling_eyes, grinning_smiling_eyes_width, grinning_smiling_eyes_height}, // 😁 Grinning Smiling Eyes
|
{"\U0001F601", Grinning_Smiling_Eyes, Grinning_Smiling_Eyes_width, Grinning_Smiling_Eyes_height}, // 😁 Grinning Smiling Eyes
|
||||||
{"\U0001F60D", heart_eyes, heart_eyes_width, heart_eyes_height}, // 😍 Heart Eyes
|
{"\U0001F60D", Heart_eyes, Heart_eyes_width, Heart_eyes_height}, // 😍 Heart Eyes
|
||||||
{"\U0001F970", heart_smile, heart_smile_width, heart_smile_height}, // 🥰 Smiling Face with Hearts
|
{"\U0001F970", heart_smile, heart_smile_width, heart_smile_height}, // 🥰 Smiling Face with Hearts
|
||||||
|
|
||||||
// --- Question/Alert ---
|
// --- Question/Alert ---
|
||||||
{"\u2753", question, question_width, question_height}, // ❓ Question Mark
|
{"\u2753", question, question_width, question_height}, // ❓ Question Mark
|
||||||
{"\u203C\uFE0F", bang, bang_width, bang_height}, // ‼️ Double Exclamation Mark
|
{"\u203C\uFE0F", bang, bang_width, bang_height}, // ‼️ Double Exclamation Mark
|
||||||
{"\u26A0\uFE0F", caution, caution_width, caution_height}, // ⚠️ Warning Sign
|
|
||||||
|
|
||||||
// --- Laughing Faces ---
|
// --- Laughing Faces ---
|
||||||
{"\U0001F602", haha, haha_width, haha_height}, // 😂 Face with Tears of Joy
|
{"\U0001F602", haha, haha_width, haha_height}, // 😂 Face with Tears of Joy
|
||||||
{"\U0001F923", rofl, rofl_width, rofl_height}, // 🤣 Rolling on the Floor Laughing
|
{"\U0001F923", ROFL, ROFL_width, ROFL_height}, // 🤣 Rolling on the Floor Laughing
|
||||||
{"\U0001F606", smiling_closed_eyes, smiling_closed_eyes_width, smiling_closed_eyes_height}, // 😆 Smiling Closed Eyes
|
{"\U0001F606", Smiling_Closed_Eyes, Smiling_Closed_Eyes_width, Smiling_Closed_Eyes_height}, // 😆 Smiling Closed Eyes
|
||||||
{"\U0001F605", haha, haha_width, haha_height}, // 😅 Smiling with Sweat
|
{"\U0001F605", haha, haha_width, haha_height}, // 😅 Smiling with Sweat
|
||||||
{"\U0001F604", grinning_smiling_eyes_2, grinning_smiling_eyes_2_width,
|
{"\U0001F604", Grinning_SmilingEyes2, Grinning_SmilingEyes2_width,
|
||||||
grinning_smiling_eyes_2_height}, // 😄 Grinning Face with Smiling Eyes
|
Grinning_SmilingEyes2_height}, // 😄 Grinning Face with Smiling Eyes
|
||||||
{"\U0001F62D", loudly_crying_face, loudly_crying_face_width, loudly_crying_face_height}, // 😭 Loudly Crying Face
|
{"\U0001F62D", Loudly_Crying_Face, Loudly_Crying_Face_width, Loudly_Crying_Face_height}, // 😭 Loudly Crying Face
|
||||||
{"\U0001F92E", vomiting, vomiting_width, vomiting_height}, // 🤮 Face Vomiting
|
|
||||||
{"\U0001F60E", cool, cool_width, cool_height}, // 😎 Smiling Face with Sunglasses
|
|
||||||
{"\U0001F440", eyes, eyes_width, eyes_height}, // 👀 Eyes
|
|
||||||
{"\U0001F441\uFE0F", eye, eye_width, eye_height}, // 👁️ Eye
|
|
||||||
|
|
||||||
// --- Gestures and People ---
|
// --- Gestures and People ---
|
||||||
{"\U0001F44B", wave_icon, wave_icon_width, wave_icon_height}, // 👋 Waving Hand
|
{"\U0001F44B", wave_icon, wave_icon_width, wave_icon_height}, // 👋 Waving Hand
|
||||||
{"\u270C\uFE0F", peace_sign, peace_sign_width, peace_sign_height}, // ✌️ Victory Hand
|
{"\u270C\uFE0F", peace_sign, peace_sign_width, peace_sign_height}, // ✌️ Victory Hand
|
||||||
{"\U0001F596", vulcan_salute, vulcan_salute_width, vulcan_salute_height}, // 🖖 Vulcan Salute
|
{"\U0001F596", vulcan_salute, vulcan_salute_width, vulcan_salute_height}, // 🖖 Vulcan Salute
|
||||||
{"\U0001F64F", praying, praying_width, praying_height}, // 🙏 Praying Hands
|
{"\U0001F64F", Praying, Praying_width, Praying_height}, // 🙏 Praying Hands
|
||||||
{"\U0001F4AA", strong, strong_width, strong_height}, // 💪 Flexed Biceps
|
|
||||||
{"\U0001F937", shrug, shrug_width, shrug_height}, // 🤷 Person Shrugging
|
|
||||||
{"\U0001F920", cowboy, cowboy_width, cowboy_height}, // 🤠 Cowboy Hat Face
|
{"\U0001F920", cowboy, cowboy_width, cowboy_height}, // 🤠 Cowboy Hat Face
|
||||||
{"\U0001F3A7", deadmau5, deadmau5_width, deadmau5_height}, // 🎧 Headphones
|
{"\U0001F3A7", deadmau5, deadmau5_width, deadmau5_height}, // 🎧 Headphones
|
||||||
|
|
||||||
// --- Symbols ---
|
|
||||||
{"\u2714\uFE0F", check_mark, check_mark_width, check_mark_height}, // ✔️ Check Mark
|
|
||||||
{"\u2705", check_mark, check_mark_width, check_mark_height}, // ✅ Check Mark Button
|
|
||||||
{"\u2611\uFE0F", check_mark, check_mark_width, check_mark_height}, // ☑️ Check Box with Check
|
|
||||||
{"\U0001F3E0", house, house_width, house_height}, // 🏠 House
|
|
||||||
|
|
||||||
// --- Weather ---
|
// --- Weather ---
|
||||||
{"\u2600", sun, sun_width, sun_height}, // ☀ Sun (without variation selector)
|
{"\u2600", sun, sun_width, sun_height}, // ☀ Sun (without variation selector)
|
||||||
{"\u2600\uFE0F", sun, sun_width, sun_height}, // ☀️ Sun (with variation selector)
|
{"\u2600\uFE0F", sun, sun_width, sun_height}, // ☀️ Sun (with variation selector)
|
||||||
{"\U0001F327\uFE0F", rain, rain_width, rain_height}, // 🌧️ Cloud with Rain
|
{"\U0001F327\uFE0F", rain, rain_width, rain_height}, // 🌧️ Cloud with Rain
|
||||||
{"\u2601\uFE0F", cloud, cloud_width, cloud_height}, // ☁️ Cloud
|
{"\u2601\uFE0F", cloud, cloud_width, cloud_height}, // ☁️ Cloud
|
||||||
{"\U0001F32B\uFE0F", fog, fog_width, fog_height}, // 🌫️ Fog
|
{"\U0001F32B\uFE0F", fog, fog_width, fog_height}, // 🌫️ Fog
|
||||||
{"\u2744\uFE0F", snowflake, snowflake_width, snowflake_height}, // ❄️ Snowflake
|
|
||||||
{"\U0001F4A7", drop, drop_width, drop_height}, // 💧 Droplet
|
|
||||||
{"\U0001F321\uFE0F", thermometer, thermometer_width, thermometer_height}, // 🌡️ Thermometer
|
|
||||||
{"\U0001F326\uFE0F", sun_behind_raincloud, sun_behind_raincloud_width,
|
|
||||||
sun_behind_raincloud_height}, // 🌦️ Sun Behind Rain Cloud
|
|
||||||
{"\u26C5", sun_behind_cloud, sun_behind_cloud_width, sun_behind_cloud_height}, // ⛅ Sun Behind Cloud
|
|
||||||
{"\u26C5\uFE0F", sun_behind_cloud, sun_behind_cloud_width, sun_behind_cloud_height}, // ⛅️ Sun Behind Cloud
|
|
||||||
{"\U0001F328\uFE0F", cloud_with_snow, cloud_with_snow_width, cloud_with_snow_height}, // 🌨️ Cloud with Snow
|
|
||||||
{"\U0001F329\uFE0F", cloud_with_lightning, cloud_with_lightning_width,
|
|
||||||
cloud_with_lightning_height}, // 🌩️ Cloud with Lightning
|
|
||||||
{"\u26C8", cloud_with_lightning_rain, cloud_with_lightning_rain_width,
|
|
||||||
cloud_with_lightning_rain_height}, // ⛈ Cloud with Lightning and Rain
|
|
||||||
{"\u26C8\uFE0F", cloud_with_lightning_rain, cloud_with_lightning_rain_width,
|
|
||||||
cloud_with_lightning_rain_height}, // ⛈️ Cloud with Lightning and Rain
|
|
||||||
{"\U0001F32C\uFE0F", wind_face, wind_face_width, wind_face_height}, // 🌬️ Wind Face
|
|
||||||
|
|
||||||
// --- Moon Phases ---
|
|
||||||
{"\U0001F311", new_moon, new_moon_width, new_moon_height}, // 🌑 New Moon
|
|
||||||
{"\U0001F312", waxing_crescent_moon, waxing_crescent_moon_width, waxing_crescent_moon_height}, // 🌒 Waxing Crescent Moon
|
|
||||||
{"\U0001F313", first_quarter_moon, first_quarter_moon_width, first_quarter_moon_height}, // 🌓 First Quarter Moon
|
|
||||||
{"\U0001F314", waxing_gibbous_moon, waxing_gibbous_moon_width, waxing_gibbous_moon_height}, // 🌔 Waxing Gibbous Moon
|
|
||||||
{"\U0001F315", full_moon, full_moon_width, full_moon_height}, // 🌕 Full Moon
|
|
||||||
{"\U0001F316", waning_gibbous_moon, waning_gibbous_moon_width, waning_gibbous_moon_height}, // 🌖 Waning Gibbous Moon
|
|
||||||
{"\U0001F317", last_quarter_moon, last_quarter_moon_width, last_quarter_moon_height}, // 🌗 Last Quarter Moon
|
|
||||||
{"\U0001F318", waning_crescent_moon, waning_crescent_moon_width, waning_crescent_moon_height}, // 🌘 Waning Crescent Moon
|
|
||||||
{"\U0001F31B", first_quarter_moon_face, first_quarter_moon_face_width,
|
|
||||||
first_quarter_moon_face_height}, // 🌛 First Quarter Moon Face
|
|
||||||
|
|
||||||
// --- Misc Faces ---
|
// --- Misc Faces ---
|
||||||
{"\U0001F608", devil, devil_width, devil_height}, // 😈 Smiling Face with Horns
|
{"\U0001F608", devil, devil_width, devil_height}, // 😈 Smiling Face with Horns
|
||||||
@@ -107,49 +67,13 @@ const Emote emotes[] = {
|
|||||||
{"\U0001F498", heart, heart_width, heart_height}, // 💘 Heart with Arrow
|
{"\U0001F498", heart, heart_width, heart_height}, // 💘 Heart with Arrow
|
||||||
|
|
||||||
// --- Objects ---
|
// --- Objects ---
|
||||||
{"\U0001F4A9", poo, poo_width, poo_height}, // 💩 Pile of Poo
|
{"\U0001F4A9", poo, poo_width, poo_height}, // 💩 Pile of Poo
|
||||||
{"\U0001F514", bell_icon, bell_icon_width, bell_icon_height}, // 🔔 Bell
|
{"\U0001F514", bell_icon, bell_icon_width, bell_icon_height}, // 🔔 Bell
|
||||||
{"\U0001F4CB", clipboard, clipboard_width, clipboard_height}, // 📋 Clipboard
|
{"\U0001F36A", cookie, cookie_width, cookie_height}, // 🍪 Cookie
|
||||||
{"\U0001F36A", cookie, cookie_width, cookie_height}, // 🍪 Cookie
|
{"\U0001F525", Fire, Fire_width, Fire_height}, // 🔥 Fire
|
||||||
{"\U0001F370", shortcake, shortcake_width, shortcake_height}, // 🍰 Shortcake
|
{"\u2728", Sparkles, Sparkles_width, Sparkles_height}, // ✨ Sparkles
|
||||||
{"\U0001F351", peach, peach_width, peach_height}, // 🍑 Peach
|
{"\U0001F573\uFE0F", hole, hole_width, hole_height}, // 🕳️ Hole
|
||||||
{"\U0001F983", turkey, turkey_width, turkey_height}, // 🦃 Turkey
|
{"\U0001F3B3", bowling, bowling_width, bowling_height} // 🎳 Bowling
|
||||||
{"\U0001F357", turkey_leg, turkey_leg_width, turkey_leg_height}, // 🍗 Poultry Leg
|
|
||||||
{"\U0001F525", fire, fire_width, fire_height}, // 🔥 Fire
|
|
||||||
{"\u2728", sparkles, sparkles_width, sparkles_height}, // ✨ Sparkles
|
|
||||||
{"\U0001F573\uFE0F", hole, hole_width, hole_height}, // 🕳️ Hole
|
|
||||||
{"\U0001F3B3", bowling, bowling_width, bowling_height}, // 🎳 Bowling
|
|
||||||
|
|
||||||
// --- Arrows ---
|
|
||||||
{"\u2193", downwards_arrow, downwards_arrow_width, downwards_arrow_height}, // ↓ Downwards Arrow
|
|
||||||
{"\u2193\uFE0E", downwards_arrow, downwards_arrow_width, downwards_arrow_height}, // ↓︎ Downwards Arrow (text)
|
|
||||||
{"\u2193\uFE0F", downwards_arrow, downwards_arrow_width, downwards_arrow_height}, // ↓️ Downwards Arrow (emoji)
|
|
||||||
{"\u2199", south_west_arrow, south_west_arrow_width, south_west_arrow_height}, // ↙ South West Arrow
|
|
||||||
{"\u2199\uFE0E", south_west_arrow, south_west_arrow_width, south_west_arrow_height}, // ↙︎ South West Arrow (text)
|
|
||||||
{"\u2199\uFE0F", south_west_arrow, south_west_arrow_width, south_west_arrow_height}, // ↙️ South West Arrow (emoji)
|
|
||||||
{"\u2190", leftwards_arrow, leftwards_arrow_width, leftwards_arrow_height}, // ← Leftwards Arrow
|
|
||||||
{"\u2190\uFE0E", leftwards_arrow, leftwards_arrow_width, leftwards_arrow_height}, // ←︎ Leftwards Arrow (text)
|
|
||||||
{"\u2190\uFE0F", leftwards_arrow, leftwards_arrow_width, leftwards_arrow_height}, // ←️ Leftwards Arrow (emoji)
|
|
||||||
{"\u2196", north_west_arrow, north_west_arrow_width, north_west_arrow_height}, // ↖ North West Arrow
|
|
||||||
{"\u2196\uFE0E", north_west_arrow, north_west_arrow_width, north_west_arrow_height}, // ↖︎ North West Arrow (text)
|
|
||||||
{"\u2196\uFE0F", north_west_arrow, north_west_arrow_width, north_west_arrow_height}, // ↖️ North West Arrow (emoji)
|
|
||||||
{"\u2191", upwards_arrow, upwards_arrow_width, upwards_arrow_height}, // ↑ Upwards Arrow
|
|
||||||
{"\u2191\uFE0E", upwards_arrow, upwards_arrow_width, upwards_arrow_height}, // ↑︎ Upwards Arrow (text)
|
|
||||||
{"\u2191\uFE0F", upwards_arrow, upwards_arrow_width, upwards_arrow_height}, // ↑️ Upwards Arrow (emoji)
|
|
||||||
{"\u2197", north_east_arrow, north_east_arrow_width, north_east_arrow_height}, // ↗ North East Arrow
|
|
||||||
{"\u2197\uFE0E", north_east_arrow, north_east_arrow_width, north_east_arrow_height}, // ↗︎ North East Arrow (text)
|
|
||||||
{"\u2197\uFE0F", north_east_arrow, north_east_arrow_width, north_east_arrow_height}, // ↗️ North East Arrow (emoji)
|
|
||||||
{"\u2192", rightwards_arrow, rightwards_arrow_width, rightwards_arrow_height}, // → Rightwards Arrow
|
|
||||||
{"\u2192\uFE0E", rightwards_arrow, rightwards_arrow_width, rightwards_arrow_height}, // →︎ Rightwards Arrow (text)
|
|
||||||
{"\u2192\uFE0F", rightwards_arrow, rightwards_arrow_width, rightwards_arrow_height}, // →️ Rightwards Arrow (emoji)
|
|
||||||
{"\u2198", south_east_arrow, south_east_arrow_width, south_east_arrow_height}, // ↘ South East Arrow
|
|
||||||
{"\u2198\uFE0E", south_east_arrow, south_east_arrow_width, south_east_arrow_height}, // ↘︎ South East Arrow (text)
|
|
||||||
{"\u2198\uFE0F", south_east_arrow, south_east_arrow_width, south_east_arrow_height}, // ↘️ South East Arrow (emoji)
|
|
||||||
|
|
||||||
// --- Halloween ---
|
|
||||||
{"\U0001F383", jack_o_lantern, jack_o_lantern_width, jack_o_lantern_height}, // 🎃 Jack-O-Lantern
|
|
||||||
{"\U0001F47B", ghost, ghost_width, ghost_height}, // 👻 Ghost
|
|
||||||
{"\U0001F480", skull, skull_width, skull_height} // 💀 Skull
|
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -164,23 +88,23 @@ const unsigned char thumbdown[] PROGMEM = {0xF0, 0x1F, 0x08, 0x20, 0x06, 0x30, 0
|
|||||||
0x40, 0x06, 0x70, 0x06, 0x40, 0x06, 0x3F, 0x18, 0x02, 0x20, 0x02,
|
0x40, 0x06, 0x70, 0x06, 0x40, 0x06, 0x3F, 0x18, 0x02, 0x20, 0x02,
|
||||||
0x40, 0x04, 0x80, 0x04, 0x80, 0x04, 0x00, 0x03, 0x00, 0x00};
|
0x40, 0x04, 0x80, 0x04, 0x80, 0x04, 0x00, 0x03, 0x00, 0x00};
|
||||||
|
|
||||||
const unsigned char smiling_eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
|
const unsigned char Smiling_Eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
|
||||||
0x4A, 0x02, 0x40, 0x02, 0x40, 0x22, 0x44, 0x22, 0x44, 0xC2, 0x43,
|
0x4A, 0x02, 0x40, 0x02, 0x40, 0x22, 0x44, 0x22, 0x44, 0xC2, 0x43,
|
||||||
0x04, 0x20, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
0x04, 0x20, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||||
|
|
||||||
const unsigned char grinning[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x22, 0x42,
|
const unsigned char Grinning[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x22, 0x42,
|
||||||
0x42, 0x02, 0x40, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
|
0x42, 0x02, 0x40, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
|
||||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||||
|
|
||||||
const unsigned char slightly_smiling[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x22, 0x42,
|
const unsigned char Slightly_Smiling[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x22, 0x42,
|
||||||
0x42, 0x02, 0x40, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
|
0x42, 0x02, 0x40, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
|
||||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||||
|
|
||||||
const unsigned char winking_face[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x20, 0x42,
|
const unsigned char Winking_Face[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x44, 0x20, 0x42,
|
||||||
0x46, 0x02, 0x40, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
|
0x46, 0x02, 0x40, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
|
||||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||||
|
|
||||||
const unsigned char grinning_smiling_eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
|
const unsigned char Grinning_Smiling_Eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
|
||||||
0x4A, 0x02, 0x40, 0xFA, 0x5F, 0x0A, 0x50, 0x0A, 0x50, 0x12, 0x48,
|
0x4A, 0x02, 0x40, 0xFA, 0x5F, 0x0A, 0x50, 0x0A, 0x50, 0x12, 0x48,
|
||||||
0x24, 0x24, 0xC4, 0x23, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
0x24, 0x24, 0xC4, 0x23, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||||
|
|
||||||
@@ -188,7 +112,7 @@ const unsigned char heart_smile[] PROGMEM = {0x00, 0x00, 0x6C, 0x07, 0x7C, 0x18,
|
|||||||
0x0A, 0x02, 0xD8, 0x02, 0xF8, 0x22, 0xFC, 0x20, 0x74, 0xDB, 0x23,
|
0x0A, 0x02, 0xD8, 0x02, 0xF8, 0x22, 0xFC, 0x20, 0x74, 0xDB, 0x23,
|
||||||
0x1F, 0x00, 0x1F, 0x20, 0x0E, 0x18, 0xE4, 0x07, 0x00, 0x00};
|
0x1F, 0x00, 0x1F, 0x20, 0x0E, 0x18, 0xE4, 0x07, 0x00, 0x00};
|
||||||
|
|
||||||
const unsigned char heart_eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x54, 0x2A, 0xFA,
|
const unsigned char Heart_eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x54, 0x2A, 0xFA,
|
||||||
0x5F, 0x72, 0x4E, 0x22, 0x44, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48,
|
0x5F, 0x72, 0x4E, 0x22, 0x44, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48,
|
||||||
0x24, 0x24, 0xC4, 0x23, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
0x24, 0x24, 0xC4, 0x23, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||||
|
|
||||||
@@ -204,19 +128,19 @@ const unsigned char haha[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04,
|
|||||||
0x4A, 0x0A, 0x50, 0x0E, 0x70, 0xF2, 0x4F, 0x12, 0x48, 0x32, 0x44,
|
0x4A, 0x0A, 0x50, 0x0E, 0x70, 0xF2, 0x4F, 0x12, 0x48, 0x32, 0x44,
|
||||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||||
|
|
||||||
const unsigned char rofl[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x84, 0x21, 0x84, 0x20, 0x02,
|
const unsigned char ROFL[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x84, 0x21, 0x84, 0x20, 0x02,
|
||||||
0x4C, 0x02, 0x4A, 0x1A, 0x49, 0x8A, 0x48, 0x42, 0x48, 0x22, 0x44,
|
0x4C, 0x02, 0x4A, 0x1A, 0x49, 0x8A, 0x48, 0x42, 0x48, 0x22, 0x44,
|
||||||
0xE4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
0xE4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||||
|
|
||||||
const unsigned char smiling_closed_eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x42,
|
const unsigned char Smiling_Closed_Eyes[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x42,
|
||||||
0x42, 0x22, 0x44, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
|
0x42, 0x22, 0x44, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
|
||||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||||
|
|
||||||
const unsigned char grinning_smiling_eyes_2[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
|
const unsigned char Grinning_SmilingEyes2[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
|
||||||
0x4A, 0x02, 0x40, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
|
0x4A, 0x02, 0x40, 0x02, 0x40, 0xF2, 0x4F, 0x12, 0x48, 0x22, 0x44,
|
||||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||||
|
|
||||||
const unsigned char loudly_crying_face[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x34, 0x2C, 0x4A,
|
const unsigned char Loudly_Crying_Face[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x34, 0x2C, 0x4A,
|
||||||
0x52, 0x12, 0x48, 0x12, 0x48, 0x92, 0x49, 0x52, 0x4A, 0x52, 0x4A,
|
0x52, 0x12, 0x48, 0x12, 0x48, 0x92, 0x49, 0x52, 0x4A, 0x52, 0x4A,
|
||||||
0x54, 0x2A, 0x94, 0x29, 0x18, 0x18, 0xF0, 0x0F, 0x00, 0x00};
|
0x54, 0x2A, 0x94, 0x29, 0x18, 0x18, 0xF0, 0x0F, 0x00, 0x00};
|
||||||
|
|
||||||
@@ -268,7 +192,7 @@ const unsigned char cookie[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04
|
|||||||
0x40, 0x02, 0x58, 0x82, 0x5B, 0x92, 0x43, 0x82, 0x43, 0x02, 0x40,
|
0x40, 0x02, 0x58, 0x82, 0x5B, 0x92, 0x43, 0x82, 0x43, 0x02, 0x40,
|
||||||
0x64, 0x28, 0x64, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
0x64, 0x28, 0x64, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||||
|
|
||||||
const unsigned char fire[] PROGMEM = {0x30, 0x00, 0xF0, 0x00, 0xF8, 0x03, 0xF8, 0x07, 0xFC, 0x1F, 0xFC,
|
const unsigned char Fire[] PROGMEM = {0x30, 0x00, 0xF0, 0x00, 0xF8, 0x03, 0xF8, 0x07, 0xFC, 0x1F, 0xFC,
|
||||||
0x1F, 0xFE, 0x3E, 0x7E, 0x3E, 0x3E, 0x7C, 0x1E, 0x78, 0x1E, 0x70,
|
0x1F, 0xFE, 0x3E, 0x7E, 0x3E, 0x3E, 0x7C, 0x1E, 0x78, 0x1E, 0x70,
|
||||||
0x1C, 0x70, 0x1C, 0x70, 0x38, 0x38, 0x30, 0x38, 0x60, 0x0C};
|
0x1C, 0x70, 0x1C, 0x70, 0x38, 0x38, 0x30, 0x38, 0x60, 0x0C};
|
||||||
|
|
||||||
@@ -276,11 +200,11 @@ const unsigned char peace_sign[] PROGMEM = {0xC0, 0x30, 0x40, 0x29, 0x40, 0x25,
|
|||||||
0x0A, 0x54, 0x68, 0x54, 0x58, 0x54, 0x44, 0x3C, 0x22, 0x04, 0x22,
|
0x0A, 0x54, 0x68, 0x54, 0x58, 0x54, 0x44, 0x3C, 0x22, 0x04, 0x22,
|
||||||
0x04, 0x12, 0x08, 0x10, 0x10, 0x08, 0xE0, 0x07, 0x00, 0x00};
|
0x04, 0x12, 0x08, 0x10, 0x10, 0x08, 0xE0, 0x07, 0x00, 0x00};
|
||||||
|
|
||||||
const unsigned char praying[] PROGMEM = {0x00, 0x00, 0x40, 0x02, 0xA0, 0x05, 0x90, 0x09, 0x90, 0x09, 0x90,
|
const unsigned char Praying[] PROGMEM = {0x00, 0x00, 0x40, 0x02, 0xA0, 0x05, 0x90, 0x09, 0x90, 0x09, 0x90,
|
||||||
0x09, 0x98, 0x19, 0x94, 0x29, 0xA4, 0x25, 0xA4, 0x25, 0x84, 0x21,
|
0x09, 0x98, 0x19, 0x94, 0x29, 0xA4, 0x25, 0xA4, 0x25, 0x84, 0x21,
|
||||||
0x84, 0x21, 0x86, 0x61, 0x4E, 0x72, 0x7F, 0x7E, 0x3F, 0xFC};
|
0x84, 0x21, 0x86, 0x61, 0x4E, 0x72, 0x7F, 0x7E, 0x3F, 0xFC};
|
||||||
|
|
||||||
const unsigned char sparkles[] PROGMEM = {0x00, 0x00, 0x10, 0x00, 0x38, 0x04, 0x10, 0x04, 0x00, 0x0E, 0x00,
|
const unsigned char Sparkles[] PROGMEM = {0x00, 0x00, 0x10, 0x00, 0x38, 0x04, 0x10, 0x04, 0x00, 0x0E, 0x00,
|
||||||
0x1F, 0x80, 0x3F, 0xE0, 0xFF, 0x80, 0x3F, 0x10, 0x1F, 0x10, 0x0E,
|
0x1F, 0x80, 0x3F, 0xE0, 0xFF, 0x80, 0x3F, 0x10, 0x1F, 0x10, 0x0E,
|
||||||
0x38, 0x04, 0xFE, 0x04, 0x38, 0x00, 0x10, 0x00, 0x10, 0x00};
|
0x38, 0x04, 0xFE, 0x04, 0x38, 0x00, 0x10, 0x00, 0x10, 0x00};
|
||||||
|
|
||||||
@@ -303,179 +227,7 @@ const unsigned char bowling[] PROGMEM = {0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x0
|
|||||||
const unsigned char vulcan_salute[] PROGMEM = {0x08, 0x02, 0x16, 0x0D, 0x15, 0x15, 0x15, 0x15, 0xA9, 0x12, 0x4A,
|
const unsigned char vulcan_salute[] PROGMEM = {0x08, 0x02, 0x16, 0x0D, 0x15, 0x15, 0x15, 0x15, 0xA9, 0x12, 0x4A,
|
||||||
0x0A, 0x02, 0x38, 0x04, 0x48, 0x04, 0x44, 0x04, 0x22, 0x04, 0x22,
|
0x0A, 0x02, 0x38, 0x04, 0x48, 0x04, 0x44, 0x04, 0x22, 0x04, 0x22,
|
||||||
0x04, 0x12, 0x08, 0x10, 0x10, 0x08, 0xE0, 0x07, 0x00, 0x00};
|
0x04, 0x12, 0x08, 0x10, 0x10, 0x08, 0xE0, 0x07, 0x00, 0x00};
|
||||||
|
|
||||||
const unsigned char jack_o_lantern[] PROGMEM = {0xC0, 0x00, 0x80, 0x01, 0xB8, 0x1D, 0xC4, 0x23, 0x22, 0x44, 0x05,
|
|
||||||
0xA0, 0x31, 0x8C, 0x51, 0x8A, 0x61, 0x86, 0x09, 0x90, 0xB9, 0x9D,
|
|
||||||
0x49, 0x92, 0xB2, 0x4D, 0x42, 0x42, 0x04, 0x20, 0xF8, 0x1F};
|
|
||||||
|
|
||||||
const unsigned char ghost[] PROGMEM = {0xC0, 0x03, 0xF0, 0x0F, 0xF8, 0x1F, 0xDC, 0x3B, 0xBC, 0x3D, 0xDF,
|
|
||||||
0xFB, 0xFF, 0xFF, 0x1F, 0xF8, 0x1E, 0x78, 0x1C, 0x38, 0x3C, 0x3C,
|
|
||||||
0xFC, 0x3F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0x8C, 0x31};
|
|
||||||
|
|
||||||
const unsigned char skull[] PROGMEM = {0xE0, 0x07, 0xF8, 0x1F, 0xFC, 0x3F, 0xFE, 0x7F, 0xFE, 0x7F, 0xC7,
|
|
||||||
0xE3, 0x87, 0xE1, 0x87, 0xE1, 0x8F, 0xF1, 0xFE, 0x7F, 0x7C, 0x3E,
|
|
||||||
0xFC, 0x3F, 0xFC, 0x3F, 0xFC, 0x3F, 0xF8, 0x1F, 0xB0, 0x0D};
|
|
||||||
|
|
||||||
const unsigned char vomiting[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x04, 0x20, 0x22,
|
|
||||||
0x44, 0x42, 0x42, 0x22, 0x44, 0x02, 0x40, 0x02, 0x40, 0xC2, 0x43,
|
|
||||||
0x64, 0x26, 0x64, 0x26, 0x68, 0x16, 0x50, 0x0A, 0xF8, 0x1F};
|
|
||||||
|
|
||||||
const unsigned char cool[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0xFC, 0x3F, 0xFA,
|
|
||||||
0x5F, 0x72, 0x4E, 0x02, 0x40, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
|
|
||||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char shortcake[] PROGMEM = {0x00, 0x00, 0x00, 0x0F, 0x80, 0x3F, 0xE0, 0xFC, 0xE0, 0xE1, 0xF0,
|
|
||||||
0xB8, 0x10, 0x87, 0xC8, 0x80, 0x3C, 0xE0, 0x06, 0x98, 0x02, 0xC7,
|
|
||||||
0xE2, 0x30, 0x1A, 0x0E, 0xC6, 0x01, 0x32, 0x00, 0x0E, 0x00};
|
|
||||||
|
|
||||||
const unsigned char caution[] PROGMEM = {0x00, 0x00, 0x80, 0x01, 0xC0, 0x03, 0xC0, 0x03, 0x60, 0x06, 0x60,
|
|
||||||
0x06, 0x70, 0x0E, 0x70, 0x0E, 0x78, 0x1E, 0x78, 0x1E, 0x7C, 0x3E,
|
|
||||||
0xFC, 0x3F, 0x7E, 0x7E, 0x7E, 0x7E, 0xFC, 0x3F, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char clipboard[] PROGMEM = {0xC0, 0x03, 0x7E, 0x7E, 0xC2, 0x43, 0xFA, 0x5F, 0x0A, 0x5B, 0xFA,
|
|
||||||
0x5F, 0x8A, 0x54, 0xFA, 0x5F, 0x4A, 0x58, 0xFA, 0x5F, 0x2A, 0x51,
|
|
||||||
0xFA, 0x5F, 0x0A, 0x59, 0xFA, 0x5F, 0x02, 0x40, 0xFE, 0x7F};
|
|
||||||
|
|
||||||
const unsigned char snowflake[] PROGMEM = {0x00, 0x00, 0x40, 0x01, 0x88, 0x08, 0x8C, 0x18, 0xD0, 0x05, 0x60,
|
|
||||||
0x03, 0x32, 0x26, 0x1C, 0x1C, 0x32, 0x26, 0x60, 0x03, 0xD0, 0x05,
|
|
||||||
0x8C, 0x18, 0x88, 0x08, 0x40, 0x01, 0x00, 0x00, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char drop[] PROGMEM = {0x00, 0x00, 0x00, 0x01, 0x80, 0x03, 0xC0, 0x07, 0xE0, 0x0F, 0xE0,
|
|
||||||
0x0F, 0xF0, 0x1F, 0xF0, 0x1F, 0xF8, 0x3F, 0xF8, 0x3F, 0xF8, 0x3F,
|
|
||||||
0xF8, 0x3F, 0xF0, 0x1F, 0xE0, 0x0F, 0x80, 0x03, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char thermometer[] PROGMEM = {0x00, 0x00, 0x0C, 0x00, 0x16, 0x00, 0x2E, 0x00, 0x5C, 0x00, 0xB8,
|
|
||||||
0x00, 0x70, 0x01, 0xE0, 0x02, 0xC0, 0x05, 0x80, 0x3B, 0x00, 0x47,
|
|
||||||
0x00, 0xBE, 0x00, 0x9E, 0x00, 0xBE, 0x00, 0x7C, 0x00, 0x38};
|
|
||||||
|
|
||||||
const unsigned char sun_behind_raincloud[] PROGMEM = {0xC0, 0x03, 0x20, 0x04, 0x10, 0x0E, 0x38, 0x1F, 0xFC, 0x37, 0xEE,
|
|
||||||
0x77, 0xDE, 0x7B, 0x3E, 0x7C, 0xFC, 0x3F, 0x00, 0x00, 0x48, 0x12,
|
|
||||||
0x48, 0x12, 0x24, 0x09, 0x24, 0x09, 0x00, 0x00, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char sun_behind_cloud[] PROGMEM = {0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x04, 0x0E, 0x3C, 0x1B, 0xFC,
|
|
||||||
0x3B, 0xFE, 0x7B, 0xFA, 0x7B, 0xF6, 0x7D, 0x0C, 0x3E, 0xF8, 0x1F,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char cloud_with_snow[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x38, 0x1F, 0xFC, 0x3F, 0xFE,
|
|
||||||
0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0x00, 0x00, 0x08, 0x02,
|
|
||||||
0x40, 0x10, 0x00, 0x00, 0x24, 0x09, 0x00, 0x00, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char cloud_with_lightning[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x38, 0x1F, 0xFC, 0x3F, 0xFE,
|
|
||||||
0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0x00, 0x01, 0x80, 0x01,
|
|
||||||
0x80, 0x01, 0xC0, 0x07, 0x00, 0x03, 0x00, 0x03, 0x00, 0x01};
|
|
||||||
|
|
||||||
const unsigned char cloud_with_lightning_rain[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x38, 0x1F, 0xFC, 0x3F, 0xFE,
|
|
||||||
0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0x00, 0x01, 0x90, 0x21,
|
|
||||||
0x90, 0x21, 0xC8, 0x17, 0x08, 0x13, 0x00, 0x03, 0x00, 0x01};
|
|
||||||
|
|
||||||
const unsigned char wind_face[] PROGMEM = {0xFF, 0x00, 0x01, 0x01, 0x01, 0x01, 0xF9, 0x00, 0xF9, 0x01, 0xD9,
|
|
||||||
0x01, 0x99, 0x01, 0xF9, 0x01, 0xF9, 0x33, 0xFD, 0x4B, 0xFD, 0x85,
|
|
||||||
0xFD, 0x9A, 0xFD, 0x75, 0xFD, 0x09, 0xFD, 0x01, 0xFF, 0x00};
|
|
||||||
|
|
||||||
const unsigned char new_moon[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x04, 0x20, 0x02,
|
|
||||||
0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40, 0x02, 0x40,
|
|
||||||
0x04, 0x20, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char waxing_crescent_moon[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x1F, 0x04, 0x3E, 0x04, 0x3C, 0x02,
|
|
||||||
0x78, 0x02, 0x78, 0x02, 0x78, 0x02, 0x78, 0x02, 0x78, 0x02, 0x78,
|
|
||||||
0x04, 0x3C, 0x04, 0x3E, 0x18, 0x1F, 0xE0, 0x07, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char first_quarter_moon[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x1F, 0x04, 0x3F, 0x04, 0x3F, 0x02,
|
|
||||||
0x7F, 0x02, 0x7F, 0x02, 0x7F, 0x02, 0x7F, 0x02, 0x7F, 0x02, 0x7F,
|
|
||||||
0x04, 0x3F, 0x04, 0x3F, 0x18, 0x1F, 0xE0, 0x07, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char waxing_gibbous_moon[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x1F, 0x84, 0x3F, 0xC4, 0x3F, 0xC2,
|
|
||||||
0x7F, 0xC2, 0x7F, 0xC2, 0x7F, 0xC2, 0x7F, 0xC2, 0x7F, 0xC2, 0x7F,
|
|
||||||
0xC4, 0x3F, 0x84, 0x3F, 0x18, 0x1F, 0xE0, 0x07, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char full_moon[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0xF8, 0x1F, 0xFC, 0x3F, 0xFC, 0x3F, 0xFE,
|
|
||||||
0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F,
|
|
||||||
0xFC, 0x3F, 0xFC, 0x3F, 0xF8, 0x1F, 0xE0, 0x07, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char waning_gibbous_moon[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0xF8, 0x18, 0xFC, 0x21, 0xFC, 0x23, 0xFE,
|
|
||||||
0x43, 0xFE, 0x43, 0xFE, 0x43, 0xFE, 0x43, 0xFE, 0x43, 0xFE, 0x43,
|
|
||||||
0xFC, 0x23, 0xFC, 0x21, 0xF8, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char last_quarter_moon[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0xF8, 0x18, 0xFC, 0x20, 0xFC, 0x20, 0xFE,
|
|
||||||
0x40, 0xFE, 0x40, 0xFE, 0x40, 0xFE, 0x40, 0xFE, 0x40, 0xFE, 0x40,
|
|
||||||
0xFC, 0x20, 0xFC, 0x20, 0xF8, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char waning_crescent_moon[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0xF8, 0x18, 0x7C, 0x20, 0x3C, 0x20, 0x1E,
|
|
||||||
0x40, 0x1E, 0x40, 0x1E, 0x40, 0x1E, 0x40, 0x1E, 0x40, 0x1E, 0x40,
|
|
||||||
0x3C, 0x20, 0x7C, 0x20, 0xF8, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char first_quarter_moon_face[] PROGMEM = {0x00, 0x0F, 0x00, 0x12, 0x00, 0x24, 0x00, 0x44, 0x00, 0x48, 0x00,
|
|
||||||
0x88, 0x00, 0x84, 0x80, 0x93, 0x80, 0x80, 0x03, 0x81, 0x8D, 0x80,
|
|
||||||
0x71, 0x40, 0x82, 0x41, 0x02, 0x20, 0x0C, 0x18, 0xF0, 0x07};
|
|
||||||
|
|
||||||
const unsigned char peach[] PROGMEM = {0x70, 0x0F, 0x88, 0x10, 0x78, 0x1F, 0x88, 0x11, 0x04, 0x22, 0x02,
|
|
||||||
0x44, 0x02, 0x44, 0x02, 0x44, 0x02, 0x44, 0x02, 0x42, 0x02, 0x40,
|
|
||||||
0x04, 0x20, 0x04, 0x20, 0x08, 0x10, 0x30, 0x0C, 0xC0, 0x03};
|
|
||||||
|
|
||||||
const unsigned char turkey[] PROGMEM = {0x00, 0x00, 0x38, 0x00, 0x44, 0x38, 0x56, 0x54, 0x45, 0x52, 0xE2,
|
|
||||||
0x21, 0x2C, 0x56, 0x14, 0x58, 0x0A, 0x37, 0x86, 0x68, 0x82, 0x50,
|
|
||||||
0x82, 0x20, 0x04, 0x41, 0xF8, 0x7F, 0x40, 0x02, 0xF0, 0x07};
|
|
||||||
|
|
||||||
const unsigned char turkey_leg[] PROGMEM = {0x0C, 0x00, 0x1E, 0x00, 0x1F, 0x00, 0x2F, 0x00, 0x46, 0x00, 0x88,
|
|
||||||
0x01, 0x10, 0x0E, 0x20, 0x30, 0x20, 0x40, 0x40, 0x40, 0x40, 0x80,
|
|
||||||
0x40, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x43, 0x00, 0x3C};
|
|
||||||
|
|
||||||
const unsigned char south_west_arrow[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x1C, 0x00, 0x3E, 0x00,
|
|
||||||
0x1F, 0x80, 0x0F, 0xC2, 0x07, 0xE6, 0x03, 0xFE, 0x01, 0xFE, 0x00,
|
|
||||||
0x7E, 0x00, 0x7E, 0x00, 0xFE, 0x00, 0xFE, 0x01, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char south_east_arrow[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x38, 0x00, 0x7C, 0x00, 0xF8,
|
|
||||||
0x00, 0xF0, 0x01, 0xE0, 0x43, 0xC0, 0x67, 0x80, 0x7F, 0x00, 0x7F,
|
|
||||||
0x00, 0x7E, 0x00, 0x7E, 0x00, 0x7F, 0x80, 0x7F, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char north_west_arrow[] PROGMEM = {0x00, 0x00, 0xFE, 0x01, 0xFE, 0x00, 0x7E, 0x00, 0x7E, 0x00, 0xFE,
|
|
||||||
0x00, 0xFE, 0x01, 0xE6, 0x03, 0xC2, 0x07, 0x80, 0x0F, 0x00, 0x1F,
|
|
||||||
0x00, 0x3E, 0x00, 0x1C, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char north_east_arrow[] PROGMEM = {0x00, 0x00, 0x80, 0x7F, 0x00, 0x7F, 0x00, 0x7E, 0x00, 0x7E, 0x00,
|
|
||||||
0x7F, 0x80, 0x7F, 0xC0, 0x67, 0xE0, 0x43, 0xF0, 0x01, 0xF8, 0x00,
|
|
||||||
0x7C, 0x00, 0x38, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char downwards_arrow[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0,
|
|
||||||
0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xFC, 0x3F,
|
|
||||||
0xF8, 0x1F, 0xF0, 0x0F, 0xE0, 0x07, 0xC0, 0x03, 0x80, 0x01};
|
|
||||||
|
|
||||||
const unsigned char leftwards_arrow[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x30, 0x00, 0x38, 0x00, 0x3C,
|
|
||||||
0x00, 0xFE, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, 0xFE, 0x3F, 0x3C, 0x00,
|
|
||||||
0x38, 0x00, 0x30, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char upwards_arrow[] PROGMEM = {0x80, 0x01, 0xC0, 0x03, 0xE0, 0x07, 0xF0, 0x0F, 0xF8, 0x1F, 0xFC,
|
|
||||||
0x3F, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03,
|
|
||||||
0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char rightwards_arrow[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x1C, 0x00,
|
|
||||||
0x3C, 0xFC, 0x7F, 0xFC, 0xFF, 0xFC, 0xFF, 0xFC, 0x7F, 0x00, 0x3C,
|
|
||||||
0x00, 0x1C, 0x00, 0x0C, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char strong[] PROGMEM = {0x38, 0x00, 0x44, 0x00, 0x62, 0x00, 0x42, 0x00, 0x42, 0x00, 0x3A,
|
|
||||||
0x00, 0x11, 0x3C, 0x11, 0x42, 0xD1, 0x81, 0x31, 0x82, 0x11, 0x82,
|
|
||||||
0x21, 0x80, 0x01, 0x80, 0x01, 0x80, 0x02, 0x40, 0xFC, 0x3F};
|
|
||||||
|
|
||||||
const unsigned char check_mark[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x70, 0x00, 0x3C, 0x00,
|
|
||||||
0x1E, 0x00, 0x0F, 0x80, 0x07, 0xC3, 0x03, 0xEE, 0x03, 0xFC, 0x01,
|
|
||||||
0xF8, 0x00, 0xF0, 0x00, 0x70, 0x00, 0x60, 0x00, 0x20, 0x00};
|
|
||||||
|
|
||||||
const unsigned char house[] PROGMEM = {0x80, 0x01, 0x5C, 0x02, 0x34, 0x04, 0x14, 0x08, 0x0C, 0x10, 0x04,
|
|
||||||
0x20, 0x02, 0x40, 0xFF, 0xFF, 0x02, 0x40, 0x7A, 0x5F, 0x4A, 0x55,
|
|
||||||
0x4A, 0x5F, 0x6A, 0x55, 0x4A, 0x5F, 0x4A, 0x40, 0xFE, 0x7F};
|
|
||||||
|
|
||||||
const unsigned char shrug[] PROGMEM = {0xC0, 0x03, 0x20, 0x04, 0x10, 0x08, 0x50, 0x0A, 0x10, 0x08, 0x90,
|
|
||||||
0x09, 0x27, 0xE4, 0x49, 0x92, 0xAA, 0x55, 0x16, 0x68, 0x12, 0x48,
|
|
||||||
0x02, 0x40, 0x02, 0x40, 0x0C, 0x30, 0x08, 0x10, 0xF8, 0x1F};
|
|
||||||
|
|
||||||
const unsigned char eyes[] PROGMEM = {0x00, 0x00, 0x3C, 0x3C, 0x42, 0x42, 0x81, 0x81, 0x85, 0x85, 0x8F,
|
|
||||||
0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F, 0x8F,
|
|
||||||
0x85, 0x85, 0x81, 0x81, 0x42, 0x42, 0x3C, 0x3C, 0x00, 0x00};
|
|
||||||
|
|
||||||
const unsigned char eye[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x07, 0xF8, 0x1F, 0xF4,
|
|
||||||
0x2F, 0x7A, 0x5E, 0x39, 0x9C, 0x39, 0x9C, 0x7A, 0x5E, 0xF4, 0x2F,
|
|
||||||
0xF8, 0x1F, 0xE0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
} // namespace graphics
|
} // namespace graphics
|
||||||
#endif
|
#endif
|
||||||
@@ -22,33 +22,33 @@ extern const int numEmotes;
|
|||||||
extern const unsigned char thumbup[] PROGMEM;
|
extern const unsigned char thumbup[] PROGMEM;
|
||||||
extern const unsigned char thumbdown[] PROGMEM;
|
extern const unsigned char thumbdown[] PROGMEM;
|
||||||
|
|
||||||
#define smiling_eyes_height 16
|
#define Smiling_Eyes_height 16
|
||||||
#define smiling_eyes_width 16
|
#define Smiling_Eyes_width 16
|
||||||
extern const unsigned char smiling_eyes[] PROGMEM;
|
extern const unsigned char Smiling_Eyes[] PROGMEM;
|
||||||
|
|
||||||
#define grinning_height 16
|
#define Grinning_height 16
|
||||||
#define grinning_width 16
|
#define Grinning_width 16
|
||||||
extern const unsigned char grinning[] PROGMEM;
|
extern const unsigned char Grinning[] PROGMEM;
|
||||||
|
|
||||||
#define slightly_smiling_height 16
|
#define Slightly_Smiling_height 16
|
||||||
#define slightly_smiling_width 16
|
#define Slightly_Smiling_width 16
|
||||||
extern const unsigned char slightly_smiling[] PROGMEM;
|
extern const unsigned char Slightly_Smiling[] PROGMEM;
|
||||||
|
|
||||||
#define winking_face_height 16
|
#define Winking_Face_height 16
|
||||||
#define winking_face_width 16
|
#define Winking_Face_width 16
|
||||||
extern const unsigned char winking_face[] PROGMEM;
|
extern const unsigned char Winking_Face[] PROGMEM;
|
||||||
|
|
||||||
#define grinning_smiling_eyes_height 16
|
#define Grinning_Smiling_Eyes_height 16
|
||||||
#define grinning_smiling_eyes_width 16
|
#define Grinning_Smiling_Eyes_width 16
|
||||||
extern const unsigned char grinning_smiling_eyes[] PROGMEM;
|
extern const unsigned char Grinning_Smiling_Eyes[] PROGMEM;
|
||||||
|
|
||||||
#define heart_smile_height 16
|
#define heart_smile_height 16
|
||||||
#define heart_smile_width 16
|
#define heart_smile_width 16
|
||||||
extern const unsigned char heart_smile[] PROGMEM;
|
extern const unsigned char heart_smile[] PROGMEM;
|
||||||
|
|
||||||
#define heart_eyes_height 16
|
#define Heart_eyes_height 16
|
||||||
#define heart_eyes_width 16
|
#define Heart_eyes_width 16
|
||||||
extern const unsigned char heart_eyes[] PROGMEM;
|
extern const unsigned char Heart_eyes[] PROGMEM;
|
||||||
|
|
||||||
#define question_height 16
|
#define question_height 16
|
||||||
#define question_width 16
|
#define question_width 16
|
||||||
@@ -62,21 +62,21 @@ extern const unsigned char bang[] PROGMEM;
|
|||||||
#define haha_width 16
|
#define haha_width 16
|
||||||
extern const unsigned char haha[] PROGMEM;
|
extern const unsigned char haha[] PROGMEM;
|
||||||
|
|
||||||
#define rofl_height 16
|
#define ROFL_height 16
|
||||||
#define rofl_width 16
|
#define ROFL_width 16
|
||||||
extern const unsigned char rofl[] PROGMEM;
|
extern const unsigned char ROFL[] PROGMEM;
|
||||||
|
|
||||||
#define smiling_closed_eyes_height 16
|
#define Smiling_Closed_Eyes_height 16
|
||||||
#define smiling_closed_eyes_width 16
|
#define Smiling_Closed_Eyes_width 16
|
||||||
extern const unsigned char smiling_closed_eyes[] PROGMEM;
|
extern const unsigned char Smiling_Closed_Eyes[] PROGMEM;
|
||||||
|
|
||||||
#define grinning_smiling_eyes_2_height 16
|
#define Grinning_SmilingEyes2_height 16
|
||||||
#define grinning_smiling_eyes_2_width 16
|
#define Grinning_SmilingEyes2_width 16
|
||||||
extern const unsigned char grinning_smiling_eyes_2[] PROGMEM;
|
extern const unsigned char Grinning_SmilingEyes2[] PROGMEM;
|
||||||
|
|
||||||
#define loudly_crying_face_height 16
|
#define Loudly_Crying_Face_height 16
|
||||||
#define loudly_crying_face_width 16
|
#define Loudly_Crying_Face_width 16
|
||||||
extern const unsigned char loudly_crying_face[] PROGMEM;
|
extern const unsigned char Loudly_Crying_Face[] PROGMEM;
|
||||||
|
|
||||||
#define wave_icon_height 16
|
#define wave_icon_height 16
|
||||||
#define wave_icon_width 16
|
#define wave_icon_width 16
|
||||||
@@ -126,21 +126,21 @@ extern const unsigned char bell_icon[] PROGMEM;
|
|||||||
#define cookie_height 16
|
#define cookie_height 16
|
||||||
extern const unsigned char cookie[] PROGMEM;
|
extern const unsigned char cookie[] PROGMEM;
|
||||||
|
|
||||||
#define fire_width 16
|
#define Fire_width 16
|
||||||
#define fire_height 16
|
#define Fire_height 16
|
||||||
extern const unsigned char fire[] PROGMEM;
|
extern const unsigned char Fire[] PROGMEM;
|
||||||
|
|
||||||
#define peace_sign_width 16
|
#define peace_sign_width 16
|
||||||
#define peace_sign_height 16
|
#define peace_sign_height 16
|
||||||
extern const unsigned char peace_sign[] PROGMEM;
|
extern const unsigned char peace_sign[] PROGMEM;
|
||||||
|
|
||||||
#define praying_width 16
|
#define Praying_width 16
|
||||||
#define praying_height 16
|
#define Praying_height 16
|
||||||
extern const unsigned char praying[] PROGMEM;
|
extern const unsigned char Praying[] PROGMEM;
|
||||||
|
|
||||||
#define sparkles_width 16
|
#define Sparkles_width 16
|
||||||
#define sparkles_height 16
|
#define Sparkles_height 16
|
||||||
extern const unsigned char sparkles[] PROGMEM;
|
extern const unsigned char Sparkles[] PROGMEM;
|
||||||
|
|
||||||
#define clown_width 16
|
#define clown_width 16
|
||||||
#define clown_height 16
|
#define clown_height 16
|
||||||
@@ -161,178 +161,6 @@ extern const unsigned char bowling[] PROGMEM;
|
|||||||
#define vulcan_salute_width 16
|
#define vulcan_salute_width 16
|
||||||
#define vulcan_salute_height 16
|
#define vulcan_salute_height 16
|
||||||
extern const unsigned char vulcan_salute[] PROGMEM;
|
extern const unsigned char vulcan_salute[] PROGMEM;
|
||||||
|
|
||||||
#define jack_o_lantern_width 16
|
|
||||||
#define jack_o_lantern_height 16
|
|
||||||
extern const unsigned char jack_o_lantern[] PROGMEM;
|
|
||||||
|
|
||||||
#define ghost_width 16
|
|
||||||
#define ghost_height 16
|
|
||||||
extern const unsigned char ghost[] PROGMEM;
|
|
||||||
|
|
||||||
#define skull_width 16
|
|
||||||
#define skull_height 16
|
|
||||||
extern const unsigned char skull[] PROGMEM;
|
|
||||||
|
|
||||||
#define vomiting_width 16
|
|
||||||
#define vomiting_height 16
|
|
||||||
extern const unsigned char vomiting[] PROGMEM;
|
|
||||||
|
|
||||||
#define cool_width 16
|
|
||||||
#define cool_height 16
|
|
||||||
extern const unsigned char cool[] PROGMEM;
|
|
||||||
|
|
||||||
#define shortcake_width 16
|
|
||||||
#define shortcake_height 16
|
|
||||||
extern const unsigned char shortcake[] PROGMEM;
|
|
||||||
|
|
||||||
#define caution_width 16
|
|
||||||
#define caution_height 16
|
|
||||||
extern const unsigned char caution[] PROGMEM;
|
|
||||||
|
|
||||||
#define clipboard_width 16
|
|
||||||
#define clipboard_height 16
|
|
||||||
extern const unsigned char clipboard[] PROGMEM;
|
|
||||||
|
|
||||||
#define snowflake_width 16
|
|
||||||
#define snowflake_height 16
|
|
||||||
extern const unsigned char snowflake[] PROGMEM;
|
|
||||||
|
|
||||||
#define drop_width 16
|
|
||||||
#define drop_height 16
|
|
||||||
extern const unsigned char drop[] PROGMEM;
|
|
||||||
|
|
||||||
#define thermometer_width 16
|
|
||||||
#define thermometer_height 16
|
|
||||||
extern const unsigned char thermometer[] PROGMEM;
|
|
||||||
|
|
||||||
#define sun_behind_raincloud_width 16
|
|
||||||
#define sun_behind_raincloud_height 16
|
|
||||||
extern const unsigned char sun_behind_raincloud[] PROGMEM;
|
|
||||||
|
|
||||||
#define sun_behind_cloud_width 16
|
|
||||||
#define sun_behind_cloud_height 16
|
|
||||||
extern const unsigned char sun_behind_cloud[] PROGMEM;
|
|
||||||
|
|
||||||
#define cloud_with_snow_width 16
|
|
||||||
#define cloud_with_snow_height 16
|
|
||||||
extern const unsigned char cloud_with_snow[] PROGMEM;
|
|
||||||
|
|
||||||
#define cloud_with_lightning_width 16
|
|
||||||
#define cloud_with_lightning_height 16
|
|
||||||
extern const unsigned char cloud_with_lightning[] PROGMEM;
|
|
||||||
|
|
||||||
#define cloud_with_lightning_rain_width 16
|
|
||||||
#define cloud_with_lightning_rain_height 16
|
|
||||||
extern const unsigned char cloud_with_lightning_rain[] PROGMEM;
|
|
||||||
|
|
||||||
#define wind_face_width 16
|
|
||||||
#define wind_face_height 16
|
|
||||||
extern const unsigned char wind_face[] PROGMEM;
|
|
||||||
|
|
||||||
#define new_moon_width 16
|
|
||||||
#define new_moon_height 16
|
|
||||||
extern const unsigned char new_moon[] PROGMEM;
|
|
||||||
|
|
||||||
#define waxing_crescent_moon_width 16
|
|
||||||
#define waxing_crescent_moon_height 16
|
|
||||||
extern const unsigned char waxing_crescent_moon[] PROGMEM;
|
|
||||||
|
|
||||||
#define first_quarter_moon_width 16
|
|
||||||
#define first_quarter_moon_height 16
|
|
||||||
extern const unsigned char first_quarter_moon[] PROGMEM;
|
|
||||||
|
|
||||||
#define waxing_gibbous_moon_width 16
|
|
||||||
#define waxing_gibbous_moon_height 16
|
|
||||||
extern const unsigned char waxing_gibbous_moon[] PROGMEM;
|
|
||||||
|
|
||||||
#define full_moon_width 16
|
|
||||||
#define full_moon_height 16
|
|
||||||
extern const unsigned char full_moon[] PROGMEM;
|
|
||||||
|
|
||||||
#define waning_gibbous_moon_width 16
|
|
||||||
#define waning_gibbous_moon_height 16
|
|
||||||
extern const unsigned char waning_gibbous_moon[] PROGMEM;
|
|
||||||
|
|
||||||
#define last_quarter_moon_width 16
|
|
||||||
#define last_quarter_moon_height 16
|
|
||||||
extern const unsigned char last_quarter_moon[] PROGMEM;
|
|
||||||
|
|
||||||
#define waning_crescent_moon_width 16
|
|
||||||
#define waning_crescent_moon_height 16
|
|
||||||
extern const unsigned char waning_crescent_moon[] PROGMEM;
|
|
||||||
|
|
||||||
#define first_quarter_moon_face_width 16
|
|
||||||
#define first_quarter_moon_face_height 16
|
|
||||||
extern const unsigned char first_quarter_moon_face[] PROGMEM;
|
|
||||||
|
|
||||||
#define peach_width 16
|
|
||||||
#define peach_height 16
|
|
||||||
extern const unsigned char peach[] PROGMEM;
|
|
||||||
|
|
||||||
#define turkey_width 16
|
|
||||||
#define turkey_height 16
|
|
||||||
extern const unsigned char turkey[] PROGMEM;
|
|
||||||
|
|
||||||
#define turkey_leg_width 16
|
|
||||||
#define turkey_leg_height 16
|
|
||||||
extern const unsigned char turkey_leg[] PROGMEM;
|
|
||||||
|
|
||||||
#define south_west_arrow_width 16
|
|
||||||
#define south_west_arrow_height 16
|
|
||||||
extern const unsigned char south_west_arrow[] PROGMEM;
|
|
||||||
|
|
||||||
#define south_east_arrow_width 16
|
|
||||||
#define south_east_arrow_height 16
|
|
||||||
extern const unsigned char south_east_arrow[] PROGMEM;
|
|
||||||
|
|
||||||
#define north_west_arrow_width 16
|
|
||||||
#define north_west_arrow_height 16
|
|
||||||
extern const unsigned char north_west_arrow[] PROGMEM;
|
|
||||||
|
|
||||||
#define north_east_arrow_width 16
|
|
||||||
#define north_east_arrow_height 16
|
|
||||||
extern const unsigned char north_east_arrow[] PROGMEM;
|
|
||||||
|
|
||||||
#define downwards_arrow_width 16
|
|
||||||
#define downwards_arrow_height 16
|
|
||||||
extern const unsigned char downwards_arrow[] PROGMEM;
|
|
||||||
|
|
||||||
#define leftwards_arrow_width 16
|
|
||||||
#define leftwards_arrow_height 16
|
|
||||||
extern const unsigned char leftwards_arrow[] PROGMEM;
|
|
||||||
|
|
||||||
#define upwards_arrow_width 16
|
|
||||||
#define upwards_arrow_height 16
|
|
||||||
extern const unsigned char upwards_arrow[] PROGMEM;
|
|
||||||
|
|
||||||
#define rightwards_arrow_width 16
|
|
||||||
#define rightwards_arrow_height 16
|
|
||||||
extern const unsigned char rightwards_arrow[] PROGMEM;
|
|
||||||
|
|
||||||
#define strong_width 16
|
|
||||||
#define strong_height 16
|
|
||||||
extern const unsigned char strong[] PROGMEM;
|
|
||||||
|
|
||||||
#define check_mark_width 16
|
|
||||||
#define check_mark_height 16
|
|
||||||
extern const unsigned char check_mark[] PROGMEM;
|
|
||||||
|
|
||||||
#define house_width 16
|
|
||||||
#define house_height 16
|
|
||||||
extern const unsigned char house[] PROGMEM;
|
|
||||||
|
|
||||||
#define shrug_width 16
|
|
||||||
#define shrug_height 16
|
|
||||||
extern const unsigned char shrug[] PROGMEM;
|
|
||||||
|
|
||||||
#define eyes_width 16
|
|
||||||
#define eyes_height 16
|
|
||||||
extern const unsigned char eyes[] PROGMEM;
|
|
||||||
|
|
||||||
#define eye_width 16
|
|
||||||
#define eye_height 16
|
|
||||||
extern const unsigned char eye[] PROGMEM;
|
|
||||||
#endif // EXCLUDE_EMOJI
|
#endif // EXCLUDE_EMOJI
|
||||||
|
|
||||||
} // namespace graphics
|
} // namespace graphics
|
||||||
@@ -304,6 +304,58 @@ const uint8_t chirpy[] = {
|
|||||||
0x01, 0x18, 0x06, 0x18, 0xe0, 0x01, 0x18, 0x06, 0x18, 0xe0, 0x01, 0x0c, 0x03, 0x30, 0xe0, 0x01, 0x0c, 0x03, 0x30, 0xe0, 0x01,
|
0x01, 0x18, 0x06, 0x18, 0xe0, 0x01, 0x18, 0x06, 0x18, 0xe0, 0x01, 0x0c, 0x03, 0x30, 0xe0, 0x01, 0x0c, 0x03, 0x30, 0xe0, 0x01,
|
||||||
0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0xfe, 0xff, 0xff, 0xff, 0xdf};
|
0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x00, 0xe0, 0xfe, 0xff, 0xff, 0xff, 0xdf};
|
||||||
|
|
||||||
|
#define chirpy_width_hirez 76
|
||||||
|
#define chirpy_height_hirez 100
|
||||||
|
const uint8_t chirpy_hirez[] = {
|
||||||
|
0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0x03,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0f, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00,
|
||||||
|
0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc,
|
||||||
|
0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03,
|
||||||
|
0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xf0, 0xff, 0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0,
|
||||||
|
0xff, 0xf0, 0xff, 0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f,
|
||||||
|
0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0,
|
||||||
|
0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff,
|
||||||
|
0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f,
|
||||||
|
0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0,
|
||||||
|
0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff,
|
||||||
|
0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00,
|
||||||
|
0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc,
|
||||||
|
0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03,
|
||||||
|
0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0,
|
||||||
|
0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f,
|
||||||
|
0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0,
|
||||||
|
0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0x3f, 0xc0, 0xff, 0x0f, 0xf0, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xf0, 0xff,
|
||||||
|
0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xf0, 0xff, 0x3f, 0xfc, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0x00, 0xfc, 0x03, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f,
|
||||||
|
0x00, 0xfc, 0x03, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc,
|
||||||
|
0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03,
|
||||||
|
0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00,
|
||||||
|
0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00,
|
||||||
|
0xc0, 0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0, 0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xc0,
|
||||||
|
0x03, 0xfc, 0x03, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00,
|
||||||
|
0x0f, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x0f, 0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xf0, 0x00, 0x0f,
|
||||||
|
0x0f, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c,
|
||||||
|
0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x3c, 0xc0, 0x03, 0x3c, 0x00,
|
||||||
|
0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00,
|
||||||
|
0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0xf0, 0x00, 0x00, 0xfc,
|
||||||
|
0x03, 0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03,
|
||||||
|
0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00, 0xc0, 0x03, 0x3c, 0x00, 0xc0, 0x03, 0x00, 0xfc, 0x03, 0x00,
|
||||||
|
0xf0, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0,
|
||||||
|
0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0xf0, 0x00, 0x0f, 0x00, 0x00, 0x0f, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||||
|
0xff, 0xff, 0xf3, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3};
|
||||||
|
|
||||||
#define chirpy_small_image_width 8
|
#define chirpy_small_image_width 8
|
||||||
#define chirpy_small_image_height 8
|
#define chirpy_small_image_height 8
|
||||||
const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
|
const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
|
||||||
|
|||||||
@@ -88,14 +88,8 @@ class Applet : public GFX
|
|||||||
virtual void onForeground() {}
|
virtual void onForeground() {}
|
||||||
virtual void onBackground() {}
|
virtual void onBackground() {}
|
||||||
virtual void onShutdown() {}
|
virtual void onShutdown() {}
|
||||||
virtual void onButtonShortPress() {}
|
virtual void onButtonShortPress() {} // (System Applets only)
|
||||||
virtual void onButtonLongPress() {}
|
virtual void onButtonLongPress() {} // (System Applets only)
|
||||||
virtual void onExitShort() {}
|
|
||||||
virtual void onExitLong() {}
|
|
||||||
virtual void onNavUp() {}
|
|
||||||
virtual void onNavDown() {}
|
|
||||||
virtual void onNavLeft() {}
|
|
||||||
virtual void onNavRight() {}
|
|
||||||
|
|
||||||
virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification
|
virtual bool approveNotification(Notification &n); // Allow an applet to veto a notification
|
||||||
|
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
|
||||||
|
|
||||||
#include "./AlignStickApplet.h"
|
|
||||||
|
|
||||||
using namespace NicheGraphics;
|
|
||||||
|
|
||||||
InkHUD::AlignStickApplet::AlignStickApplet()
|
|
||||||
{
|
|
||||||
if (!settings->joystick.aligned)
|
|
||||||
bringToForeground();
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onRender()
|
|
||||||
{
|
|
||||||
setFont(fontMedium);
|
|
||||||
printAt(0, 0, "Align Joystick:");
|
|
||||||
setFont(fontSmall);
|
|
||||||
std::string instructions = "Move joystick in the direction indicated";
|
|
||||||
printWrapped(0, fontMedium.lineHeight() * 1.5, width(), instructions);
|
|
||||||
|
|
||||||
// Size of the region in which the joystick graphic should fit
|
|
||||||
uint16_t joyXLimit = X(0.8);
|
|
||||||
uint16_t contentH = fontMedium.lineHeight() * 1.5 + fontSmall.lineHeight() * 1;
|
|
||||||
if (getTextWidth(instructions) > width())
|
|
||||||
contentH += fontSmall.lineHeight();
|
|
||||||
uint16_t freeY = height() - contentH - fontSmall.lineHeight() * 1.2;
|
|
||||||
uint16_t joyYLimit = freeY * 0.8;
|
|
||||||
|
|
||||||
// Use the shorter of the two
|
|
||||||
uint16_t joyWidth = joyXLimit < joyYLimit ? joyXLimit : joyYLimit;
|
|
||||||
|
|
||||||
// Center the joystick graphic
|
|
||||||
uint16_t centerX = X(0.5);
|
|
||||||
uint16_t centerY = contentH + freeY * 0.5;
|
|
||||||
|
|
||||||
// Draw joystick graphic
|
|
||||||
drawStick(centerX, centerY, joyWidth);
|
|
||||||
|
|
||||||
setFont(fontSmall);
|
|
||||||
printAt(X(0.5), Y(1.0) - fontSmall.lineHeight() * 0.2, "Long press to skip", CENTER, BOTTOM);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw a scalable joystick graphic
|
|
||||||
void InkHUD::AlignStickApplet::drawStick(uint16_t centerX, uint16_t centerY, uint16_t width)
|
|
||||||
{
|
|
||||||
if (width < 9) // too small to draw
|
|
||||||
return;
|
|
||||||
|
|
||||||
else if (width < 40) { // only draw up arrow
|
|
||||||
uint16_t chamfer = width < 20 ? 1 : 2;
|
|
||||||
|
|
||||||
// Draw filled up arrow
|
|
||||||
drawDirection(centerX, centerY - width / 4, Direction::UP, width, chamfer, BLACK);
|
|
||||||
|
|
||||||
} else { // large enough to draw the full thing
|
|
||||||
uint16_t chamfer = width < 80 ? 1 : 2;
|
|
||||||
uint16_t stroke = 3; // pixels
|
|
||||||
uint16_t arrowW = width * 0.22;
|
|
||||||
uint16_t hollowW = arrowW - stroke * 2;
|
|
||||||
|
|
||||||
// Draw center circle
|
|
||||||
fillCircle((int16_t)centerX, (int16_t)centerY, (int16_t)(width * 0.2), BLACK);
|
|
||||||
fillCircle((int16_t)centerX, (int16_t)centerY, (int16_t)(width * 0.2) - stroke, WHITE);
|
|
||||||
|
|
||||||
// Draw filled up arrow
|
|
||||||
drawDirection(centerX, centerY - width / 2, Direction::UP, arrowW, chamfer, BLACK);
|
|
||||||
|
|
||||||
// Draw down arrow
|
|
||||||
drawDirection(centerX, centerY + width / 2, Direction::DOWN, arrowW, chamfer, BLACK);
|
|
||||||
drawDirection(centerX, centerY + width / 2 - stroke, Direction::DOWN, hollowW, 0, WHITE);
|
|
||||||
|
|
||||||
// Draw left arrow
|
|
||||||
drawDirection(centerX - width / 2, centerY, Direction::LEFT, arrowW, chamfer, BLACK);
|
|
||||||
drawDirection(centerX - width / 2 + stroke, centerY, Direction::LEFT, hollowW, 0, WHITE);
|
|
||||||
|
|
||||||
// Draw right arrow
|
|
||||||
drawDirection(centerX + width / 2, centerY, Direction::RIGHT, arrowW, chamfer, BLACK);
|
|
||||||
drawDirection(centerX + width / 2 - stroke, centerY, Direction::RIGHT, hollowW, 0, WHITE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw a scalable joystick direction arrow
|
|
||||||
// a right-triangle with blunted tips
|
|
||||||
/*
|
|
||||||
_ <--point
|
|
||||||
^ / \
|
|
||||||
| / \
|
|
||||||
size / \
|
|
||||||
| / \
|
|
||||||
v |_________|
|
|
||||||
|
|
||||||
*/
|
|
||||||
void InkHUD::AlignStickApplet::drawDirection(uint16_t pointX, uint16_t pointY, Direction direction, uint16_t size,
|
|
||||||
uint16_t chamfer, Color color)
|
|
||||||
{
|
|
||||||
uint16_t chamferW = chamfer * 2 + 1;
|
|
||||||
uint16_t triangleW = size - chamferW;
|
|
||||||
|
|
||||||
// Draw arrow
|
|
||||||
switch (direction) {
|
|
||||||
case Direction::UP:
|
|
||||||
fillRect(pointX - chamfer, pointY, chamferW, triangleW, color);
|
|
||||||
fillRect(pointX - chamfer - triangleW, pointY + triangleW, chamferW + triangleW * 2, chamferW, color);
|
|
||||||
fillTriangle(pointX - chamfer, pointY, pointX - chamfer - triangleW, pointY + triangleW, pointX - chamfer,
|
|
||||||
pointY + triangleW, color);
|
|
||||||
fillTriangle(pointX + chamfer, pointY, pointX + chamfer + triangleW, pointY + triangleW, pointX + chamfer,
|
|
||||||
pointY + triangleW, color);
|
|
||||||
break;
|
|
||||||
case Direction::DOWN:
|
|
||||||
fillRect(pointX - chamfer, pointY - triangleW + 1, chamferW, triangleW, color);
|
|
||||||
fillRect(pointX - chamfer - triangleW, pointY - size + 1, chamferW + triangleW * 2, chamferW, color);
|
|
||||||
fillTriangle(pointX - chamfer, pointY, pointX - chamfer - triangleW, pointY - triangleW, pointX - chamfer,
|
|
||||||
pointY - triangleW, color);
|
|
||||||
fillTriangle(pointX + chamfer, pointY, pointX + chamfer + triangleW, pointY - triangleW, pointX + chamfer,
|
|
||||||
pointY - triangleW, color);
|
|
||||||
break;
|
|
||||||
case Direction::LEFT:
|
|
||||||
fillRect(pointX, pointY - chamfer, triangleW, chamferW, color);
|
|
||||||
fillRect(pointX + triangleW, pointY - chamfer - triangleW, chamferW, chamferW + triangleW * 2, color);
|
|
||||||
fillTriangle(pointX, pointY - chamfer, pointX + triangleW, pointY - chamfer - triangleW, pointX + triangleW,
|
|
||||||
pointY - chamfer, color);
|
|
||||||
fillTriangle(pointX, pointY + chamfer, pointX + triangleW, pointY + chamfer + triangleW, pointX + triangleW,
|
|
||||||
pointY + chamfer, color);
|
|
||||||
break;
|
|
||||||
case Direction::RIGHT:
|
|
||||||
fillRect(pointX - triangleW + 1, pointY - chamfer, triangleW, chamferW, color);
|
|
||||||
fillRect(pointX - size + 1, pointY - chamfer - triangleW, chamferW, chamferW + triangleW * 2, color);
|
|
||||||
fillTriangle(pointX, pointY - chamfer, pointX - triangleW, pointY - chamfer - triangleW, pointX - triangleW,
|
|
||||||
pointY - chamfer, color);
|
|
||||||
fillTriangle(pointX, pointY + chamfer, pointX - triangleW, pointY + chamfer + triangleW, pointX - triangleW,
|
|
||||||
pointY + chamfer, color);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onForeground()
|
|
||||||
{
|
|
||||||
// Prevent most other applets from requesting update, and skip their rendering entirely
|
|
||||||
// Another system applet with a higher precedence can potentially ignore this
|
|
||||||
SystemApplet::lockRendering = true;
|
|
||||||
SystemApplet::lockRequests = true;
|
|
||||||
|
|
||||||
handleInput = true; // Intercept the button input for our applet
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onBackground()
|
|
||||||
{
|
|
||||||
// Allow normal update behavior to resume
|
|
||||||
SystemApplet::lockRendering = false;
|
|
||||||
SystemApplet::lockRequests = false;
|
|
||||||
SystemApplet::handleInput = false;
|
|
||||||
|
|
||||||
// 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
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onButtonLongPress()
|
|
||||||
{
|
|
||||||
sendToBackground();
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onExitLong()
|
|
||||||
{
|
|
||||||
sendToBackground();
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onNavUp()
|
|
||||||
{
|
|
||||||
settings->joystick.aligned = true;
|
|
||||||
|
|
||||||
sendToBackground();
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onNavDown()
|
|
||||||
{
|
|
||||||
inkhud->rotateJoystick(2); // 180 deg
|
|
||||||
settings->joystick.aligned = true;
|
|
||||||
|
|
||||||
sendToBackground();
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onNavLeft()
|
|
||||||
{
|
|
||||||
inkhud->rotateJoystick(3); // 270 deg
|
|
||||||
settings->joystick.aligned = true;
|
|
||||||
|
|
||||||
sendToBackground();
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::AlignStickApplet::onNavRight()
|
|
||||||
{
|
|
||||||
inkhud->rotateJoystick(1); // 90 deg
|
|
||||||
settings->joystick.aligned = true;
|
|
||||||
|
|
||||||
sendToBackground();
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
System Applet for manually aligning the joystick with the screen
|
|
||||||
|
|
||||||
should be run at startup if the joystick is enabled
|
|
||||||
and not aligned to the screen
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "configuration.h"
|
|
||||||
|
|
||||||
#include "graphics/niche/InkHUD/SystemApplet.h"
|
|
||||||
|
|
||||||
namespace NicheGraphics::InkHUD
|
|
||||||
{
|
|
||||||
|
|
||||||
class AlignStickApplet : public SystemApplet
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AlignStickApplet();
|
|
||||||
|
|
||||||
void onRender() override;
|
|
||||||
void onForeground() override;
|
|
||||||
void onBackground() override;
|
|
||||||
void onButtonLongPress() override;
|
|
||||||
void onExitLong() override;
|
|
||||||
void onNavUp() override;
|
|
||||||
void onNavDown() override;
|
|
||||||
void onNavLeft() override;
|
|
||||||
void onNavRight() override;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
enum Direction {
|
|
||||||
UP,
|
|
||||||
DOWN,
|
|
||||||
LEFT,
|
|
||||||
RIGHT,
|
|
||||||
};
|
|
||||||
|
|
||||||
void drawStick(uint16_t centerX, uint16_t centerY, uint16_t width);
|
|
||||||
void drawDirection(uint16_t pointX, uint16_t pointY, Direction direction, uint16_t size, uint16_t chamfer, Color color);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -30,7 +30,6 @@ enum MenuAction {
|
|||||||
TOGGLE_AUTOSHOW_APPLET,
|
TOGGLE_AUTOSHOW_APPLET,
|
||||||
SET_RECENTS,
|
SET_RECENTS,
|
||||||
ROTATE,
|
ROTATE,
|
||||||
ALIGN_JOYSTICK,
|
|
||||||
LAYOUT,
|
LAYOUT,
|
||||||
TOGGLE_BATTERY_ICON,
|
TOGGLE_BATTERY_ICON,
|
||||||
TOGGLE_NOTIFICATIONS,
|
TOGGLE_NOTIFICATIONS,
|
||||||
|
|||||||
@@ -178,10 +178,6 @@ void InkHUD::MenuApplet::execute(MenuItem item)
|
|||||||
inkhud->rotate();
|
inkhud->rotate();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ALIGN_JOYSTICK:
|
|
||||||
inkhud->openAlignStick();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LAYOUT:
|
case LAYOUT:
|
||||||
// Todo: smarter incrementing of tile count
|
// Todo: smarter incrementing of tile count
|
||||||
settings->userTiles.count++;
|
settings->userTiles.count++;
|
||||||
@@ -291,17 +287,14 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
// items.push_back(MenuItem("Display Off", MenuPage::EXIT)); // TODO
|
// items.push_back(MenuItem("Display Off", MenuPage::EXIT)); // TODO
|
||||||
items.push_back(MenuItem("Save & Shut Down", MenuAction::SHUTDOWN));
|
items.push_back(MenuItem("Save & Shut Down", MenuAction::SHUTDOWN));
|
||||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||||
previousPage = MenuPage::EXIT;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SEND:
|
case SEND:
|
||||||
populateSendPage();
|
populateSendPage();
|
||||||
previousPage = MenuPage::ROOT;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CANNEDMESSAGE_RECIPIENT:
|
case CANNEDMESSAGE_RECIPIENT:
|
||||||
populateRecipientPage();
|
populateRecipientPage();
|
||||||
previousPage = MenuPage::OPTIONS;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OPTIONS:
|
case OPTIONS:
|
||||||
@@ -328,8 +321,6 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
if (settings->userTiles.maxCount > 1)
|
if (settings->userTiles.maxCount > 1)
|
||||||
items.push_back(MenuItem("Layout", MenuAction::LAYOUT, MenuPage::OPTIONS));
|
items.push_back(MenuItem("Layout", MenuAction::LAYOUT, MenuPage::OPTIONS));
|
||||||
items.push_back(MenuItem("Rotate", MenuAction::ROTATE, MenuPage::OPTIONS));
|
items.push_back(MenuItem("Rotate", MenuAction::ROTATE, MenuPage::OPTIONS));
|
||||||
if (settings->joystick.enabled)
|
|
||||||
items.push_back(MenuItem("Align Joystick", MenuAction::ALIGN_JOYSTICK, MenuPage::EXIT));
|
|
||||||
items.push_back(MenuItem("Notifications", MenuAction::TOGGLE_NOTIFICATIONS, MenuPage::OPTIONS,
|
items.push_back(MenuItem("Notifications", MenuAction::TOGGLE_NOTIFICATIONS, MenuPage::OPTIONS,
|
||||||
&settings->optionalFeatures.notifications));
|
&settings->optionalFeatures.notifications));
|
||||||
items.push_back(MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS,
|
items.push_back(MenuItem("Battery Icon", MenuAction::TOGGLE_BATTERY_ICON, MenuPage::OPTIONS,
|
||||||
@@ -341,24 +332,20 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
|
|||||||
items.push_back(
|
items.push_back(
|
||||||
MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::OPTIONS, &config.display.use_12h_clock));
|
MenuItem("12-Hour Clock", MenuAction::TOGGLE_12H_CLOCK, MenuPage::OPTIONS, &config.display.use_12h_clock));
|
||||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||||
previousPage = MenuPage::ROOT;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case APPLETS:
|
case APPLETS:
|
||||||
populateAppletPage();
|
populateAppletPage();
|
||||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||||
previousPage = MenuPage::OPTIONS;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AUTOSHOW:
|
case AUTOSHOW:
|
||||||
populateAutoshowPage();
|
populateAutoshowPage();
|
||||||
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
items.push_back(MenuItem("Exit", MenuPage::EXIT));
|
||||||
previousPage = MenuPage::OPTIONS;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RECENTS:
|
case RECENTS:
|
||||||
populateRecentsPage();
|
populateRecentsPage();
|
||||||
previousPage = MenuPage::OPTIONS;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EXIT:
|
case EXIT:
|
||||||
@@ -492,21 +479,12 @@ void InkHUD::MenuApplet::onButtonShortPress()
|
|||||||
// Push the auto-close timer back
|
// Push the auto-close timer back
|
||||||
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
||||||
|
|
||||||
if (!settings->joystick.enabled) {
|
// Move menu cursor to next entry, then update
|
||||||
// Move menu cursor to next entry, then update
|
if (cursorShown)
|
||||||
if (cursorShown)
|
cursor = (cursor + 1) % items.size();
|
||||||
cursor = (cursor + 1) % items.size();
|
else
|
||||||
else
|
cursorShown = true;
|
||||||
cursorShown = true;
|
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
||||||
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
|
||||||
} else {
|
|
||||||
if (cursorShown)
|
|
||||||
execute(items.at(cursor));
|
|
||||||
else
|
|
||||||
showPage(MenuPage::EXIT);
|
|
||||||
if (!wantsToRender())
|
|
||||||
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::MenuApplet::onButtonLongPress()
|
void InkHUD::MenuApplet::onButtonLongPress()
|
||||||
@@ -526,62 +504,6 @@ void InkHUD::MenuApplet::onButtonLongPress()
|
|||||||
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::MenuApplet::onExitShort()
|
|
||||||
{
|
|
||||||
// Exit the menu
|
|
||||||
showPage(MenuPage::EXIT);
|
|
||||||
|
|
||||||
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::MenuApplet::onNavUp()
|
|
||||||
{
|
|
||||||
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
|
||||||
|
|
||||||
// Move menu cursor to previous entry, then update
|
|
||||||
if (cursor == 0)
|
|
||||||
cursor = items.size() - 1;
|
|
||||||
else
|
|
||||||
cursor--;
|
|
||||||
|
|
||||||
if (!cursorShown)
|
|
||||||
cursorShown = true;
|
|
||||||
|
|
||||||
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::MenuApplet::onNavDown()
|
|
||||||
{
|
|
||||||
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
|
||||||
|
|
||||||
// Move menu cursor to next entry, then update
|
|
||||||
if (cursorShown)
|
|
||||||
cursor = (cursor + 1) % items.size();
|
|
||||||
else
|
|
||||||
cursorShown = true;
|
|
||||||
|
|
||||||
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::MenuApplet::onNavLeft()
|
|
||||||
{
|
|
||||||
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
|
||||||
|
|
||||||
// Go to the previous menu page
|
|
||||||
showPage(previousPage);
|
|
||||||
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::MenuApplet::onNavRight()
|
|
||||||
{
|
|
||||||
OSThread::setIntervalFromNow(MENU_TIMEOUT_SEC * 1000UL);
|
|
||||||
|
|
||||||
if (cursorShown)
|
|
||||||
execute(items.at(cursor));
|
|
||||||
if (!wantsToRender())
|
|
||||||
requestUpdate(Drivers::EInk::UpdateTypes::FAST);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dynamically create MenuItem entries for activating / deactivating Applets, for the "Applet Selection" submenu
|
// Dynamically create MenuItem entries for activating / deactivating Applets, for the "Applet Selection" submenu
|
||||||
void InkHUD::MenuApplet::populateAppletPage()
|
void InkHUD::MenuApplet::populateAppletPage()
|
||||||
{
|
{
|
||||||
@@ -874,4 +796,4 @@ void InkHUD::MenuApplet::freeCannedMessageResources()
|
|||||||
cm.recipientItems.clear();
|
cm.recipientItems.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -27,11 +27,6 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
void onButtonShortPress() override;
|
void onButtonShortPress() override;
|
||||||
void onButtonLongPress() override;
|
void onButtonLongPress() override;
|
||||||
void onExitShort() override;
|
|
||||||
void onNavUp() override;
|
|
||||||
void onNavDown() override;
|
|
||||||
void onNavLeft() override;
|
|
||||||
void onNavRight() override;
|
|
||||||
void onRender() override;
|
void onRender() override;
|
||||||
|
|
||||||
void show(Tile *t); // Open the menu, onto a user tile
|
void show(Tile *t); // Open the menu, onto a user tile
|
||||||
@@ -57,7 +52,6 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data
|
void freeCannedMessageResources(); // Clear MenuApplet's canned message processing data
|
||||||
|
|
||||||
MenuPage currentPage = MenuPage::ROOT;
|
MenuPage currentPage = MenuPage::ROOT;
|
||||||
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)
|
||||||
|
|
||||||
@@ -103,4 +97,4 @@ class MenuApplet : public SystemApplet, public concurrency::OSThread
|
|||||||
|
|
||||||
} // namespace NicheGraphics::InkHUD
|
} // namespace NicheGraphics::InkHUD
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -153,42 +153,6 @@ void InkHUD::NotificationApplet::onButtonLongPress()
|
|||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onExitShort()
|
|
||||||
{
|
|
||||||
dismiss();
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onExitLong()
|
|
||||||
{
|
|
||||||
dismiss();
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onNavUp()
|
|
||||||
{
|
|
||||||
dismiss();
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onNavDown()
|
|
||||||
{
|
|
||||||
dismiss();
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onNavLeft()
|
|
||||||
{
|
|
||||||
dismiss();
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::NotificationApplet::onNavRight()
|
|
||||||
{
|
|
||||||
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
|
||||||
// Called internally when we first get a "notifiable event", and then again before render,
|
// Called internally when we first get a "notifiable event", and then again before render,
|
||||||
// in case autoshow swapped which applet was displayed
|
// in case autoshow swapped which applet was displayed
|
||||||
|
|||||||
@@ -31,12 +31,6 @@ class NotificationApplet : public SystemApplet
|
|||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
void onButtonShortPress() override;
|
void onButtonShortPress() override;
|
||||||
void onButtonLongPress() override;
|
void onButtonLongPress() override;
|
||||||
void onExitShort() override;
|
|
||||||
void onExitLong() override;
|
|
||||||
void onNavUp() override;
|
|
||||||
void onNavDown() override;
|
|
||||||
void onNavLeft() override;
|
|
||||||
void onNavRight() override;
|
|
||||||
|
|
||||||
int onReceiveTextMessage(const meshtastic_MeshPacket *p);
|
int onReceiveTextMessage(const meshtastic_MeshPacket *p);
|
||||||
|
|
||||||
|
|||||||
@@ -112,21 +112,12 @@ void InkHUD::TipsApplet::onRender()
|
|||||||
setFont(fontSmall);
|
setFont(fontSmall);
|
||||||
int16_t cursorY = fontMedium.lineHeight() * 1.5;
|
int16_t cursorY = fontMedium.lineHeight() * 1.5;
|
||||||
|
|
||||||
if (!settings->joystick.enabled) {
|
printAt(0, cursorY, "User Button");
|
||||||
printAt(0, cursorY, "User Button");
|
cursorY += fontSmall.lineHeight() * 1.2;
|
||||||
cursorY += fontSmall.lineHeight() * 1.2;
|
printAt(0, cursorY, "- short press: next");
|
||||||
printAt(0, cursorY, "- short press: next");
|
cursorY += fontSmall.lineHeight() * 1.2;
|
||||||
cursorY += fontSmall.lineHeight() * 1.2;
|
printAt(0, cursorY, "- long press: select / open menu");
|
||||||
printAt(0, cursorY, "- long press: select / open menu");
|
cursorY += fontSmall.lineHeight() * 1.5;
|
||||||
} else {
|
|
||||||
printAt(0, cursorY, "Joystick");
|
|
||||||
cursorY += fontSmall.lineHeight() * 1.2;
|
|
||||||
printAt(0, cursorY, "- open menu / select");
|
|
||||||
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);
|
||||||
} break;
|
} break;
|
||||||
@@ -136,13 +127,8 @@ void InkHUD::TipsApplet::onRender()
|
|||||||
printAt(0, 0, "Tip: Rotation");
|
printAt(0, 0, "Tip: Rotation");
|
||||||
|
|
||||||
setFont(fontSmall);
|
setFont(fontSmall);
|
||||||
if (!settings->joystick.enabled) {
|
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
|
||||||
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
|
"To rotate the display, use the InkHUD menu. Long-press the user button > Options > Rotate.");
|
||||||
"To rotate the display, use the InkHUD menu. Long-press the user button > Options > Rotate.");
|
|
||||||
} else {
|
|
||||||
printWrapped(0, fontMedium.lineHeight() * 1.5, width(),
|
|
||||||
"To rotate the display, use the InkHUD menu. Press the user button > Options > Rotate.");
|
|
||||||
}
|
|
||||||
|
|
||||||
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
printAt(0, Y(1.0), "Press button to continue", LEFT, BOTTOM);
|
||||||
|
|
||||||
@@ -246,10 +232,4 @@ void InkHUD::TipsApplet::onButtonShortPress()
|
|||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Functions the same as the user button in this instance
|
|
||||||
void InkHUD::TipsApplet::onExitShort()
|
|
||||||
{
|
|
||||||
onButtonShortPress();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -36,7 +36,6 @@ class TipsApplet : public SystemApplet
|
|||||||
void onForeground() override;
|
void onForeground() override;
|
||||||
void onBackground() override;
|
void onBackground() override;
|
||||||
void onButtonShortPress() override;
|
void onButtonShortPress() override;
|
||||||
void onExitShort() override;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void renderWelcome(); // Very first screen of tutorial
|
void renderWelcome(); // Very first screen of tutorial
|
||||||
|
|||||||
@@ -62,22 +62,12 @@ void InkHUD::HeardApplet::populateFromNodeDB()
|
|||||||
{
|
{
|
||||||
// Fill a collection with pointers to each node in db
|
// Fill a collection with pointers to each node in db
|
||||||
std::vector<meshtastic_NodeInfoLite *> ordered;
|
std::vector<meshtastic_NodeInfoLite *> ordered;
|
||||||
for (auto mn = nodeDB->meshNodes->begin(); mn != nodeDB->meshNodes->end(); ++mn) {
|
for (int i = 1; i < maxCards(); i++) {
|
||||||
// Only copy if valid, and not our own node
|
auto mn = nodeDB->getMeshNodeByIndex(i);
|
||||||
if (mn->num != 0 && mn->num != nodeDB->getNodeNum())
|
if (mn->num != 0 && mn->num != nodeDB->getNodeNum())
|
||||||
ordered.push_back(&*mn);
|
ordered.push_back(&*mn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort the collection by age
|
|
||||||
std::sort(ordered.begin(), ordered.end(), [](meshtastic_NodeInfoLite *top, meshtastic_NodeInfoLite *bottom) -> bool {
|
|
||||||
return (top->last_heard > bottom->last_heard);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Keep the most recent entries only
|
|
||||||
// Just enough to fill the screen
|
|
||||||
if (ordered.size() > maxCards())
|
|
||||||
ordered.resize(maxCards());
|
|
||||||
|
|
||||||
// Create card info for these (stale) node observations
|
// Create card info for these (stale) node observations
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
for (meshtastic_NodeInfoLite *node : ordered) {
|
for (meshtastic_NodeInfoLite *node : ordered) {
|
||||||
|
|||||||
@@ -55,15 +55,10 @@ void InkHUD::Events::onButtonShort()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If no system applet is handling input, default behavior instead is to cycle applets
|
// If no system applet is handling input, default behavior instead is to cycle applets
|
||||||
// or open menu if joystick is enabled
|
if (consumer)
|
||||||
if (consumer) {
|
|
||||||
consumer->onButtonShortPress();
|
consumer->onButtonShortPress();
|
||||||
} else if (!dismissedExt) { // Don't change applet if this button press silenced the external notification module
|
else if (!dismissedExt) // Don't change applet if this button press silenced the external notification module
|
||||||
if (!settings->joystick.enabled)
|
inkhud->nextApplet();
|
||||||
inkhud->nextApplet();
|
|
||||||
else
|
|
||||||
inkhud->openMenu();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::Events::onButtonLong()
|
void InkHUD::Events::onButtonLong()
|
||||||
@@ -88,156 +83,6 @@ void InkHUD::Events::onButtonLong()
|
|||||||
inkhud->openMenu();
|
inkhud->openMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::Events::onExitShort()
|
|
||||||
{
|
|
||||||
if (settings->joystick.enabled) {
|
|
||||||
// Audio feedback (via buzzer)
|
|
||||||
// Short tone
|
|
||||||
playChirp();
|
|
||||||
// Cancel any beeping, buzzing, blinking
|
|
||||||
// Some button handling suppressed if we are dismissing an external notification (see below)
|
|
||||||
bool dismissedExt = dismissExternalNotification();
|
|
||||||
|
|
||||||
// Check which system applet wants to handle the button press (if any)
|
|
||||||
SystemApplet *consumer = nullptr;
|
|
||||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
|
||||||
if (sa->handleInput) {
|
|
||||||
consumer = sa;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no system applet is handling input, default behavior instead is change tiles
|
|
||||||
if (consumer)
|
|
||||||
consumer->onExitShort();
|
|
||||||
else if (!dismissedExt) // Don't change tile if this button press silenced the external notification module
|
|
||||||
inkhud->nextTile();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::Events::onExitLong()
|
|
||||||
{
|
|
||||||
if (settings->joystick.enabled) {
|
|
||||||
// Audio feedback (via buzzer)
|
|
||||||
// Slightly longer than playChirp
|
|
||||||
playBoop();
|
|
||||||
|
|
||||||
// Check which system applet wants to handle the button press (if any)
|
|
||||||
SystemApplet *consumer = nullptr;
|
|
||||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
|
||||||
if (sa->handleInput) {
|
|
||||||
consumer = sa;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (consumer)
|
|
||||||
consumer->onExitLong();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::Events::onNavUp()
|
|
||||||
{
|
|
||||||
if (settings->joystick.enabled) {
|
|
||||||
// Audio feedback (via buzzer)
|
|
||||||
// Short tone
|
|
||||||
playChirp();
|
|
||||||
// Cancel any beeping, buzzing, blinking
|
|
||||||
// Some button handling suppressed if we are dismissing an external notification (see below)
|
|
||||||
bool dismissedExt = dismissExternalNotification();
|
|
||||||
|
|
||||||
// Check which system applet wants to handle the button press (if any)
|
|
||||||
SystemApplet *consumer = nullptr;
|
|
||||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
|
||||||
if (sa->handleInput) {
|
|
||||||
consumer = sa;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (consumer)
|
|
||||||
consumer->onNavUp();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::Events::onNavDown()
|
|
||||||
{
|
|
||||||
if (settings->joystick.enabled) {
|
|
||||||
// Audio feedback (via buzzer)
|
|
||||||
// Short tone
|
|
||||||
playChirp();
|
|
||||||
// Cancel any beeping, buzzing, blinking
|
|
||||||
// Some button handling suppressed if we are dismissing an external notification (see below)
|
|
||||||
bool dismissedExt = dismissExternalNotification();
|
|
||||||
|
|
||||||
// Check which system applet wants to handle the button press (if any)
|
|
||||||
SystemApplet *consumer = nullptr;
|
|
||||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
|
||||||
if (sa->handleInput) {
|
|
||||||
consumer = sa;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (consumer)
|
|
||||||
consumer->onNavDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::Events::onNavLeft()
|
|
||||||
{
|
|
||||||
if (settings->joystick.enabled) {
|
|
||||||
// Audio feedback (via buzzer)
|
|
||||||
// Short tone
|
|
||||||
playChirp();
|
|
||||||
// Cancel any beeping, buzzing, blinking
|
|
||||||
// Some button handling suppressed if we are dismissing an external notification (see below)
|
|
||||||
bool dismissedExt = dismissExternalNotification();
|
|
||||||
|
|
||||||
// Check which system applet wants to handle the button press (if any)
|
|
||||||
SystemApplet *consumer = nullptr;
|
|
||||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
|
||||||
if (sa->handleInput) {
|
|
||||||
consumer = sa;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no system applet is handling input, default behavior instead is to cycle applets
|
|
||||||
if (consumer)
|
|
||||||
consumer->onNavLeft();
|
|
||||||
else if (!dismissedExt) // Don't change applet if this button press silenced the external notification module
|
|
||||||
inkhud->prevApplet();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InkHUD::Events::onNavRight()
|
|
||||||
{
|
|
||||||
if (settings->joystick.enabled) {
|
|
||||||
// Audio feedback (via buzzer)
|
|
||||||
// Short tone
|
|
||||||
playChirp();
|
|
||||||
// Cancel any beeping, buzzing, blinking
|
|
||||||
// Some button handling suppressed if we are dismissing an external notification (see below)
|
|
||||||
bool dismissedExt = dismissExternalNotification();
|
|
||||||
|
|
||||||
// Check which system applet wants to handle the button press (if any)
|
|
||||||
SystemApplet *consumer = nullptr;
|
|
||||||
for (SystemApplet *sa : inkhud->systemApplets) {
|
|
||||||
if (sa->handleInput) {
|
|
||||||
consumer = sa;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no system applet is handling input, default behavior instead is to cycle applets
|
|
||||||
if (consumer)
|
|
||||||
consumer->onNavRight();
|
|
||||||
else if (!dismissedExt) // Don't change applet if this button press silenced the external notification module
|
|
||||||
inkhud->nextApplet();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
||||||
|
|||||||
@@ -29,12 +29,6 @@ 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 onExitShort(); // Exit button: short press
|
|
||||||
void onExitLong(); // Exit button: long press
|
|
||||||
void onNavUp(); // Navigate up
|
|
||||||
void onNavDown(); // Navigate down
|
|
||||||
void onNavLeft(); // Navigate left
|
|
||||||
void onNavRight(); // Navigate right
|
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
@@ -80,94 +80,6 @@ void InkHUD::InkHUD::longpress()
|
|||||||
events->onButtonLong();
|
events->onButtonLong();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call this when your exit button gets a short press
|
|
||||||
void InkHUD::InkHUD::exitShort()
|
|
||||||
{
|
|
||||||
events->onExitShort();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call this when your exit button gets a long press
|
|
||||||
void InkHUD::InkHUD::exitLong()
|
|
||||||
{
|
|
||||||
events->onExitLong();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call this when your joystick gets an up input
|
|
||||||
void InkHUD::InkHUD::navUp()
|
|
||||||
{
|
|
||||||
switch ((persistence->settings.rotation + persistence->settings.joystick.alignment) % 4) {
|
|
||||||
case 1: // 90 deg
|
|
||||||
events->onNavLeft();
|
|
||||||
break;
|
|
||||||
case 2: // 180 deg
|
|
||||||
events->onNavDown();
|
|
||||||
break;
|
|
||||||
case 3: // 270 deg
|
|
||||||
events->onNavRight();
|
|
||||||
break;
|
|
||||||
default: // 0 deg
|
|
||||||
events->onNavUp();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call this when your joystick gets a down input
|
|
||||||
void InkHUD::InkHUD::navDown()
|
|
||||||
{
|
|
||||||
switch ((persistence->settings.rotation + persistence->settings.joystick.alignment) % 4) {
|
|
||||||
case 1: // 90 deg
|
|
||||||
events->onNavRight();
|
|
||||||
break;
|
|
||||||
case 2: // 180 deg
|
|
||||||
events->onNavUp();
|
|
||||||
break;
|
|
||||||
case 3: // 270 deg
|
|
||||||
events->onNavLeft();
|
|
||||||
break;
|
|
||||||
default: // 0 deg
|
|
||||||
events->onNavDown();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call this when your joystick gets a left input
|
|
||||||
void InkHUD::InkHUD::navLeft()
|
|
||||||
{
|
|
||||||
switch ((persistence->settings.rotation + persistence->settings.joystick.alignment) % 4) {
|
|
||||||
case 1: // 90 deg
|
|
||||||
events->onNavDown();
|
|
||||||
break;
|
|
||||||
case 2: // 180 deg
|
|
||||||
events->onNavRight();
|
|
||||||
break;
|
|
||||||
case 3: // 270 deg
|
|
||||||
events->onNavUp();
|
|
||||||
break;
|
|
||||||
default: // 0 deg
|
|
||||||
events->onNavLeft();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call this when your joystick gets a right input
|
|
||||||
void InkHUD::InkHUD::navRight()
|
|
||||||
{
|
|
||||||
switch ((persistence->settings.rotation + persistence->settings.joystick.alignment) % 4) {
|
|
||||||
case 1: // 90 deg
|
|
||||||
events->onNavUp();
|
|
||||||
break;
|
|
||||||
case 2: // 180 deg
|
|
||||||
events->onNavLeft();
|
|
||||||
break;
|
|
||||||
case 3: // 270 deg
|
|
||||||
events->onNavDown();
|
|
||||||
break;
|
|
||||||
default: // 0 deg
|
|
||||||
events->onNavRight();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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"
|
||||||
@@ -176,14 +88,6 @@ void InkHUD::InkHUD::nextApplet()
|
|||||||
windowManager->nextApplet();
|
windowManager->nextApplet();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cycle the previous user applet to the foreground
|
|
||||||
// Only activated applets are cycled
|
|
||||||
// If user has a multi-applet layout, the applets will cycle on the "focused tile"
|
|
||||||
void InkHUD::InkHUD::prevApplet()
|
|
||||||
{
|
|
||||||
windowManager->prevApplet();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show the menu (on the the focused tile)
|
// Show the menu (on the the focused tile)
|
||||||
// The applet previously displayed there will be restored once the menu closes
|
// The applet previously displayed there will be restored once the menu closes
|
||||||
void InkHUD::InkHUD::openMenu()
|
void InkHUD::InkHUD::openMenu()
|
||||||
@@ -191,12 +95,6 @@ void InkHUD::InkHUD::openMenu()
|
|||||||
windowManager->openMenu();
|
windowManager->openMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bring AlignStick applet to the foreground
|
|
||||||
void InkHUD::InkHUD::openAlignStick()
|
|
||||||
{
|
|
||||||
windowManager->openAlignStick();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
||||||
@@ -204,26 +102,12 @@ void InkHUD::InkHUD::nextTile()
|
|||||||
windowManager->nextTile();
|
windowManager->nextTile();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
void InkHUD::InkHUD::prevTile()
|
|
||||||
{
|
|
||||||
windowManager->prevTile();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rotate the display image by 90 degrees
|
// Rotate the display image by 90 degrees
|
||||||
void InkHUD::InkHUD::rotate()
|
void InkHUD::InkHUD::rotate()
|
||||||
{
|
{
|
||||||
windowManager->rotate();
|
windowManager->rotate();
|
||||||
}
|
}
|
||||||
|
|
||||||
// rotate the joystick in 90 degree increments
|
|
||||||
void InkHUD::InkHUD::rotateJoystick(uint8_t angle)
|
|
||||||
{
|
|
||||||
persistence->settings.joystick.alignment += angle;
|
|
||||||
persistence->settings.joystick.alignment %= 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show / hide the battery indicator in top-right
|
// Show / hide the battery indicator in top-right
|
||||||
void InkHUD::InkHUD::toggleBatteryIcon()
|
void InkHUD::InkHUD::toggleBatteryIcon()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,25 +55,15 @@ class InkHUD
|
|||||||
|
|
||||||
void shortpress();
|
void shortpress();
|
||||||
void longpress();
|
void longpress();
|
||||||
void exitShort();
|
|
||||||
void exitLong();
|
|
||||||
void navUp();
|
|
||||||
void navDown();
|
|
||||||
void navLeft();
|
|
||||||
void navRight();
|
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
void nextApplet();
|
void nextApplet();
|
||||||
void prevApplet();
|
|
||||||
void openMenu();
|
void openMenu();
|
||||||
void openAlignStick();
|
|
||||||
void nextTile();
|
void nextTile();
|
||||||
void prevTile();
|
|
||||||
void rotate();
|
void rotate();
|
||||||
void rotateJoystick(uint8_t angle = 1); // rotate 90 deg by default
|
|
||||||
void toggleBatteryIcon();
|
void toggleBatteryIcon();
|
||||||
|
|
||||||
// Updating the display
|
// Updating the display
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class Persistence
|
|||||||
|
|
||||||
// Used to invalidate old settings, if needed
|
// Used to invalidate old settings, if needed
|
||||||
// Version 0 is reserved for testing, and will always load defaults
|
// Version 0 is reserved for testing, and will always load defaults
|
||||||
static constexpr uint32_t SETTINGS_VERSION = 3;
|
static constexpr uint32_t SETTINGS_VERSION = 2;
|
||||||
|
|
||||||
struct Settings {
|
struct Settings {
|
||||||
struct Meta {
|
struct Meta {
|
||||||
@@ -96,19 +96,6 @@ class Persistence
|
|||||||
bool safeShutdownSeen = false;
|
bool safeShutdownSeen = false;
|
||||||
} tips;
|
} tips;
|
||||||
|
|
||||||
// Joystick settings for enabling and aligning to the screen
|
|
||||||
struct Joystick {
|
|
||||||
// Modifies the UI for joystick use
|
|
||||||
bool enabled = false;
|
|
||||||
|
|
||||||
// gets set to true when AlignStick applet is completed
|
|
||||||
bool aligned = false;
|
|
||||||
|
|
||||||
// Rotation of the joystick
|
|
||||||
// Multiples of 90 degrees clockwise
|
|
||||||
uint8_t alignment = 0;
|
|
||||||
} joystick;
|
|
||||||
|
|
||||||
// Rotation of the display
|
// Rotation of the display
|
||||||
// Multiples of 90 degrees clockwise
|
// Multiples of 90 degrees clockwise
|
||||||
// Most commonly: rotation is 0 when flex connector is oriented below display
|
// Most commonly: rotation is 0 when flex connector is oriented below display
|
||||||
|
|||||||
@@ -8,5 +8,4 @@ build_flags =
|
|||||||
-D MESHTASTIC_EXCLUDE_INPUTBROKER ; Suppress default input handling
|
-D MESHTASTIC_EXCLUDE_INPUTBROKER ; Suppress default input handling
|
||||||
-D HAS_BUTTON=0 ; Suppress default ButtonThread
|
-D HAS_BUTTON=0 ; Suppress default ButtonThread
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# TODO renovate
|
https://github.com/ZinggJM/GFX_Root#2.0.0 ; Used by InkHUD as a "slimmer" version of AdafruitGFX
|
||||||
https://github.com/ZinggJM/GFX_Root#2.0.0 ; Used by InkHUD as a "slimmer" version of AdafruitGFX
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "./WindowManager.h"
|
#include "./WindowManager.h"
|
||||||
|
|
||||||
#include "./Applets/System/AlignStick/AlignStickApplet.h"
|
|
||||||
#include "./Applets/System/BatteryIcon/BatteryIconApplet.h"
|
#include "./Applets/System/BatteryIcon/BatteryIconApplet.h"
|
||||||
#include "./Applets/System/Logo/LogoApplet.h"
|
#include "./Applets/System/Logo/LogoApplet.h"
|
||||||
#include "./Applets/System/Menu/MenuApplet.h"
|
#include "./Applets/System/Menu/MenuApplet.h"
|
||||||
@@ -99,38 +98,6 @@ void InkHUD::WindowManager::nextTile()
|
|||||||
userTiles.at(settings->userTiles.focused)->requestHighlight();
|
userTiles.at(settings->userTiles.focused)->requestHighlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Focus on a different tile but decrement index
|
|
||||||
void InkHUD::WindowManager::prevTile()
|
|
||||||
{
|
|
||||||
// Close the menu applet if open
|
|
||||||
// We don't *really* want to do this, but it simplifies handling *a lot*
|
|
||||||
MenuApplet *menu = (MenuApplet *)inkhud->getSystemApplet("Menu");
|
|
||||||
bool menuWasOpen = false;
|
|
||||||
if (menu->isForeground()) {
|
|
||||||
menu->sendToBackground();
|
|
||||||
menuWasOpen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap to next tile
|
|
||||||
if (settings->userTiles.focused == 0)
|
|
||||||
settings->userTiles.focused = settings->userTiles.count - 1;
|
|
||||||
else
|
|
||||||
settings->userTiles.focused--;
|
|
||||||
|
|
||||||
// Make sure that we don't get stuck on the placeholder tile
|
|
||||||
refocusTile();
|
|
||||||
|
|
||||||
if (menuWasOpen)
|
|
||||||
menu->show(userTiles.at(settings->userTiles.focused));
|
|
||||||
|
|
||||||
// Ask the tile to draw an indicator showing which tile is now focused
|
|
||||||
// Requests a render
|
|
||||||
// We only draw this indicator if the device uses an aux button to switch tiles.
|
|
||||||
// Assume aux button is used to switch tiles if the "next tile" menu item is hidden
|
|
||||||
if (!settings->optionalMenuItems.nextTile)
|
|
||||||
userTiles.at(settings->userTiles.focused)->requestHighlight();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show the menu (on the the focused tile)
|
// Show the menu (on the the focused tile)
|
||||||
// The applet previously displayed there will be restored once the menu closes
|
// The applet previously displayed there will be restored once the menu closes
|
||||||
void InkHUD::WindowManager::openMenu()
|
void InkHUD::WindowManager::openMenu()
|
||||||
@@ -139,15 +106,6 @@ void InkHUD::WindowManager::openMenu()
|
|||||||
menu->show(userTiles.at(settings->userTiles.focused));
|
menu->show(userTiles.at(settings->userTiles.focused));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bring the AlignStick applet to the foreground
|
|
||||||
void InkHUD::WindowManager::openAlignStick()
|
|
||||||
{
|
|
||||||
if (settings->joystick.enabled) {
|
|
||||||
AlignStickApplet *alignStick = (AlignStickApplet *)inkhud->getSystemApplet("AlignStick");
|
|
||||||
alignStick->bringToForeground();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// On the currently focussed tile: cycle to the next available user applet
|
// On the currently focussed tile: cycle to the next available user applet
|
||||||
// Applets available for this must be activated, and not already displayed on another tile
|
// Applets available for this must be activated, and not already displayed on another tile
|
||||||
void InkHUD::WindowManager::nextApplet()
|
void InkHUD::WindowManager::nextApplet()
|
||||||
@@ -197,59 +155,6 @@ void InkHUD::WindowManager::nextApplet()
|
|||||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST); // bringToForeground already requested, but we're manually forcing FAST
|
inkhud->forceUpdate(EInk::UpdateTypes::FAST); // bringToForeground already requested, but we're manually forcing FAST
|
||||||
}
|
}
|
||||||
|
|
||||||
// On the currently focussed tile: cycle to the previous available user applet
|
|
||||||
// Applets available for this must be activated, and not already displayed on another tile
|
|
||||||
void InkHUD::WindowManager::prevApplet()
|
|
||||||
{
|
|
||||||
Tile *t = userTiles.at(settings->userTiles.focused);
|
|
||||||
|
|
||||||
// Abort if zero applets available
|
|
||||||
// nullptr means WindowManager::refocusTile determined that there were no available applets
|
|
||||||
if (!t->getAssignedApplet())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Find the index of the applet currently shown on the tile
|
|
||||||
uint8_t appletIndex = -1;
|
|
||||||
for (uint8_t i = 0; i < inkhud->userApplets.size(); i++) {
|
|
||||||
if (inkhud->userApplets.at(i) == t->getAssignedApplet()) {
|
|
||||||
appletIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm that we did find the applet
|
|
||||||
assert(appletIndex != (uint8_t)-1);
|
|
||||||
|
|
||||||
// Iterate forward through the WindowManager::applets, looking for the previous valid applet
|
|
||||||
Applet *prevValidApplet = nullptr;
|
|
||||||
for (uint8_t i = 1; i < inkhud->userApplets.size(); i++) {
|
|
||||||
uint8_t newAppletIndex = 0;
|
|
||||||
if (i > appletIndex)
|
|
||||||
newAppletIndex = inkhud->userApplets.size() + appletIndex - i;
|
|
||||||
else
|
|
||||||
newAppletIndex = (appletIndex - i);
|
|
||||||
Applet *a = inkhud->userApplets.at(newAppletIndex);
|
|
||||||
|
|
||||||
// Looking for an applet which is active (enabled by user), but currently in background
|
|
||||||
if (a->isActive() && !a->isForeground()) {
|
|
||||||
prevValidApplet = a;
|
|
||||||
settings->userTiles.displayedUserApplet[settings->userTiles.focused] =
|
|
||||||
newAppletIndex; // Remember this setting between boots!
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm that we found another applet
|
|
||||||
if (!prevValidApplet)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Hide old applet, show new applet
|
|
||||||
t->getAssignedApplet()->sendToBackground();
|
|
||||||
t->assignApplet(prevValidApplet);
|
|
||||||
prevValidApplet->bringToForeground();
|
|
||||||
inkhud->forceUpdate(EInk::UpdateTypes::FAST); // bringToForeground already requested, but we're manually forcing FAST
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rotate the display image by 90 degrees
|
// Rotate the display image by 90 degrees
|
||||||
void InkHUD::WindowManager::rotate()
|
void InkHUD::WindowManager::rotate()
|
||||||
{
|
{
|
||||||
@@ -433,8 +338,6 @@ void InkHUD::WindowManager::createSystemApplets()
|
|||||||
addSystemApplet("Logo", new LogoApplet, new Tile);
|
addSystemApplet("Logo", new LogoApplet, new Tile);
|
||||||
addSystemApplet("Pairing", new PairingApplet, new Tile);
|
addSystemApplet("Pairing", new PairingApplet, new Tile);
|
||||||
addSystemApplet("Tips", new TipsApplet, new Tile);
|
addSystemApplet("Tips", new TipsApplet, new Tile);
|
||||||
if (settings->joystick.enabled)
|
|
||||||
addSystemApplet("AlignStick", new AlignStickApplet, new Tile);
|
|
||||||
|
|
||||||
addSystemApplet("Menu", new MenuApplet, nullptr);
|
addSystemApplet("Menu", new MenuApplet, nullptr);
|
||||||
|
|
||||||
@@ -457,8 +360,6 @@ void InkHUD::WindowManager::placeSystemTiles()
|
|||||||
inkhud->getSystemApplet("Logo")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
inkhud->getSystemApplet("Logo")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
||||||
inkhud->getSystemApplet("Pairing")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
inkhud->getSystemApplet("Pairing")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
||||||
inkhud->getSystemApplet("Tips")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
inkhud->getSystemApplet("Tips")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
||||||
if (settings->joystick.enabled)
|
|
||||||
inkhud->getSystemApplet("AlignStick")->getTile()->setRegion(0, 0, inkhud->width(), inkhud->height());
|
|
||||||
|
|
||||||
inkhud->getSystemApplet("Notification")->getTile()->setRegion(0, 0, inkhud->width(), 20);
|
inkhud->getSystemApplet("Notification")->getTile()->setRegion(0, 0, inkhud->width(), 20);
|
||||||
|
|
||||||
|
|||||||
@@ -28,11 +28,8 @@ class WindowManager
|
|||||||
// - call these to make stuff change
|
// - call these to make stuff change
|
||||||
|
|
||||||
void nextTile();
|
void nextTile();
|
||||||
void prevTile();
|
|
||||||
void openMenu();
|
void openMenu();
|
||||||
void openAlignStick();
|
|
||||||
void nextApplet();
|
void nextApplet();
|
||||||
void prevApplet();
|
|
||||||
void rotate();
|
void rotate();
|
||||||
void toggleBatteryIcon();
|
void toggleBatteryIcon();
|
||||||
|
|
||||||
|
|||||||
@@ -273,7 +273,7 @@ _(Example shows only config required by InkHUD. This is not a complete `env` def
|
|||||||
extends = esp32s3_base, inkhud ; or nrf52840_base, etc
|
extends = esp32s3_base, inkhud ; or nrf52840_base, etc
|
||||||
|
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
${esp32s3_base.build_src_filter}
|
${esp32_base.build_src_filter}
|
||||||
${inkhud.build_src_filter}
|
${inkhud.build_src_filter}
|
||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
@@ -756,12 +756,12 @@ This mapping of emoji to control characters is fairly arbitrary. Selection was i
|
|||||||
| `0x03` | 🙂 |
|
| `0x03` | 🙂 |
|
||||||
| `0x04` | 😆 |
|
| `0x04` | 😆 |
|
||||||
| `0x05` | 👋 |
|
| `0x05` | 👋 |
|
||||||
| `0x06` | ☀ |
|
| `0x06` | ☀ |
|
||||||
| ~~`0x07`~~ | (bell char, unused) |
|
| ~~`0x07`~~ | (bell char, unused) |
|
||||||
| `0x08` | 🌧 |
|
| `0x08` | 🌧 |
|
||||||
| `0x09` | ☁ |
|
| `0x09` | ☁ |
|
||||||
| ~~`0x0A`~~ | (line feed, unused) |
|
| ~~`0x0A`~~ | (line feed, unused) |
|
||||||
| `0x0B` | ♥ |
|
| `0x0B` | ♥ |
|
||||||
| `0x0C` | 💩 |
|
| `0x0C` | 💩 |
|
||||||
| ~~`0x0D`~~ | (carriage return, unused) |
|
| ~~`0x0D`~~ | (carriage return, unused) |
|
||||||
| `0x0E` | 🔔 |
|
| `0x0E` | 🔔 |
|
||||||
|
|||||||
@@ -1,523 +0,0 @@
|
|||||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
|
||||||
|
|
||||||
#include "./TwoButtonExtended.h"
|
|
||||||
|
|
||||||
#include "NodeDB.h" // For the helper function TwoButtonExtended::getUserButtonPin
|
|
||||||
#include "PowerFSM.h"
|
|
||||||
#include "sleep.h"
|
|
||||||
|
|
||||||
using namespace NicheGraphics::Inputs;
|
|
||||||
|
|
||||||
TwoButtonExtended::TwoButtonExtended() : concurrency::OSThread("TwoButtonExtended")
|
|
||||||
{
|
|
||||||
// Don't start polling buttons for release immediately
|
|
||||||
// Assume they are in a "released" state at boot
|
|
||||||
OSThread::disable();
|
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
// Register callbacks for before and after lightsleep
|
|
||||||
lsObserver.observe(¬ifyLightSleep);
|
|
||||||
lsEndObserver.observe(¬ifyLightSleepEnd);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Explicitly initialize these, just to keep cppcheck quiet..
|
|
||||||
buttons[0] = Button();
|
|
||||||
buttons[1] = Button();
|
|
||||||
joystick[Direction::UP] = SimpleButton();
|
|
||||||
joystick[Direction::DOWN] = SimpleButton();
|
|
||||||
joystick[Direction::LEFT] = SimpleButton();
|
|
||||||
joystick[Direction::RIGHT] = SimpleButton();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get access to (or create) the singleton instance of this class
|
|
||||||
// Accessible inside the ISRs, even though we maybe shouldn't
|
|
||||||
TwoButtonExtended *TwoButtonExtended::getInstance()
|
|
||||||
{
|
|
||||||
// Instantiate the class the first time this method is called
|
|
||||||
static TwoButtonExtended *const singletonInstance = new TwoButtonExtended;
|
|
||||||
|
|
||||||
return singletonInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Begin receiving button input
|
|
||||||
// We probably need to do this after sleep, as well as at boot
|
|
||||||
void TwoButtonExtended::start()
|
|
||||||
{
|
|
||||||
if (buttons[0].pin != 0xFF)
|
|
||||||
attachInterrupt(buttons[0].pin, TwoButtonExtended::isrPrimary, buttons[0].activeLogic == LOW ? FALLING : RISING);
|
|
||||||
|
|
||||||
if (buttons[1].pin != 0xFF)
|
|
||||||
attachInterrupt(buttons[1].pin, TwoButtonExtended::isrSecondary, buttons[1].activeLogic == LOW ? FALLING : RISING);
|
|
||||||
|
|
||||||
if (joystick[Direction::UP].pin != 0xFF)
|
|
||||||
attachInterrupt(joystick[Direction::UP].pin, TwoButtonExtended::isrJoystickUp,
|
|
||||||
joystickActiveLogic == LOW ? FALLING : RISING);
|
|
||||||
|
|
||||||
if (joystick[Direction::DOWN].pin != 0xFF)
|
|
||||||
attachInterrupt(joystick[Direction::DOWN].pin, TwoButtonExtended::isrJoystickDown,
|
|
||||||
joystickActiveLogic == LOW ? FALLING : RISING);
|
|
||||||
|
|
||||||
if (joystick[Direction::LEFT].pin != 0xFF)
|
|
||||||
attachInterrupt(joystick[Direction::LEFT].pin, TwoButtonExtended::isrJoystickLeft,
|
|
||||||
joystickActiveLogic == LOW ? FALLING : RISING);
|
|
||||||
|
|
||||||
if (joystick[Direction::RIGHT].pin != 0xFF)
|
|
||||||
attachInterrupt(joystick[Direction::RIGHT].pin, TwoButtonExtended::isrJoystickRight,
|
|
||||||
joystickActiveLogic == LOW ? FALLING : RISING);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop receiving button input, and run custom sleep code
|
|
||||||
// Called before device sleeps. This might be power-off, or just ESP32 light sleep
|
|
||||||
// Some devices will want to attach interrupts here, for the user button to wake from sleep
|
|
||||||
void TwoButtonExtended::stop()
|
|
||||||
{
|
|
||||||
if (buttons[0].pin != 0xFF)
|
|
||||||
detachInterrupt(buttons[0].pin);
|
|
||||||
|
|
||||||
if (buttons[1].pin != 0xFF)
|
|
||||||
detachInterrupt(buttons[1].pin);
|
|
||||||
|
|
||||||
if (joystick[Direction::UP].pin != 0xFF)
|
|
||||||
detachInterrupt(joystick[Direction::UP].pin);
|
|
||||||
|
|
||||||
if (joystick[Direction::DOWN].pin != 0xFF)
|
|
||||||
detachInterrupt(joystick[Direction::DOWN].pin);
|
|
||||||
|
|
||||||
if (joystick[Direction::LEFT].pin != 0xFF)
|
|
||||||
detachInterrupt(joystick[Direction::LEFT].pin);
|
|
||||||
|
|
||||||
if (joystick[Direction::RIGHT].pin != 0xFF)
|
|
||||||
detachInterrupt(joystick[Direction::RIGHT].pin);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to resolve a GPIO pin for the user button, honoring userPrefs.jsonc and device settings
|
|
||||||
// This helper method isn't used by the TwoButtonExtended class itself, it could be moved elsewhere.
|
|
||||||
// Intention is to pass this value to TwoButtonExtended::setWiring in the setupNicheGraphics method.
|
|
||||||
uint8_t TwoButtonExtended::getUserButtonPin()
|
|
||||||
{
|
|
||||||
uint8_t pin = 0xFF; // Unset
|
|
||||||
|
|
||||||
// Use default pin for variant, if no better source
|
|
||||||
#ifdef BUTTON_PIN
|
|
||||||
pin = BUTTON_PIN;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// From userPrefs.jsonc, if set
|
|
||||||
#ifdef USERPREFS_BUTTON_PIN
|
|
||||||
pin = USERPREFS_BUTTON_PIN;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// From user's override in device settings, if set
|
|
||||||
if (config.device.button_gpio)
|
|
||||||
pin = config.device.button_gpio;
|
|
||||||
|
|
||||||
return pin;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configures the wiring and logic of either button
|
|
||||||
// Called when outlining your NicheGraphics implementation, in variant/nicheGraphics.cpp
|
|
||||||
void TwoButtonExtended::setWiring(uint8_t whichButton, uint8_t pin, bool internalPullup)
|
|
||||||
{
|
|
||||||
// Prevent the same GPIO being assigned to multiple buttons
|
|
||||||
// Allows an edge case when the user remaps hardware buttons using device settings, due to a broken user button
|
|
||||||
for (uint8_t i = 0; i < whichButton; i++) {
|
|
||||||
if (buttons[i].pin == pin) {
|
|
||||||
LOG_WARN("Attempted reuse of GPIO %d. Ignoring assignment whichButton=%d", pin, whichButton);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(whichButton < 2);
|
|
||||||
buttons[whichButton].pin = pin;
|
|
||||||
buttons[whichButton].activeLogic = LOW;
|
|
||||||
|
|
||||||
pinMode(buttons[whichButton].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configures the wiring and logic of the joystick buttons
|
|
||||||
// Called when outlining your NicheGraphics implementation, in variant/nicheGraphics.cpp
|
|
||||||
void TwoButtonExtended::setJoystickWiring(uint8_t uPin, uint8_t dPin, uint8_t lPin, uint8_t rPin, bool internalPullup)
|
|
||||||
{
|
|
||||||
if (joystick[Direction::UP].pin == uPin || joystick[Direction::DOWN].pin == dPin || joystick[Direction::LEFT].pin == lPin ||
|
|
||||||
joystick[Direction::RIGHT].pin == rPin) {
|
|
||||||
LOG_WARN("Attempted reuse of Joystick GPIO. Ignoring assignment");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
joystick[Direction::UP].pin = uPin;
|
|
||||||
joystick[Direction::DOWN].pin = dPin;
|
|
||||||
joystick[Direction::LEFT].pin = lPin;
|
|
||||||
joystick[Direction::RIGHT].pin = rPin;
|
|
||||||
joystickActiveLogic = LOW;
|
|
||||||
|
|
||||||
pinMode(joystick[Direction::UP].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
|
||||||
pinMode(joystick[Direction::DOWN].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
|
||||||
pinMode(joystick[Direction::LEFT].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
|
||||||
pinMode(joystick[Direction::RIGHT].pin, internalPullup ? INPUT_PULLUP : INPUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TwoButtonExtended::setTiming(uint8_t whichButton, uint32_t debounceMs, uint32_t longpressMs)
|
|
||||||
{
|
|
||||||
assert(whichButton < 2);
|
|
||||||
buttons[whichButton].debounceLength = debounceMs;
|
|
||||||
buttons[whichButton].longpressLength = longpressMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TwoButtonExtended::setJoystickDebounce(uint32_t debounceMs)
|
|
||||||
{
|
|
||||||
joystickDebounceLength = debounceMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set what should happen when a button becomes pressed
|
|
||||||
// Use this to implement a "while held" behavior
|
|
||||||
void TwoButtonExtended::setHandlerDown(uint8_t whichButton, Callback onDown)
|
|
||||||
{
|
|
||||||
assert(whichButton < 2);
|
|
||||||
buttons[whichButton].onDown = onDown;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set what should happen when a button becomes unpressed
|
|
||||||
// Use this to implement a "While held" behavior
|
|
||||||
void TwoButtonExtended::setHandlerUp(uint8_t whichButton, Callback onUp)
|
|
||||||
{
|
|
||||||
assert(whichButton < 2);
|
|
||||||
buttons[whichButton].onUp = onUp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set what should happen when a "short press" event has occurred
|
|
||||||
void TwoButtonExtended::setHandlerShortPress(uint8_t whichButton, Callback onPress)
|
|
||||||
{
|
|
||||||
assert(whichButton < 2);
|
|
||||||
buttons[whichButton].onPress = onPress;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set what should happen when a "long press" event has fired
|
|
||||||
// Note: this will occur while the button is still held
|
|
||||||
void TwoButtonExtended::setHandlerLongPress(uint8_t whichButton, Callback onLongPress)
|
|
||||||
{
|
|
||||||
assert(whichButton < 2);
|
|
||||||
buttons[whichButton].onLongPress = onLongPress;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set what should happen when a joystick button becomes pressed
|
|
||||||
// Use this to implement a "while held" behavior
|
|
||||||
void TwoButtonExtended::setJoystickDownHandlers(Callback uDown, Callback dDown, Callback lDown, Callback rDown)
|
|
||||||
{
|
|
||||||
joystick[Direction::UP].onDown = uDown;
|
|
||||||
joystick[Direction::DOWN].onDown = dDown;
|
|
||||||
joystick[Direction::LEFT].onDown = lDown;
|
|
||||||
joystick[Direction::RIGHT].onDown = rDown;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set what should happen when a joystick button becomes unpressed
|
|
||||||
// Use this to implement a "while held" behavior
|
|
||||||
void TwoButtonExtended::setJoystickUpHandlers(Callback uUp, Callback dUp, Callback lUp, Callback rUp)
|
|
||||||
{
|
|
||||||
joystick[Direction::UP].onUp = uUp;
|
|
||||||
joystick[Direction::DOWN].onUp = dUp;
|
|
||||||
joystick[Direction::LEFT].onUp = lUp;
|
|
||||||
joystick[Direction::RIGHT].onUp = rUp;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set what should happen when a "press" event has fired
|
|
||||||
// Note: this will occur while the joystick button is still held
|
|
||||||
void TwoButtonExtended::setJoystickPressHandlers(Callback uPress, Callback dPress, Callback lPress, Callback rPress)
|
|
||||||
{
|
|
||||||
joystick[Direction::UP].onPress = uPress;
|
|
||||||
joystick[Direction::DOWN].onPress = dPress;
|
|
||||||
joystick[Direction::LEFT].onPress = lPress;
|
|
||||||
joystick[Direction::RIGHT].onPress = rPress;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the start of a press to the primary button
|
|
||||||
// Wakes our button thread
|
|
||||||
void TwoButtonExtended::isrPrimary()
|
|
||||||
{
|
|
||||||
static volatile bool isrRunning = false;
|
|
||||||
|
|
||||||
if (!isrRunning) {
|
|
||||||
isrRunning = true;
|
|
||||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
|
||||||
if (b->buttons[0].state == State::REST) {
|
|
||||||
b->buttons[0].state = State::IRQ;
|
|
||||||
b->buttons[0].irqAtMillis = millis();
|
|
||||||
b->startThread();
|
|
||||||
}
|
|
||||||
isrRunning = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the start of a press to the secondary button
|
|
||||||
// Wakes our button thread
|
|
||||||
void TwoButtonExtended::isrSecondary()
|
|
||||||
{
|
|
||||||
static volatile bool isrRunning = false;
|
|
||||||
|
|
||||||
if (!isrRunning) {
|
|
||||||
isrRunning = true;
|
|
||||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
|
||||||
if (b->buttons[1].state == State::REST) {
|
|
||||||
b->buttons[1].state = State::IRQ;
|
|
||||||
b->buttons[1].irqAtMillis = millis();
|
|
||||||
b->startThread();
|
|
||||||
}
|
|
||||||
isrRunning = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle the start of a press to the joystick buttons
|
|
||||||
// Also wakes our button thread
|
|
||||||
void TwoButtonExtended::isrJoystickUp()
|
|
||||||
{
|
|
||||||
static volatile bool isrRunning = false;
|
|
||||||
|
|
||||||
if (!isrRunning) {
|
|
||||||
isrRunning = true;
|
|
||||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
|
||||||
if (b->joystick[Direction::UP].state == State::REST) {
|
|
||||||
b->joystick[Direction::UP].state = State::IRQ;
|
|
||||||
b->joystick[Direction::UP].irqAtMillis = millis();
|
|
||||||
b->startThread();
|
|
||||||
}
|
|
||||||
isrRunning = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TwoButtonExtended::isrJoystickDown()
|
|
||||||
{
|
|
||||||
static volatile bool isrRunning = false;
|
|
||||||
|
|
||||||
if (!isrRunning) {
|
|
||||||
isrRunning = true;
|
|
||||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
|
||||||
if (b->joystick[Direction::DOWN].state == State::REST) {
|
|
||||||
b->joystick[Direction::DOWN].state = State::IRQ;
|
|
||||||
b->joystick[Direction::DOWN].irqAtMillis = millis();
|
|
||||||
b->startThread();
|
|
||||||
}
|
|
||||||
isrRunning = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TwoButtonExtended::isrJoystickLeft()
|
|
||||||
{
|
|
||||||
static volatile bool isrRunning = false;
|
|
||||||
|
|
||||||
if (!isrRunning) {
|
|
||||||
isrRunning = true;
|
|
||||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
|
||||||
if (b->joystick[Direction::LEFT].state == State::REST) {
|
|
||||||
b->joystick[Direction::LEFT].state = State::IRQ;
|
|
||||||
b->joystick[Direction::LEFT].irqAtMillis = millis();
|
|
||||||
b->startThread();
|
|
||||||
}
|
|
||||||
isrRunning = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void TwoButtonExtended::isrJoystickRight()
|
|
||||||
{
|
|
||||||
static volatile bool isrRunning = false;
|
|
||||||
|
|
||||||
if (!isrRunning) {
|
|
||||||
isrRunning = true;
|
|
||||||
TwoButtonExtended *b = TwoButtonExtended::getInstance();
|
|
||||||
if (b->joystick[Direction::RIGHT].state == State::REST) {
|
|
||||||
b->joystick[Direction::RIGHT].state = State::IRQ;
|
|
||||||
b->joystick[Direction::RIGHT].irqAtMillis = millis();
|
|
||||||
b->startThread();
|
|
||||||
}
|
|
||||||
isrRunning = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concise method to start our button thread
|
|
||||||
// Follows an ISR, listening for button release
|
|
||||||
void TwoButtonExtended::startThread()
|
|
||||||
{
|
|
||||||
if (!OSThread::enabled) {
|
|
||||||
OSThread::setInterval(10);
|
|
||||||
OSThread::enabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concise method to stop our button thread
|
|
||||||
// Called when we no longer need to poll for button release
|
|
||||||
void TwoButtonExtended::stopThread()
|
|
||||||
{
|
|
||||||
if (OSThread::enabled) {
|
|
||||||
OSThread::disable();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset both buttons manually
|
|
||||||
// Just in case an IRQ fires during the process of resetting the system
|
|
||||||
// Can occur with super rapid presses?
|
|
||||||
buttons[0].state = REST;
|
|
||||||
buttons[1].state = REST;
|
|
||||||
joystick[Direction::UP].state = REST;
|
|
||||||
joystick[Direction::DOWN].state = REST;
|
|
||||||
joystick[Direction::LEFT].state = REST;
|
|
||||||
joystick[Direction::RIGHT].state = REST;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Our button thread
|
|
||||||
// Started by an IRQ, on either button
|
|
||||||
// Polls for button releases
|
|
||||||
// Stops when both buttons released
|
|
||||||
int32_t TwoButtonExtended::runOnce()
|
|
||||||
{
|
|
||||||
constexpr uint8_t BUTTON_COUNT = sizeof(buttons) / sizeof(Button);
|
|
||||||
constexpr uint8_t JOYSTICK_COUNT = sizeof(joystick) / sizeof(SimpleButton);
|
|
||||||
|
|
||||||
// Allow either button to request that our thread should continue polling
|
|
||||||
bool awaitingRelease = false;
|
|
||||||
|
|
||||||
// Check both primary and secondary buttons
|
|
||||||
for (uint8_t i = 0; i < BUTTON_COUNT; i++) {
|
|
||||||
switch (buttons[i].state) {
|
|
||||||
// No action: button has not been pressed
|
|
||||||
case REST:
|
|
||||||
break;
|
|
||||||
|
|
||||||
// New press detected by interrupt
|
|
||||||
case IRQ:
|
|
||||||
powerFSM.trigger(EVENT_PRESS); // Tell PowerFSM that press occurred (resets sleep timer)
|
|
||||||
buttons[i].onDown(); // Run callback: press has begun (possible hold behavior)
|
|
||||||
buttons[i].state = State::POLLING_UNFIRED; // Mark that button-down has been handled
|
|
||||||
awaitingRelease = true; // Mark that polling-for-release should continue
|
|
||||||
break;
|
|
||||||
|
|
||||||
// An existing press continues
|
|
||||||
// Not held long enough to register as longpress
|
|
||||||
case POLLING_UNFIRED: {
|
|
||||||
uint32_t length = millis() - buttons[i].irqAtMillis;
|
|
||||||
|
|
||||||
// If button released since last thread tick,
|
|
||||||
if (digitalRead(buttons[i].pin) != buttons[i].activeLogic) {
|
|
||||||
buttons[i].onUp(); // Run callback: press has ended (possible release of a hold)
|
|
||||||
buttons[i].state = State::REST; // Mark that the button has reset
|
|
||||||
if (length > buttons[i].debounceLength && length < buttons[i].longpressLength) // If too short for longpress,
|
|
||||||
buttons[i].onPress(); // Run callback: press
|
|
||||||
}
|
|
||||||
// If button not yet released
|
|
||||||
else {
|
|
||||||
awaitingRelease = true; // Mark that polling-for-release should continue
|
|
||||||
if (length >= buttons[i].longpressLength) {
|
|
||||||
// Run callback: long press (once)
|
|
||||||
// Then continue waiting for release, to rearm
|
|
||||||
buttons[i].state = State::POLLING_FIRED;
|
|
||||||
buttons[i].onLongPress();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Button still held, but duration long enough that longpress event already fired
|
|
||||||
// Just waiting for release
|
|
||||||
case POLLING_FIRED:
|
|
||||||
// Release detected
|
|
||||||
if (digitalRead(buttons[i].pin) != buttons[i].activeLogic) {
|
|
||||||
buttons[i].state = State::REST;
|
|
||||||
buttons[i].onUp(); // Callback: release of hold (in this case: *after* longpress has fired)
|
|
||||||
}
|
|
||||||
// Not yet released, keep polling
|
|
||||||
else
|
|
||||||
awaitingRelease = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check all the joystick directions
|
|
||||||
for (uint8_t i = 0; i < JOYSTICK_COUNT; i++) {
|
|
||||||
switch (joystick[i].state) {
|
|
||||||
// No action: button has not been pressed
|
|
||||||
case REST:
|
|
||||||
break;
|
|
||||||
|
|
||||||
// New press detected by interrupt
|
|
||||||
case IRQ:
|
|
||||||
powerFSM.trigger(EVENT_PRESS); // Tell PowerFSM that press occurred (resets sleep timer)
|
|
||||||
joystick[i].onDown(); // Run callback: press has begun (possible hold behavior)
|
|
||||||
joystick[i].state = State::POLLING_UNFIRED; // Mark that button-down has been handled
|
|
||||||
awaitingRelease = true; // Mark that polling-for-release should continue
|
|
||||||
break;
|
|
||||||
|
|
||||||
// An existing press continues
|
|
||||||
// Not held long enough to register as press
|
|
||||||
case POLLING_UNFIRED: {
|
|
||||||
uint32_t length = millis() - joystick[i].irqAtMillis;
|
|
||||||
|
|
||||||
// If button released since last thread tick,
|
|
||||||
if (digitalRead(joystick[i].pin) != joystickActiveLogic) {
|
|
||||||
joystick[i].onUp(); // Run callback: press has ended (possible release of a hold)
|
|
||||||
joystick[i].state = State::REST; // Mark that the button has reset
|
|
||||||
}
|
|
||||||
// If button not yet released
|
|
||||||
else {
|
|
||||||
awaitingRelease = true; // Mark that polling-for-release should continue
|
|
||||||
if (length >= joystickDebounceLength) {
|
|
||||||
// Run callback: long press (once)
|
|
||||||
// Then continue waiting for release, to rearm
|
|
||||||
joystick[i].state = State::POLLING_FIRED;
|
|
||||||
joystick[i].onPress();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Button still held after press
|
|
||||||
// Just waiting for release
|
|
||||||
case POLLING_FIRED:
|
|
||||||
// Release detected
|
|
||||||
if (digitalRead(joystick[i].pin) != joystickActiveLogic) {
|
|
||||||
joystick[i].state = State::REST;
|
|
||||||
joystick[i].onUp(); // Callback: release of hold
|
|
||||||
}
|
|
||||||
// Not yet released, keep polling
|
|
||||||
else
|
|
||||||
awaitingRelease = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all buttons are now released
|
|
||||||
// we don't need to waste cpu resources polling
|
|
||||||
// IRQ will restart this thread when we next need it
|
|
||||||
if (!awaitingRelease)
|
|
||||||
stopThread();
|
|
||||||
|
|
||||||
// Run this method again, or don't..
|
|
||||||
// Use whatever behavior was previously set by stopThread() or startThread()
|
|
||||||
return OSThread::interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
|
|
||||||
// Detach our class' interrupts before lightsleep
|
|
||||||
// Allows sleep.cpp to configure its own interrupts, which wake the device on user-button press
|
|
||||||
int TwoButtonExtended::beforeLightSleep(void *unused)
|
|
||||||
{
|
|
||||||
stop();
|
|
||||||
return 0; // Indicates success
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reconfigure our interrupts
|
|
||||||
// Our class' interrupts were disconnected during sleep, to allow the user button to wake the device from sleep
|
|
||||||
int TwoButtonExtended::afterLightSleep(esp_sleep_wakeup_cause_t cause)
|
|
||||||
{
|
|
||||||
start();
|
|
||||||
|
|
||||||
// Manually trigger the button-down ISR
|
|
||||||
// - during light sleep, our ISR is disabled
|
|
||||||
// - if light sleep ends by button press, pretend our own ISR caught it
|
|
||||||
// - need to manually confirm by reading pin ourselves, to avoid occasional false positives
|
|
||||||
// (false positive only when using internal pullup resistors?)
|
|
||||||
if (cause == ESP_SLEEP_WAKEUP_GPIO && digitalRead(buttons[0].pin) == buttons[0].activeLogic)
|
|
||||||
isrPrimary();
|
|
||||||
|
|
||||||
return 0; // Indicates success
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Re-usable NicheGraphics input source
|
|
||||||
|
|
||||||
Short and Long press for up to two buttons
|
|
||||||
Interrupt driven
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
This expansion adds support for four more buttons
|
|
||||||
These buttons are single-action only, no long press
|
|
||||||
Interrupt driven
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "configuration.h"
|
|
||||||
|
|
||||||
#include "assert.h"
|
|
||||||
#include "functional"
|
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
#include "esp_sleep.h" // For light-sleep handling
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "Observer.h"
|
|
||||||
|
|
||||||
namespace NicheGraphics::Inputs
|
|
||||||
{
|
|
||||||
|
|
||||||
class TwoButtonExtended : protected concurrency::OSThread
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
typedef std::function<void()> Callback;
|
|
||||||
|
|
||||||
static uint8_t getUserButtonPin(); // Resolve the GPIO, considering the various possible source of definition
|
|
||||||
|
|
||||||
static TwoButtonExtended *getInstance(); // Create or get the singleton instance
|
|
||||||
void start(); // Start handling button input
|
|
||||||
void stop(); // Stop handling button input (disconnect ISRs for sleep)
|
|
||||||
void setWiring(uint8_t whichButton, uint8_t pin, bool internalPullup = false);
|
|
||||||
void setJoystickWiring(uint8_t uPin, uint8_t dPin, uint8_t lPin, uint8_t rPin, bool internalPullup = false);
|
|
||||||
void setTiming(uint8_t whichButton, uint32_t debounceMs, uint32_t longpressMs);
|
|
||||||
void setJoystickDebounce(uint32_t debounceMs);
|
|
||||||
void setHandlerDown(uint8_t whichButton, Callback onDown);
|
|
||||||
void setHandlerUp(uint8_t whichButton, Callback onUp);
|
|
||||||
void setHandlerShortPress(uint8_t whichButton, Callback onShortPress);
|
|
||||||
void setHandlerLongPress(uint8_t whichButton, Callback onLongPress);
|
|
||||||
void setJoystickDownHandlers(Callback uDown, Callback dDown, Callback ldown, Callback rDown);
|
|
||||||
void setJoystickUpHandlers(Callback uUp, Callback dUp, Callback lUp, Callback rUp);
|
|
||||||
void setJoystickPressHandlers(Callback uPress, Callback dPress, Callback lPress, Callback rPress);
|
|
||||||
|
|
||||||
// Disconnect and reconnect interrupts for light sleep
|
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
int beforeLightSleep(void *unused);
|
|
||||||
int afterLightSleep(esp_sleep_wakeup_cause_t cause);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Internal state of a specific button
|
|
||||||
enum State {
|
|
||||||
REST, // Up, no activity
|
|
||||||
IRQ, // Down detected, not yet handled
|
|
||||||
POLLING_UNFIRED, // Down handled, polling for release
|
|
||||||
POLLING_FIRED, // Longpress fired, button still held
|
|
||||||
};
|
|
||||||
|
|
||||||
// Joystick Directions
|
|
||||||
enum Direction { UP = 0, DOWN, LEFT, RIGHT };
|
|
||||||
|
|
||||||
// Data used for direction (single-action) buttons
|
|
||||||
class SimpleButton
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
// Per-button config
|
|
||||||
uint8_t pin = 0xFF; // 0xFF: unset
|
|
||||||
volatile State state = State::REST; // Internal state
|
|
||||||
volatile uint32_t irqAtMillis; // millis() when button went down
|
|
||||||
|
|
||||||
// Per-button event callbacks
|
|
||||||
static void noop(){};
|
|
||||||
std::function<void()> onDown = noop;
|
|
||||||
std::function<void()> onUp = noop;
|
|
||||||
std::function<void()> onPress = noop;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Data used for double-action buttons
|
|
||||||
class Button : public SimpleButton
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
// Per-button extended config
|
|
||||||
bool activeLogic = LOW; // Active LOW by default.
|
|
||||||
uint32_t debounceLength = 50; // Minimum length for shortpress in ms
|
|
||||||
uint32_t longpressLength = 500; // Time until longpress in ms
|
|
||||||
|
|
||||||
// Per-button event callbacks
|
|
||||||
std::function<void()> onLongPress = noop;
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
// Get notified when lightsleep begins and ends
|
|
||||||
CallbackObserver<TwoButtonExtended, void *> lsObserver =
|
|
||||||
CallbackObserver<TwoButtonExtended, void *>(this, &TwoButtonExtended::beforeLightSleep);
|
|
||||||
CallbackObserver<TwoButtonExtended, esp_sleep_wakeup_cause_t> lsEndObserver =
|
|
||||||
CallbackObserver<TwoButtonExtended, esp_sleep_wakeup_cause_t>(this, &TwoButtonExtended::afterLightSleep);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int32_t runOnce() override; // Timer method. Polls for button release
|
|
||||||
|
|
||||||
void startThread(); // Start polling for release
|
|
||||||
void stopThread(); // Stop polling for release
|
|
||||||
|
|
||||||
static void isrPrimary(); // User Button ISR
|
|
||||||
static void isrSecondary(); // optional aux button or joystick center
|
|
||||||
static void isrJoystickUp();
|
|
||||||
static void isrJoystickDown();
|
|
||||||
static void isrJoystickLeft();
|
|
||||||
static void isrJoystickRight();
|
|
||||||
|
|
||||||
TwoButtonExtended(); // Constructor made private: force use of Button::instance()
|
|
||||||
|
|
||||||
// Info about both buttons
|
|
||||||
Button buttons[2];
|
|
||||||
bool joystickActiveLogic = LOW; // Active LOW by default
|
|
||||||
uint32_t joystickDebounceLength = 50; // time until press in ms
|
|
||||||
SimpleButton joystick[4];
|
|
||||||
};
|
|
||||||
|
|
||||||
}; // namespace NicheGraphics::Inputs
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -53,9 +53,9 @@ void CannedMessageStore::load()
|
|||||||
|
|
||||||
// Attempt to load the bulk canned message data from flash
|
// Attempt to load the bulk canned message data from flash
|
||||||
meshtastic_CannedMessageModuleConfig cannedMessageModuleConfig;
|
meshtastic_CannedMessageModuleConfig cannedMessageModuleConfig;
|
||||||
LoadFileResult result = nodeDB->loadProto("/prefs/cannedConf.proto", meshtastic_CannedMessageModuleConfig_size,
|
LoadFileResult result = loadProto("/prefs/cannedConf.proto", meshtastic_CannedMessageModuleConfig_size,
|
||||||
sizeof(meshtastic_CannedMessageModuleConfig),
|
sizeof(meshtastic_CannedMessageModuleConfig), &meshtastic_CannedMessageModuleConfig_msg,
|
||||||
&meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig);
|
&cannedMessageModuleConfig);
|
||||||
|
|
||||||
// Abort if nothing to load
|
// Abort if nothing to load
|
||||||
if (result != LoadFileResult::LOAD_SUCCESS || strlen(cannedMessageModuleConfig.messages) == 0)
|
if (result != LoadFileResult::LOAD_SUCCESS || strlen(cannedMessageModuleConfig.messages) == 0)
|
||||||
@@ -129,8 +129,8 @@ void CannedMessageStore::handleSet(const meshtastic_AdminMessage *request)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Write to flash
|
// Write to flash
|
||||||
nodeDB->saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size,
|
saveProto(cannedMessagesConfigFile, meshtastic_CannedMessageModuleConfig_size, &meshtastic_CannedMessageModuleConfig_msg,
|
||||||
&meshtastic_CannedMessageModuleConfig_msg, &cannedMessageModuleConfig);
|
&cannedMessageModuleConfig);
|
||||||
|
|
||||||
// Reload from flash, to update the canned messages in RAM
|
// Reload from flash, to update the canned messages in RAM
|
||||||
// (This is a lazy way to handle it)
|
// (This is a lazy way to handle it)
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ typedef struct _InputEvent {
|
|||||||
class InputPollable
|
class InputPollable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual ~InputPollable() = default;
|
|
||||||
virtual void pollOnce() = 0;
|
virtual void pollOnce() = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,6 @@
|
|||||||
#include "RotaryEncoderImpl.h"
|
#include "RotaryEncoderImpl.h"
|
||||||
#include "InputBroker.h"
|
#include "InputBroker.h"
|
||||||
#include "RotaryEncoder.h"
|
#include "RotaryEncoder.h"
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
#include "sleep.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define ORIGIN_NAME "RotaryEncoder"
|
#define ORIGIN_NAME "RotaryEncoder"
|
||||||
|
|
||||||
@@ -14,20 +11,6 @@ RotaryEncoderImpl *rotaryEncoderImpl;
|
|||||||
RotaryEncoderImpl::RotaryEncoderImpl()
|
RotaryEncoderImpl::RotaryEncoderImpl()
|
||||||
{
|
{
|
||||||
rotary = nullptr;
|
rotary = nullptr;
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
isFirstInit = true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
RotaryEncoderImpl::~RotaryEncoderImpl()
|
|
||||||
{
|
|
||||||
LOG_DEBUG("RotaryEncoderImpl destructor");
|
|
||||||
detachRotaryEncoderInterrupts();
|
|
||||||
|
|
||||||
if (rotary != nullptr) {
|
|
||||||
delete rotary;
|
|
||||||
rotary = nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RotaryEncoderImpl::init()
|
bool RotaryEncoderImpl::init()
|
||||||
@@ -42,22 +25,15 @@ bool RotaryEncoderImpl::init()
|
|||||||
eventCcw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_ccw);
|
eventCcw = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_ccw);
|
||||||
eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press);
|
eventPressed = static_cast<input_broker_event>(moduleConfig.canned_message.inputbroker_event_press);
|
||||||
|
|
||||||
if (rotary == nullptr) {
|
rotary = new RotaryEncoder(moduleConfig.canned_message.inputbroker_pin_a, moduleConfig.canned_message.inputbroker_pin_b,
|
||||||
rotary = new RotaryEncoder(moduleConfig.canned_message.inputbroker_pin_a, moduleConfig.canned_message.inputbroker_pin_b,
|
moduleConfig.canned_message.inputbroker_pin_press);
|
||||||
moduleConfig.canned_message.inputbroker_pin_press);
|
rotary->resetButton();
|
||||||
}
|
|
||||||
|
|
||||||
attachRotaryEncoderInterrupts();
|
interruptInstance = this;
|
||||||
|
auto interruptHandler = []() { inputBroker->requestPollSoon(interruptInstance); };
|
||||||
#ifdef ARCH_ESP32
|
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_a, interruptHandler, CHANGE);
|
||||||
// Register callbacks for before and after lightsleep
|
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_b, interruptHandler, CHANGE);
|
||||||
// Used to detach and reattach interrupts
|
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_press, interruptHandler, CHANGE);
|
||||||
if (isFirstInit) {
|
|
||||||
lsObserver.observe(¬ifyLightSleep);
|
|
||||||
lsEndObserver.observe(¬ifyLightSleepEnd);
|
|
||||||
isFirstInit = false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a,
|
LOG_INFO("RotaryEncoder initialized pins(%d, %d, %d), events(%d, %d, %d)", moduleConfig.canned_message.inputbroker_pin_a,
|
||||||
moduleConfig.canned_message.inputbroker_pin_b, moduleConfig.canned_message.inputbroker_pin_press, eventCw, eventCcw,
|
moduleConfig.canned_message.inputbroker_pin_b, moduleConfig.canned_message.inputbroker_pin_press, eventCw, eventCcw,
|
||||||
@@ -95,50 +71,6 @@ void RotaryEncoderImpl::pollOnce()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RotaryEncoderImpl::detachRotaryEncoderInterrupts()
|
|
||||||
{
|
|
||||||
LOG_DEBUG("RotaryEncoderImpl detach button interrupts");
|
|
||||||
if (interruptInstance == this) {
|
|
||||||
detachInterrupt(moduleConfig.canned_message.inputbroker_pin_a);
|
|
||||||
detachInterrupt(moduleConfig.canned_message.inputbroker_pin_b);
|
|
||||||
detachInterrupt(moduleConfig.canned_message.inputbroker_pin_press);
|
|
||||||
interruptInstance = nullptr;
|
|
||||||
} else {
|
|
||||||
LOG_WARN("RotaryEncoderImpl: interrupts already detached");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RotaryEncoderImpl::attachRotaryEncoderInterrupts()
|
|
||||||
{
|
|
||||||
LOG_DEBUG("RotaryEncoderImpl attach button interrupts");
|
|
||||||
if (rotary != nullptr && interruptInstance == nullptr) {
|
|
||||||
rotary->resetButton();
|
|
||||||
|
|
||||||
interruptInstance = this;
|
|
||||||
auto interruptHandler = []() { inputBroker->requestPollSoon(interruptInstance); };
|
|
||||||
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_a, interruptHandler, CHANGE);
|
|
||||||
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_b, interruptHandler, CHANGE);
|
|
||||||
attachInterrupt(moduleConfig.canned_message.inputbroker_pin_press, interruptHandler, CHANGE);
|
|
||||||
} else {
|
|
||||||
LOG_WARN("RotaryEncoderImpl: interrupts already attached");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
|
|
||||||
int RotaryEncoderImpl::beforeLightSleep(void *unused)
|
|
||||||
{
|
|
||||||
detachRotaryEncoderInterrupts();
|
|
||||||
return 0; // Indicates success;
|
|
||||||
}
|
|
||||||
|
|
||||||
int RotaryEncoderImpl::afterLightSleep(esp_sleep_wakeup_cause_t cause)
|
|
||||||
{
|
|
||||||
attachRotaryEncoderInterrupts();
|
|
||||||
return 0; // Indicates success;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RotaryEncoderImpl *RotaryEncoderImpl::interruptInstance;
|
RotaryEncoderImpl *RotaryEncoderImpl::interruptInstance;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -8,18 +8,12 @@
|
|||||||
|
|
||||||
class RotaryEncoder;
|
class RotaryEncoder;
|
||||||
|
|
||||||
class RotaryEncoderImpl final : public InputPollable
|
class RotaryEncoderImpl : public InputPollable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RotaryEncoderImpl();
|
RotaryEncoderImpl();
|
||||||
~RotaryEncoderImpl() override;
|
bool init(void);
|
||||||
bool init();
|
|
||||||
virtual void pollOnce() override;
|
virtual void pollOnce() override;
|
||||||
// Disconnect and reconnect interrupts for light sleep
|
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
int beforeLightSleep(void *unused);
|
|
||||||
int afterLightSleep(esp_sleep_wakeup_cause_t cause);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static RotaryEncoderImpl *interruptInstance;
|
static RotaryEncoderImpl *interruptInstance;
|
||||||
@@ -29,21 +23,6 @@ class RotaryEncoderImpl final : public InputPollable
|
|||||||
input_broker_event eventPressed = INPUT_BROKER_NONE;
|
input_broker_event eventPressed = INPUT_BROKER_NONE;
|
||||||
|
|
||||||
RotaryEncoder *rotary;
|
RotaryEncoder *rotary;
|
||||||
|
|
||||||
private:
|
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
bool isFirstInit;
|
|
||||||
#endif
|
|
||||||
void detachRotaryEncoderInterrupts();
|
|
||||||
void attachRotaryEncoderInterrupts();
|
|
||||||
|
|
||||||
#ifdef ARCH_ESP32
|
|
||||||
// Get notified when lightsleep begins and ends
|
|
||||||
CallbackObserver<RotaryEncoderImpl, void *> lsObserver =
|
|
||||||
CallbackObserver<RotaryEncoderImpl, void *>(this, &RotaryEncoderImpl::beforeLightSleep);
|
|
||||||
CallbackObserver<RotaryEncoderImpl, esp_sleep_wakeup_cause_t> lsEndObserver =
|
|
||||||
CallbackObserver<RotaryEncoderImpl, esp_sleep_wakeup_cause_t>(this, &RotaryEncoderImpl::afterLightSleep);
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern RotaryEncoderImpl *rotaryEncoderImpl;
|
extern RotaryEncoderImpl *rotaryEncoderImpl;
|
||||||
|
|||||||
@@ -489,6 +489,8 @@ int32_t KbI2cBase::runOnce()
|
|||||||
case 0x90: // fn+r INPUT_BROKER_MSG_REBOOT
|
case 0x90: // fn+r INPUT_BROKER_MSG_REBOOT
|
||||||
case 0x91: // fn+t
|
case 0x91: // fn+t
|
||||||
case 0xac: // fn+m INPUT_BROKER_MSG_MUTE_TOGGLE
|
case 0xac: // fn+m INPUT_BROKER_MSG_MUTE_TOGGLE
|
||||||
|
|
||||||
|
case 0x8b: // fn+del INPUT_BROKEN_MSG_DISMISS_FRAME
|
||||||
case 0xAA: // fn+b INPUT_BROKER_MSG_BLUETOOTH_TOGGLE
|
case 0xAA: // fn+b INPUT_BROKER_MSG_BLUETOOTH_TOGGLE
|
||||||
case 0x8F: // fn+e INPUT_BROKER_MSG_EMOTE_LIST
|
case 0x8F: // fn+e INPUT_BROKER_MSG_EMOTE_LIST
|
||||||
// just pass those unmodified
|
// just pass those unmodified
|
||||||
|
|||||||
11
src/main.cpp
11
src/main.cpp
@@ -428,17 +428,10 @@ void setup()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
RTCQuality ourQuality = RTCQualityDevice;
|
|
||||||
|
|
||||||
std::string timeCommandResult = exec("timedatectl status | grep synchronized | grep yes -c");
|
|
||||||
if (timeCommandResult[0] == '1') {
|
|
||||||
ourQuality = RTCQualityNTP;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
tv.tv_sec = time(NULL);
|
tv.tv_sec = time(NULL);
|
||||||
tv.tv_usec = 0;
|
tv.tv_usec = 0;
|
||||||
perhapsSetRTC(ourQuality, &tv);
|
perhapsSetRTC(RTCQualityDevice, &tv);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
powerMonInit();
|
powerMonInit();
|
||||||
@@ -447,11 +440,9 @@ void setup()
|
|||||||
LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n");
|
LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n");
|
||||||
|
|
||||||
#if defined(ARCH_ESP32) && defined(BOARD_HAS_PSRAM)
|
#if defined(ARCH_ESP32) && defined(BOARD_HAS_PSRAM)
|
||||||
#ifndef SENSECAP_INDICATOR
|
|
||||||
// use PSRAM for malloc calls > 256 bytes
|
// use PSRAM for malloc calls > 256 bytes
|
||||||
heap_caps_malloc_extmem_enable(256);
|
heap_caps_malloc_extmem_enable(256);
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(DEBUG_MUTE) && defined(DEBUG_PORT)
|
#if defined(DEBUG_MUTE) && defined(DEBUG_PORT)
|
||||||
DEBUG_PORT.printf("\r\n\r\n//\\ E S H T /\\ S T / C\r\n");
|
DEBUG_PORT.printf("\r\n\r\n//\\ E S H T /\\ S T / C\r\n");
|
||||||
|
|||||||
@@ -96,8 +96,6 @@ class Channels
|
|||||||
|
|
||||||
bool setDefaultPresetCryptoForHash(ChannelHash channelHash);
|
bool setDefaultPresetCryptoForHash(ChannelHash channelHash);
|
||||||
|
|
||||||
int16_t getHash(ChannelIndex i) { return hashes[i]; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/** Given a channel index, change to use the crypto key specified by that index
|
/** Given a channel index, change to use the crypto key specified by that index
|
||||||
*
|
*
|
||||||
@@ -115,6 +113,8 @@ class Channels
|
|||||||
*/
|
*/
|
||||||
int16_t generateHash(ChannelIndex channelNum);
|
int16_t generateHash(ChannelIndex channelNum);
|
||||||
|
|
||||||
|
int16_t getHash(ChannelIndex i) { return hashes[i]; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate a channel, fixing any errors as needed
|
* Validate a channel, fixing any errors as needed
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -124,10 +124,6 @@ void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
|
|||||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE && iface) {
|
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE && iface) {
|
||||||
iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
|
iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
|
||||||
}
|
}
|
||||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE && iface && nodeDB &&
|
|
||||||
nodeDB->isFromOrToFavoritedNode(*p)) {
|
|
||||||
iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FloodingRouter::isRebroadcaster()
|
bool FloodingRouter::isRebroadcaster()
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ struct UIFrameEvent {
|
|||||||
REDRAW_ONLY, // Don't change which frames are show, just redraw, asap
|
REDRAW_ONLY, // Don't change which frames are show, just redraw, asap
|
||||||
REGENERATE_FRAMESET, // Regenerate (change? add? remove?) screen frames, honoring requestFocus()
|
REGENERATE_FRAMESET, // Regenerate (change? add? remove?) screen frames, honoring requestFocus()
|
||||||
REGENERATE_FRAMESET_BACKGROUND, // Regenerate screen frames, Attempt to remain on the same frame throughout
|
REGENERATE_FRAMESET_BACKGROUND, // Regenerate screen frames, Attempt to remain on the same frame throughout
|
||||||
SWITCH_TO_TEXTMESSAGE // Jump directly to the Text Message screen
|
|
||||||
} action = REDRAW_ONLY;
|
} action = REDRAW_ONLY;
|
||||||
|
|
||||||
// We might want to pass additional data inside this struct at some point
|
// We might want to pass additional data inside this struct at some point
|
||||||
@@ -226,4 +225,4 @@ class MeshModule
|
|||||||
/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet
|
/** set the destination and packet parameters of packet p intended as a reply to a particular "to" packet
|
||||||
* This ensures that if the request packet was sent reliably, the reply is sent that way as well.
|
* This ensures that if the request packet was sent reliably, the reply is sent that way as well.
|
||||||
*/
|
*/
|
||||||
void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to);
|
void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to);
|
||||||
@@ -7,12 +7,10 @@
|
|||||||
#include "../concurrency/Periodic.h"
|
#include "../concurrency/Periodic.h"
|
||||||
#include "BluetoothCommon.h" // needed for updateBatteryLevel, FIXME, eventually when we pull mesh out into a lib we shouldn't be whacking bluetooth from here
|
#include "BluetoothCommon.h" // needed for updateBatteryLevel, FIXME, eventually when we pull mesh out into a lib we shouldn't be whacking bluetooth from here
|
||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
#include "MessageStore.h"
|
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
#include "RTC.h"
|
#include "RTC.h"
|
||||||
#include "TypeConversions.h"
|
#include "TypeConversions.h"
|
||||||
#include "graphics/draw/MessageRenderer.h"
|
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
#include "meshUtils.h"
|
#include "meshUtils.h"
|
||||||
@@ -194,16 +192,8 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
|
|||||||
p.id = generatePacketId(); // If the phone didn't supply one, then pick one
|
p.id = generatePacketId(); // If the phone didn't supply one, then pick one
|
||||||
|
|
||||||
p.rx_time = getValidTime(RTCQualityFromNet); // Record the time the packet arrived from the phone
|
p.rx_time = getValidTime(RTCQualityFromNet); // Record the time the packet arrived from the phone
|
||||||
|
// (so we update our nodedb for the local node)
|
||||||
|
|
||||||
#if HAS_SCREEN
|
|
||||||
if (p.decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP && p.decoded.payload.size > 0 && p.to != NODENUM_BROADCAST &&
|
|
||||||
p.to != 0) // DM only
|
|
||||||
{
|
|
||||||
perhapsDecode(&p);
|
|
||||||
const StoredMessage &sm = messageStore.addFromPacket(p);
|
|
||||||
graphics::MessageRenderer::handleNewMessage(nullptr, sm, p); // notify UI
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// Send the packet into the mesh
|
// Send the packet into the mesh
|
||||||
DEBUG_HEAP_BEFORE;
|
DEBUG_HEAP_BEFORE;
|
||||||
auto a = packetPool.allocCopy(p);
|
auto a = packetPool.allocCopy(p);
|
||||||
@@ -286,10 +276,6 @@ bool MeshService::trySendPosition(NodeNum dest, bool wantReplies)
|
|||||||
if (nodeDB->hasValidPosition(node)) {
|
if (nodeDB->hasValidPosition(node)) {
|
||||||
#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS
|
#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS
|
||||||
if (positionModule) {
|
if (positionModule) {
|
||||||
if (!config.position.fixed_position && !nodeDB->hasLocalPositionSinceBoot()) {
|
|
||||||
LOG_DEBUG("Skip position ping; no fresh position since boot");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
LOG_INFO("Send position ping to 0x%x, wantReplies=%d, channel=%d", dest, wantReplies, node->channel);
|
LOG_INFO("Send position ping to 0x%x, wantReplies=%d, channel=%d", dest, wantReplies, node->channel);
|
||||||
positionModule->sendOurPosition(dest, wantReplies, node->channel);
|
positionModule->sendOurPosition(dest, wantReplies, node->channel);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
#include "RTC.h"
|
#include "RTC.h"
|
||||||
#include "Router.h"
|
#include "Router.h"
|
||||||
#include "SPILock.h"
|
#include "SPILock.h"
|
||||||
#include "SafeFile.h"
|
|
||||||
#include "TypeConversions.h"
|
#include "TypeConversions.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
@@ -314,7 +313,7 @@ NodeDB::NodeDB()
|
|||||||
LOG_DEBUG("Number of Device Reboots: %d", myNodeInfo.reboot_count);
|
LOG_DEBUG("Number of Device Reboots: %d", myNodeInfo.reboot_count);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
resetRadioConfig(); // If bogus settings got saved, then fix them
|
_resetRadioConfig(); // If bogus settings got saved, then fix them
|
||||||
// nodeDB->LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d", config.lora.region, myNodeInfo.my_node_num, numMeshNodes);
|
// nodeDB->LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d", config.lora.region, myNodeInfo.my_node_num, numMeshNodes);
|
||||||
|
|
||||||
// Uncomment below to always enable UDP broadcasts
|
// Uncomment below to always enable UDP broadcasts
|
||||||
@@ -425,13 +424,13 @@ NodeDB::NodeDB()
|
|||||||
config.has_position = true;
|
config.has_position = true;
|
||||||
info->has_position = true;
|
info->has_position = true;
|
||||||
info->position = TypeConversions::ConvertToPositionLite(fixedGPS);
|
info->position = TypeConversions::ConvertToPositionLite(fixedGPS);
|
||||||
nodeDB->setLocalPosition(fixedGPS);
|
nodeDB->_setLocalPosition(fixedGPS);
|
||||||
config.position.fixed_position = true;
|
config.position.fixed_position = true;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
sortMeshDB();
|
sortMeshDB();
|
||||||
saveToDisk(saveWhat);
|
_saveToDisk(saveWhat);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -460,7 +459,7 @@ bool isBroadcast(uint32_t dest)
|
|||||||
return dest == NODENUM_BROADCAST || dest == NODENUM_BROADCAST_NO_LORA;
|
return dest == NODENUM_BROADCAST || dest == NODENUM_BROADCAST_NO_LORA;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDB::resetRadioConfig(bool is_fresh_install)
|
void NodeDB::_resetRadioConfig(bool is_fresh_install)
|
||||||
{
|
{
|
||||||
if (is_fresh_install) {
|
if (is_fresh_install) {
|
||||||
radioGeneration++;
|
radioGeneration++;
|
||||||
@@ -480,6 +479,7 @@ void NodeDB::resetRadioConfig(bool is_fresh_install)
|
|||||||
|
|
||||||
bool NodeDB::factoryReset(bool eraseBleBonds)
|
bool NodeDB::factoryReset(bool eraseBleBonds)
|
||||||
{
|
{
|
||||||
|
FUNCTION_START("factoryReset");
|
||||||
LOG_INFO("Perform factory reset!");
|
LOG_INFO("Perform factory reset!");
|
||||||
// first, remove the "/prefs" (this removes most prefs)
|
// first, remove the "/prefs" (this removes most prefs)
|
||||||
spiLock->lock();
|
spiLock->lock();
|
||||||
@@ -498,7 +498,7 @@ bool NodeDB::factoryReset(bool eraseBleBonds)
|
|||||||
installDefaultModuleConfig();
|
installDefaultModuleConfig();
|
||||||
installDefaultChannels();
|
installDefaultChannels();
|
||||||
// third, write everything to disk
|
// third, write everything to disk
|
||||||
saveToDisk();
|
_saveToDisk();
|
||||||
if (eraseBleBonds) {
|
if (eraseBleBonds) {
|
||||||
LOG_INFO("Erase BLE bonds");
|
LOG_INFO("Erase BLE bonds");
|
||||||
#ifdef ARCH_ESP32
|
#ifdef ARCH_ESP32
|
||||||
@@ -513,6 +513,7 @@ bool NodeDB::factoryReset(bool eraseBleBonds)
|
|||||||
Bluefruit.Central.clearBonds();
|
Bluefruit.Central.clearBonds();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
FUNCTION_END;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -649,7 +650,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
|||||||
config.device.node_info_broadcast_secs = default_node_info_broadcast_secs;
|
config.device.node_info_broadcast_secs = default_node_info_broadcast_secs;
|
||||||
config.security.serial_enabled = true;
|
config.security.serial_enabled = true;
|
||||||
config.security.admin_channel_enabled = false;
|
config.security.admin_channel_enabled = false;
|
||||||
resetRadioConfig(true); // This also triggers NodeInfo/Position requests since we're fresh
|
_resetRadioConfig(true); // This also triggers NodeInfo/Position requests since we're fresh
|
||||||
strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
|
strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
|
||||||
|
|
||||||
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \
|
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \
|
||||||
@@ -746,7 +747,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
|||||||
|
|
||||||
#ifdef USERPREFS_CONFIG_DEVICE_ROLE
|
#ifdef USERPREFS_CONFIG_DEVICE_ROLE
|
||||||
// Apply role-specific defaults when role is set via user preferences
|
// Apply role-specific defaults when role is set via user preferences
|
||||||
installRoleDefaults(config.device.role);
|
_installRoleDefaults(config.device.role);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
initConfigIntervals();
|
initConfigIntervals();
|
||||||
@@ -805,15 +806,11 @@ void NodeDB::installDefaultModuleConfig()
|
|||||||
moduleConfig.external_notification.output_ms = 500;
|
moduleConfig.external_notification.output_ms = 500;
|
||||||
moduleConfig.external_notification.nag_timeout = 2;
|
moduleConfig.external_notification.nag_timeout = 2;
|
||||||
#endif
|
#endif
|
||||||
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3)
|
#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312)
|
||||||
// Default to PIN_LED2 for external notification output (LED color depends on device variant)
|
// Default to RAK led pin 2 (blue)
|
||||||
moduleConfig.external_notification.enabled = true;
|
moduleConfig.external_notification.enabled = true;
|
||||||
moduleConfig.external_notification.output = PIN_LED2;
|
moduleConfig.external_notification.output = PIN_LED2;
|
||||||
#if defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3)
|
|
||||||
moduleConfig.external_notification.active = false;
|
|
||||||
#else
|
|
||||||
moduleConfig.external_notification.active = true;
|
moduleConfig.external_notification.active = true;
|
||||||
#endif
|
|
||||||
moduleConfig.external_notification.alert_message = true;
|
moduleConfig.external_notification.alert_message = true;
|
||||||
moduleConfig.external_notification.output_ms = 1000;
|
moduleConfig.external_notification.output_ms = 1000;
|
||||||
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
|
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
|
||||||
@@ -908,7 +905,7 @@ void NodeDB::installDefaultModuleConfig()
|
|||||||
initModuleConfigIntervals();
|
initModuleConfigIntervals();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDB::installRoleDefaults(meshtastic_Config_DeviceConfig_Role role)
|
void NodeDB::_installRoleDefaults(meshtastic_Config_DeviceConfig_Role role)
|
||||||
{
|
{
|
||||||
if (role == meshtastic_Config_DeviceConfig_Role_ROUTER) {
|
if (role == meshtastic_Config_DeviceConfig_Role_ROUTER) {
|
||||||
initConfigIntervals();
|
initConfigIntervals();
|
||||||
@@ -994,6 +991,7 @@ void NodeDB::installDefaultChannels()
|
|||||||
|
|
||||||
void NodeDB::resetNodes(bool keepFavorites)
|
void NodeDB::resetNodes(bool keepFavorites)
|
||||||
{
|
{
|
||||||
|
FUNCTION_START("resetNodes");
|
||||||
if (!config.position.fixed_position)
|
if (!config.position.fixed_position)
|
||||||
clearLocalPosition();
|
clearLocalPosition();
|
||||||
numMeshNodes = 1;
|
numMeshNodes = 1;
|
||||||
@@ -1017,10 +1015,12 @@ void NodeDB::resetNodes(bool keepFavorites)
|
|||||||
saveDeviceStateToDisk();
|
saveDeviceStateToDisk();
|
||||||
if (neighborInfoModule && moduleConfig.neighbor_info.enabled)
|
if (neighborInfoModule && moduleConfig.neighbor_info.enabled)
|
||||||
neighborInfoModule->resetNeighbors();
|
neighborInfoModule->resetNeighbors();
|
||||||
|
FUNCTION_END;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDB::removeNodeByNum(NodeNum nodeNum)
|
void NodeDB::removeNodeByNum(NodeNum nodeNum)
|
||||||
{
|
{
|
||||||
|
FUNCTION_START("removeNodeByNum");
|
||||||
int newPos = 0, removed = 0;
|
int newPos = 0, removed = 0;
|
||||||
for (int i = 0; i < numMeshNodes; i++) {
|
for (int i = 0; i < numMeshNodes; i++) {
|
||||||
if (meshNodes->at(i).num != nodeNum)
|
if (meshNodes->at(i).num != nodeNum)
|
||||||
@@ -1033,17 +1033,17 @@ void NodeDB::removeNodeByNum(NodeNum nodeNum)
|
|||||||
meshtastic_NodeInfoLite());
|
meshtastic_NodeInfoLite());
|
||||||
LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Save changes", removed);
|
LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Save changes", removed);
|
||||||
saveNodeDatabaseToDisk();
|
saveNodeDatabaseToDisk();
|
||||||
|
FUNCTION_END;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDB::clearLocalPosition()
|
void NodeDB::_clearLocalPosition()
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *node = getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeInfoLite *node = _getMeshNode(nodeDB->getNodeNum());
|
||||||
node->position.latitude_i = 0;
|
node->position.latitude_i = 0;
|
||||||
node->position.longitude_i = 0;
|
node->position.longitude_i = 0;
|
||||||
node->position.altitude = 0;
|
node->position.altitude = 0;
|
||||||
node->position.time = 0;
|
node->position.time = 0;
|
||||||
setLocalPosition(meshtastic_Position_init_default);
|
_setLocalPosition(meshtastic_Position_init_default);
|
||||||
localPositionUpdatedSinceBoot = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDB::cleanupMeshDB()
|
void NodeDB::cleanupMeshDB()
|
||||||
@@ -1119,7 +1119,7 @@ void NodeDB::pickNewNodeNum()
|
|||||||
}
|
}
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *found;
|
meshtastic_NodeInfoLite *found;
|
||||||
while (((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0) ||
|
while (((found = _getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0) ||
|
||||||
(nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED)) {
|
(nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED)) {
|
||||||
NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice
|
NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice
|
||||||
if (found)
|
if (found)
|
||||||
@@ -1133,39 +1133,6 @@ void NodeDB::pickNewNodeNum()
|
|||||||
myNodeInfo.my_node_num = nodeNum;
|
myNodeInfo.my_node_num = nodeNum;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Load a protobuf from a file, return LoadFileResult */
|
|
||||||
LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields,
|
|
||||||
void *dest_struct)
|
|
||||||
{
|
|
||||||
LoadFileResult state = LoadFileResult::OTHER_FAILURE;
|
|
||||||
#ifdef FSCom
|
|
||||||
concurrency::LockGuard g(spiLock);
|
|
||||||
|
|
||||||
auto f = FSCom.open(filename, FILE_O_READ);
|
|
||||||
|
|
||||||
if (f) {
|
|
||||||
LOG_INFO("Load %s", filename);
|
|
||||||
pb_istream_t stream = {&readcb, &f, protoSize};
|
|
||||||
if (fields != &meshtastic_NodeDatabase_msg) // contains a vector object
|
|
||||||
memset(dest_struct, 0, objSize);
|
|
||||||
if (!pb_decode(&stream, fields, dest_struct)) {
|
|
||||||
LOG_ERROR("Error: can't decode protobuf %s", PB_GET_ERROR(&stream));
|
|
||||||
state = LoadFileResult::DECODE_FAILED;
|
|
||||||
} else {
|
|
||||||
LOG_INFO("Loaded %s successfully", filename);
|
|
||||||
state = LoadFileResult::LOAD_SUCCESS;
|
|
||||||
}
|
|
||||||
f.close();
|
|
||||||
} else {
|
|
||||||
LOG_ERROR("Could not open / read %s", filename);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
LOG_ERROR("ERROR: Filesystem not implemented");
|
|
||||||
state = LoadFileResult::NO_FILESYSTEM;
|
|
||||||
#endif
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeDB::loadFromDisk()
|
void NodeDB::loadFromDisk()
|
||||||
{
|
{
|
||||||
// Mark the current device state as completely unusable, so that if we fail reading the entire file from
|
// Mark the current device state as completely unusable, so that if we fail reading the entire file from
|
||||||
@@ -1265,7 +1232,7 @@ void NodeDB::loadFromDisk()
|
|||||||
if (backupSecurity.private_key.size > 0) {
|
if (backupSecurity.private_key.size > 0) {
|
||||||
LOG_DEBUG("Restoring backup of security config");
|
LOG_DEBUG("Restoring backup of security config");
|
||||||
config.security = backupSecurity;
|
config.security = backupSecurity;
|
||||||
saveToDisk(SEGMENT_CONFIG);
|
_saveToDisk(SEGMENT_CONFIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure we load hard coded admin keys even when the configuration file has none.
|
// Make sure we load hard coded admin keys even when the configuration file has none.
|
||||||
@@ -1316,7 +1283,7 @@ void NodeDB::loadFromDisk()
|
|||||||
if (numAdminKeys > 0) {
|
if (numAdminKeys > 0) {
|
||||||
LOG_INFO("Saving %d hard coded admin keys.", numAdminKeys);
|
LOG_INFO("Saving %d hard coded admin keys.", numAdminKeys);
|
||||||
config.security.admin_key_count = numAdminKeys;
|
config.security.admin_key_count = numAdminKeys;
|
||||||
saveToDisk(SEGMENT_CONFIG);
|
_saveToDisk(SEGMENT_CONFIG);
|
||||||
}
|
}
|
||||||
|
|
||||||
state = loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig),
|
state = loadProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, sizeof(meshtastic_LocalModuleConfig),
|
||||||
@@ -1368,7 +1335,7 @@ void NodeDB::loadFromDisk()
|
|||||||
if (moduleConfig.paxcounter.paxcounter_update_interval == 900)
|
if (moduleConfig.paxcounter.paxcounter_update_interval == 900)
|
||||||
moduleConfig.paxcounter.paxcounter_update_interval = 0;
|
moduleConfig.paxcounter.paxcounter_update_interval = 0;
|
||||||
|
|
||||||
saveToDisk(SEGMENT_MODULECONFIG);
|
_saveToDisk(SEGMENT_MODULECONFIG);
|
||||||
}
|
}
|
||||||
#if ARCH_PORTDUINO
|
#if ARCH_PORTDUINO
|
||||||
// set any config overrides
|
// set any config overrides
|
||||||
@@ -1379,34 +1346,6 @@ void NodeDB::loadFromDisk()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Save a protobuf from a file, return true for success */
|
|
||||||
bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct,
|
|
||||||
bool fullAtomic)
|
|
||||||
{
|
|
||||||
bool okay = false;
|
|
||||||
#ifdef FSCom
|
|
||||||
auto f = SafeFile(filename, fullAtomic);
|
|
||||||
|
|
||||||
LOG_INFO("Save %s", filename);
|
|
||||||
pb_ostream_t stream = {&writecb, static_cast<Print *>(&f), protoSize};
|
|
||||||
|
|
||||||
if (!pb_encode(&stream, fields, dest_struct)) {
|
|
||||||
LOG_ERROR("Error: can't encode protobuf %s", PB_GET_ERROR(&stream));
|
|
||||||
} else {
|
|
||||||
okay = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool writeSucceeded = f.close();
|
|
||||||
|
|
||||||
if (!okay || !writeSucceeded) {
|
|
||||||
LOG_ERROR("Can't write prefs!");
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
LOG_ERROR("ERROR: Filesystem not implemented");
|
|
||||||
#endif
|
|
||||||
return okay;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NodeDB::saveChannelsToDisk()
|
bool NodeDB::saveChannelsToDisk()
|
||||||
{
|
{
|
||||||
#ifdef FSCom
|
#ifdef FSCom
|
||||||
@@ -1495,7 +1434,7 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NodeDB::saveToDisk(int saveWhat)
|
bool NodeDB::_saveToDisk(int saveWhat)
|
||||||
{
|
{
|
||||||
LOG_DEBUG("Save to disk %d", saveWhat);
|
LOG_DEBUG("Save to disk %d", saveWhat);
|
||||||
bool success = saveToDiskNoRetry(saveWhat);
|
bool success = saveToDiskNoRetry(saveWhat);
|
||||||
@@ -1519,10 +1458,12 @@ bool NodeDB::saveToDisk(int saveWhat)
|
|||||||
|
|
||||||
const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex)
|
const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex)
|
||||||
{
|
{
|
||||||
|
FUNCTION_START("readNextMeshNode");
|
||||||
|
meshtastic_NodeInfoLite *retVal = nullptr;
|
||||||
if (readIndex < numMeshNodes)
|
if (readIndex < numMeshNodes)
|
||||||
return &meshNodes->at(readIndex++);
|
retVal = &meshNodes->at(readIndex++);
|
||||||
else
|
FUNCTION_END;
|
||||||
return NULL;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given a node, return how many seconds in the past (vs now) that we last heard from it
|
/// Given a node, return how many seconds in the past (vs now) that we last heard from it
|
||||||
@@ -1550,7 +1491,7 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p)
|
|||||||
|
|
||||||
#define NUM_ONLINE_SECS (60 * 60 * 2) // 2 hrs to consider someone offline
|
#define NUM_ONLINE_SECS (60 * 60 * 2) // 2 hrs to consider someone offline
|
||||||
|
|
||||||
size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
|
size_t NodeDB::_getNumOnlineMeshNodes(bool localOnly)
|
||||||
{
|
{
|
||||||
size_t numseen = 0;
|
size_t numseen = 0;
|
||||||
|
|
||||||
@@ -1572,8 +1513,10 @@ size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
|
|||||||
*/
|
*/
|
||||||
void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSource src)
|
void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSource src)
|
||||||
{
|
{
|
||||||
|
FUNCTION_START("updatePosition");
|
||||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
|
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
|
||||||
if (!info) {
|
if (!info) {
|
||||||
|
FUNCTION_END;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1582,7 +1525,7 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou
|
|||||||
LOG_INFO("updatePosition LOCAL pos@%x time=%u lat=%d lon=%d alt=%d", p.timestamp, p.time, p.latitude_i, p.longitude_i,
|
LOG_INFO("updatePosition LOCAL pos@%x time=%u lat=%d lon=%d alt=%d", p.timestamp, p.time, p.latitude_i, p.longitude_i,
|
||||||
p.altitude);
|
p.altitude);
|
||||||
|
|
||||||
setLocalPosition(p);
|
_setLocalPosition(p);
|
||||||
info->position = TypeConversions::ConvertToPositionLite(p);
|
info->position = TypeConversions::ConvertToPositionLite(p);
|
||||||
} else if ((p.time > 0) && !p.latitude_i && !p.longitude_i && !p.timestamp && !p.location_source) {
|
} else if ((p.time > 0) && !p.latitude_i && !p.longitude_i && !p.timestamp && !p.location_source) {
|
||||||
// FIXME SPECIAL TIME SETTING PACKET FROM EUD TO RADIO
|
// FIXME SPECIAL TIME SETTING PACKET FROM EUD TO RADIO
|
||||||
@@ -1609,7 +1552,8 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou
|
|||||||
}
|
}
|
||||||
info->has_position = true;
|
info->has_position = true;
|
||||||
updateGUIforNode = info;
|
updateGUIforNode = info;
|
||||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
_notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||||
|
FUNCTION_END;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update telemetry info for this node based on received metrics
|
/** Update telemetry info for this node based on received metrics
|
||||||
@@ -1617,9 +1561,11 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou
|
|||||||
*/
|
*/
|
||||||
void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxSource src)
|
void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxSource src)
|
||||||
{
|
{
|
||||||
|
FUNCTION_START("updatePosition");
|
||||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
|
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
|
||||||
// Environment metrics should never go to NodeDb but we'll safegaurd anyway
|
// Environment metrics should never go to NodeDb but we'll safegaurd anyway
|
||||||
if (!info || t.which_variant != meshtastic_Telemetry_device_metrics_tag) {
|
if (!info || t.which_variant != meshtastic_Telemetry_device_metrics_tag) {
|
||||||
|
FUNCTION_END;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1632,7 +1578,8 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
|
|||||||
info->device_metrics = t.variant.device_metrics;
|
info->device_metrics = t.variant.device_metrics;
|
||||||
info->has_device_metrics = true;
|
info->has_device_metrics = true;
|
||||||
updateGUIforNode = info;
|
updateGUIforNode = info;
|
||||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
_notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||||
|
FUNCTION_END;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1640,8 +1587,10 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
|
|||||||
*/
|
*/
|
||||||
void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
||||||
{
|
{
|
||||||
|
FUNCTION_START("addFromContact");
|
||||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(contact.node_num);
|
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(contact.node_num);
|
||||||
if (!info || !contact.has_user) {
|
if (!info || !contact.has_user) {
|
||||||
|
FUNCTION_END;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If the local node has this node marked as manually verified
|
// If the local node has this node marked as manually verified
|
||||||
@@ -1650,6 +1599,7 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
|||||||
if ((info->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) && !contact.manually_verified) {
|
if ((info->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) && !contact.manually_verified) {
|
||||||
if (contact.user.public_key.size != info->user.public_key.size ||
|
if (contact.user.public_key.size != info->user.public_key.size ||
|
||||||
memcmp(contact.user.public_key.bytes, info->user.public_key.bytes, info->user.public_key.size) != 0) {
|
memcmp(contact.user.public_key.bytes, info->user.public_key.bytes, info->user.public_key.size) != 0) {
|
||||||
|
FUNCTION_END;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1693,22 +1643,26 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
|||||||
// Mark the node's key as manually verified to indicate trustworthiness.
|
// Mark the node's key as manually verified to indicate trustworthiness.
|
||||||
updateGUIforNode = info;
|
updateGUIforNode = info;
|
||||||
sortMeshDB();
|
sortMeshDB();
|
||||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
_notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||||
}
|
}
|
||||||
saveNodeDatabaseToDisk();
|
saveNodeDatabaseToDisk();
|
||||||
|
FUNCTION_END;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update user info and channel for this node based on received user data
|
/** Update user info and channel for this node based on received user data
|
||||||
*/
|
*/
|
||||||
bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex)
|
bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex)
|
||||||
{
|
{
|
||||||
|
FUNCTION_START("updateUser");
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
|
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
|
||||||
if (!info) {
|
if (!info) {
|
||||||
|
FUNCTION_END;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
#if !(MESHTASTIC_EXCLUDE_PKI)
|
||||||
if (p.public_key.size == 32 && nodeId != nodeDB->getNodeNum()) {
|
if (p.public_key.size == 32 && nodeId != getNodeNum()) {
|
||||||
printBytes("Incoming Pubkey: ", p.public_key.bytes, 32);
|
printBytes("Incoming Pubkey: ", p.public_key.bytes, 32);
|
||||||
|
|
||||||
// Alert the user if a remote node is advertising public key that matches our own
|
// Alert the user if a remote node is advertising public key that matches our own
|
||||||
@@ -1725,6 +1679,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
|||||||
sprintf(cn->message, warning, p.long_name);
|
sprintf(cn->message, warning, p.long_name);
|
||||||
service->sendClientNotification(cn);
|
service->sendClientNotification(cn);
|
||||||
}
|
}
|
||||||
|
FUNCTION_END;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1732,6 +1687,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
|||||||
// if the key doesn't match, don't update nodeDB at all.
|
// if the key doesn't match, don't update nodeDB at all.
|
||||||
if (p.public_key.size != 32 || (memcmp(p.public_key.bytes, info->user.public_key.bytes, 32) != 0)) {
|
if (p.public_key.size != 32 || (memcmp(p.public_key.bytes, info->user.public_key.bytes, 32) != 0)) {
|
||||||
LOG_WARN("Public Key mismatch, dropping NodeInfo");
|
LOG_WARN("Public Key mismatch, dropping NodeInfo");
|
||||||
|
FUNCTION_END;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
LOG_INFO("Public Key set for node, not updating!");
|
LOG_INFO("Public Key set for node, not updating!");
|
||||||
@@ -1759,19 +1715,19 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
|||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
updateGUIforNode = info;
|
updateGUIforNode = info;
|
||||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
_notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||||
|
|
||||||
// We just changed something about a User,
|
// We just changed something about a User,
|
||||||
// store our DB unless we just did so less than a minute ago
|
// store our DB unless we just did so less than a minute ago
|
||||||
|
|
||||||
if (!Throttle::isWithinTimespanMs(lastNodeDbSave, ONE_MINUTE_MS)) {
|
if (!Throttle::isWithinTimespanMs(lastNodeDbSave, ONE_MINUTE_MS)) {
|
||||||
saveToDisk(SEGMENT_NODEDATABASE);
|
_saveToDisk(SEGMENT_NODEDATABASE);
|
||||||
lastNodeDbSave = millis();
|
lastNodeDbSave = millis();
|
||||||
} else {
|
} else {
|
||||||
LOG_DEBUG("Defer NodeDB saveToDisk for now");
|
LOG_DEBUG("Defer NodeDB saveToDisk for now");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
FUNCTION_END;
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1779,107 +1735,121 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
|||||||
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
||||||
void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
|
void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
|
||||||
{
|
{
|
||||||
|
FUNCTION_START("updateFrom");
|
||||||
if (mp.from == getNodeNum()) {
|
if (mp.from == getNodeNum()) {
|
||||||
LOG_DEBUG("Ignore update from self");
|
LOG_DEBUG("Ignore update from self");
|
||||||
return;
|
} else if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
|
||||||
}
|
|
||||||
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
|
|
||||||
LOG_DEBUG("Update DB node 0x%x, rx_time=%u", mp.from, mp.rx_time);
|
LOG_DEBUG("Update DB node 0x%x, rx_time=%u", mp.from, mp.rx_time);
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getFrom(&mp));
|
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getFrom(&mp));
|
||||||
if (!info) {
|
if (info) {
|
||||||
return;
|
if (mp.rx_time) // if the packet has a valid timestamp use it to update our last_heard
|
||||||
|
info->last_heard = mp.rx_time;
|
||||||
|
|
||||||
|
if (mp.rx_snr)
|
||||||
|
info->snr = mp.rx_snr; // keep the most recent SNR we received for this node.
|
||||||
|
|
||||||
|
info->via_mqtt = mp.via_mqtt; // Store if we received this packet via MQTT
|
||||||
|
|
||||||
|
// If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway
|
||||||
|
if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start) {
|
||||||
|
info->has_hops_away = true;
|
||||||
|
info->hops_away = mp.hop_start - mp.hop_limit;
|
||||||
|
}
|
||||||
|
sortMeshDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mp.rx_time) // if the packet has a valid timestamp use it to update our last_heard
|
|
||||||
info->last_heard = mp.rx_time;
|
|
||||||
|
|
||||||
if (mp.rx_snr)
|
|
||||||
info->snr = mp.rx_snr; // keep the most recent SNR we received for this node.
|
|
||||||
|
|
||||||
info->via_mqtt = mp.via_mqtt; // Store if we received this packet via MQTT
|
|
||||||
|
|
||||||
// If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway
|
|
||||||
if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start) {
|
|
||||||
info->has_hops_away = true;
|
|
||||||
info->hops_away = mp.hop_start - mp.hop_limit;
|
|
||||||
}
|
|
||||||
sortMeshDB();
|
|
||||||
}
|
}
|
||||||
|
FUNCTION_END;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
|
void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
FUNCTION_START("set_favorite");
|
||||||
|
meshtastic_NodeInfoLite *lite = _getMeshNode(nodeId);
|
||||||
if (lite && lite->is_favorite != is_favorite) {
|
if (lite && lite->is_favorite != is_favorite) {
|
||||||
lite->is_favorite = is_favorite;
|
lite->is_favorite = is_favorite;
|
||||||
sortMeshDB();
|
sortMeshDB();
|
||||||
saveNodeDatabaseToDisk();
|
saveNodeDatabaseToDisk();
|
||||||
}
|
}
|
||||||
|
FUNCTION_END;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns true if nodeId is_favorite; false if not or not found
|
||||||
bool NodeDB::isFavorite(uint32_t nodeId)
|
bool NodeDB::isFavorite(uint32_t nodeId)
|
||||||
{
|
{
|
||||||
// returns true if nodeId is_favorite; false if not or not found
|
FUNCTION_START("set_favorite");
|
||||||
|
|
||||||
// NODENUM_BROADCAST will never be in the DB
|
// NODENUM_BROADCAST will never be in the DB
|
||||||
if (nodeId == NODENUM_BROADCAST)
|
if (nodeId == NODENUM_BROADCAST) {
|
||||||
|
FUNCTION_END;
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
meshtastic_NodeInfoLite *lite = _getMeshNode(nodeId);
|
||||||
|
|
||||||
if (lite) {
|
if (lite) {
|
||||||
|
FUNCTION_END;
|
||||||
return lite->is_favorite;
|
return lite->is_favorite;
|
||||||
}
|
}
|
||||||
|
FUNCTION_END;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
|
bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
|
||||||
{
|
{
|
||||||
|
FUNCTION_START("isFromOrToFavoritedNode");
|
||||||
// This method is logically equivalent to:
|
// This method is logically equivalent to:
|
||||||
// return isFavorite(p.from) || isFavorite(p.to);
|
// return isFavorite(p.from) || isFavorite(p.to);
|
||||||
// but is more efficient by:
|
// but is more efficient by:
|
||||||
// 1. doing only one pass through the database, instead of two
|
// 1. doing only one pass through the database, instead of two
|
||||||
// 2. exiting early when a favorite is found, or if both from and to have been seen
|
// 2. exiting early when a favorite is found, or if both from and to have been seen
|
||||||
|
|
||||||
if (p.to == NODENUM_BROADCAST)
|
|
||||||
return isFavorite(p.from); // we never store NODENUM_BROADCAST in the DB, so we only need to check p.from
|
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *lite = NULL;
|
meshtastic_NodeInfoLite *lite = NULL;
|
||||||
|
|
||||||
bool seenFrom = false;
|
bool seenFrom = false;
|
||||||
bool seenTo = false;
|
bool seenTo = false;
|
||||||
|
|
||||||
|
if (p.to == NODENUM_BROADCAST)
|
||||||
|
seenTo = true;
|
||||||
|
|
||||||
for (int i = 0; i < numMeshNodes; i++) {
|
for (int i = 0; i < numMeshNodes; i++) {
|
||||||
lite = &meshNodes->at(i);
|
lite = &meshNodes->at(i);
|
||||||
|
|
||||||
if (lite->num == p.from) {
|
if (!seenFrom && lite->num == p.from) {
|
||||||
if (lite->is_favorite)
|
if (lite->is_favorite) {
|
||||||
|
FUNCTION_END;
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
seenFrom = true;
|
seenFrom = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lite->num == p.to) {
|
if (!seenTo && lite->num == p.to) {
|
||||||
if (lite->is_favorite)
|
if (lite->is_favorite) {
|
||||||
|
FUNCTION_END;
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
seenTo = true;
|
seenTo = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seenFrom && seenTo)
|
if (seenFrom && seenTo) {
|
||||||
|
FUNCTION_END;
|
||||||
return false; // we've seen both, and neither is a favorite, so we can stop searching early
|
return false; // we've seen both, and neither is a favorite, so we can stop searching early
|
||||||
|
}
|
||||||
|
|
||||||
// Note: if we knew that sortMeshDB was always called after any change to is_favorite, we could exit early after searching
|
// Note: if we knew that sortMeshDB was always called after any change to is_favorite, we could exit early after searching
|
||||||
// all favorited nodes first.
|
// all favorited nodes first.
|
||||||
}
|
}
|
||||||
|
FUNCTION_END;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDB::pause_sort(bool paused)
|
void NodeDB::pause_sort(bool paused)
|
||||||
{
|
{
|
||||||
|
// Including the mutex macro for completeness, but it's possible it isn't appropriate here
|
||||||
|
FUNCTION_START("pause_sort");
|
||||||
sortingIsPaused = paused;
|
sortingIsPaused = paused;
|
||||||
|
FUNCTION_END;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDB::sortMeshDB()
|
void NodeDB::sortMeshDB()
|
||||||
@@ -1914,10 +1884,13 @@ void NodeDB::sortMeshDB()
|
|||||||
|
|
||||||
uint8_t NodeDB::getMeshNodeChannel(NodeNum n)
|
uint8_t NodeDB::getMeshNodeChannel(NodeNum n)
|
||||||
{
|
{
|
||||||
const meshtastic_NodeInfoLite *info = getMeshNode(n);
|
FUNCTION_START("getMeshNodeChannel");
|
||||||
|
const meshtastic_NodeInfoLite *info = _getMeshNode(n);
|
||||||
if (!info) {
|
if (!info) {
|
||||||
|
FUNCTION_END;
|
||||||
return 0; // defaults to PRIMARY
|
return 0; // defaults to PRIMARY
|
||||||
}
|
}
|
||||||
|
FUNCTION_END;
|
||||||
return info->channel;
|
return info->channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1930,7 +1903,7 @@ std::string NodeDB::getNodeId() const
|
|||||||
|
|
||||||
/// Find a node in our DB, return null for missing
|
/// Find a node in our DB, return null for missing
|
||||||
/// NOTE: This function might be called from an ISR
|
/// NOTE: This function might be called from an ISR
|
||||||
meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n)
|
meshtastic_NodeInfoLite *NodeDB::_getMeshNode(NodeNum n)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < numMeshNodes; i++)
|
for (int i = 0; i < numMeshNodes; i++)
|
||||||
if (meshNodes->at(i).num == n)
|
if (meshNodes->at(i).num == n)
|
||||||
@@ -1940,7 +1913,7 @@ meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// returns true if the maximum number of nodes is reached or we are running low on memory
|
// returns true if the maximum number of nodes is reached or we are running low on memory
|
||||||
bool NodeDB::isFull()
|
bool NodeDB::_isFull()
|
||||||
{
|
{
|
||||||
return (numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < MINIMUM_SAFE_FREE_HEAP);
|
return (numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < MINIMUM_SAFE_FREE_HEAP);
|
||||||
}
|
}
|
||||||
@@ -1948,7 +1921,7 @@ bool NodeDB::isFull()
|
|||||||
/// Find a node in our DB, create an empty NodeInfo if missing
|
/// Find a node in our DB, create an empty NodeInfo if missing
|
||||||
meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *lite = getMeshNode(n);
|
meshtastic_NodeInfoLite *lite = _getMeshNode(n);
|
||||||
|
|
||||||
if (!lite) {
|
if (!lite) {
|
||||||
if (isFull()) {
|
if (isFull()) {
|
||||||
@@ -2003,18 +1976,25 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
|||||||
/// valid lat/lon
|
/// valid lat/lon
|
||||||
bool NodeDB::hasValidPosition(const meshtastic_NodeInfoLite *n)
|
bool NodeDB::hasValidPosition(const meshtastic_NodeInfoLite *n)
|
||||||
{
|
{
|
||||||
return n->has_position && (n->position.latitude_i != 0 || n->position.longitude_i != 0);
|
FUNCTION_START("hasValidPosition");
|
||||||
|
auto retVal = n->has_position && (n->position.latitude_i != 0 || n->position.longitude_i != 0);
|
||||||
|
FUNCTION_END;
|
||||||
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If we have a node / user and they report is_licensed = true
|
/// If we have a node / user and they report is_licensed = true
|
||||||
/// we consider them licensed
|
/// we consider them licensed
|
||||||
UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum)
|
UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum)
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *info = getMeshNode(nodeNum);
|
FUNCTION_START("getLicenseStatus");
|
||||||
|
meshtastic_NodeInfoLite *info = _getMeshNode(nodeNum);
|
||||||
if (!info || !info->has_user) {
|
if (!info || !info->has_user) {
|
||||||
|
FUNCTION_END;
|
||||||
return UserLicenseStatus::NotKnown;
|
return UserLicenseStatus::NotKnown;
|
||||||
}
|
}
|
||||||
return info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed;
|
auto retVal = info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed;
|
||||||
|
FUNCTION_END;
|
||||||
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
||||||
@@ -2036,6 +2016,7 @@ bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_pub
|
|||||||
|
|
||||||
bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location)
|
bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location)
|
||||||
{
|
{
|
||||||
|
FUNCTION_START("backupPreferences");
|
||||||
bool success = false;
|
bool success = false;
|
||||||
lastBackupAttempt = millis();
|
lastBackupAttempt = millis();
|
||||||
#ifdef FSCom
|
#ifdef FSCom
|
||||||
@@ -2069,11 +2050,13 @@ bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location)
|
|||||||
// TODO: After more mainline SD card support
|
// TODO: After more mainline SD card support
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
FUNCTION_END;
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location, int restoreWhat)
|
bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location, int restoreWhat)
|
||||||
{
|
{
|
||||||
|
FUNCTION_START("backupPreferences");
|
||||||
bool success = false;
|
bool success = false;
|
||||||
#ifdef FSCom
|
#ifdef FSCom
|
||||||
if (location == meshtastic_AdminMessage_BackupLocation_FLASH) {
|
if (location == meshtastic_AdminMessage_BackupLocation_FLASH) {
|
||||||
@@ -2081,6 +2064,7 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location,
|
|||||||
if (!FSCom.exists(backupFileName)) {
|
if (!FSCom.exists(backupFileName)) {
|
||||||
spiLock->unlock();
|
spiLock->unlock();
|
||||||
LOG_WARN("Could not restore. No backup file found");
|
LOG_WARN("Could not restore. No backup file found");
|
||||||
|
FUNCTION_END;
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
spiLock->unlock();
|
spiLock->unlock();
|
||||||
@@ -2106,7 +2090,7 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location,
|
|||||||
LOG_DEBUG("Restored channels");
|
LOG_DEBUG("Restored channels");
|
||||||
}
|
}
|
||||||
|
|
||||||
success = saveToDisk(restoreWhat);
|
success = _saveToDisk(restoreWhat);
|
||||||
if (success) {
|
if (success) {
|
||||||
LOG_INFO("Restored preferences from backup");
|
LOG_INFO("Restored preferences from backup");
|
||||||
} else {
|
} else {
|
||||||
@@ -2118,6 +2102,7 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location,
|
|||||||
} else if (location == meshtastic_AdminMessage_BackupLocation_SD) {
|
} else if (location == meshtastic_AdminMessage_BackupLocation_SD) {
|
||||||
// TODO: After more mainline SD card support
|
// TODO: After more mainline SD card support
|
||||||
}
|
}
|
||||||
|
FUNCTION_END;
|
||||||
return success;
|
return success;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,13 @@
|
|||||||
#include "PortduinoGlue.h"
|
#include "PortduinoGlue.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define FUNCTION_START(FUNCTION_NAME) \
|
||||||
|
if (fakeMutex) \
|
||||||
|
LOG_ERROR("Concurrency violation in " FUNCTION_NAME); \
|
||||||
|
fakeMutex = true;
|
||||||
|
|
||||||
|
#define FUNCTION_END fakeMutex = false;
|
||||||
|
|
||||||
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
||||||
// E3B0C442 is the blank hash
|
// E3B0C442 is the blank hash
|
||||||
static const uint8_t LOW_ENTROPY_HASHES[][32] = {
|
static const uint8_t LOW_ENTROPY_HASHES[][32] = {
|
||||||
@@ -110,19 +117,6 @@ uint32_t sinceLastSeen(const meshtastic_NodeInfoLite *n);
|
|||||||
/// Given a packet, return how many seconds in the past (vs now) it was received
|
/// Given a packet, return how many seconds in the past (vs now) it was received
|
||||||
uint32_t sinceReceived(const meshtastic_MeshPacket *p);
|
uint32_t sinceReceived(const meshtastic_MeshPacket *p);
|
||||||
|
|
||||||
enum LoadFileResult {
|
|
||||||
// Successfully opened the file
|
|
||||||
LOAD_SUCCESS = 1,
|
|
||||||
// File does not exist
|
|
||||||
NOT_FOUND = 2,
|
|
||||||
// Device does not have a filesystem
|
|
||||||
NO_FILESYSTEM = 3,
|
|
||||||
// File exists, but could not decode protobufs
|
|
||||||
DECODE_FAILED = 4,
|
|
||||||
// File exists, but open failed for some reason
|
|
||||||
OTHER_FAILURE = 5
|
|
||||||
};
|
|
||||||
|
|
||||||
enum UserLicenseStatus { NotKnown, NotLicensed, Licensed };
|
enum UserLicenseStatus { NotKnown, NotLicensed, Licensed };
|
||||||
|
|
||||||
class NodeDB
|
class NodeDB
|
||||||
@@ -135,7 +129,6 @@ class NodeDB
|
|||||||
// Note: these two references just point into our static array we serialize to/from disk
|
// Note: these two references just point into our static array we serialize to/from disk
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::vector<meshtastic_NodeInfoLite> *meshNodes;
|
|
||||||
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
|
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
|
||||||
meshtastic_NodeInfoLite *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
meshtastic_NodeInfoLite *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
||||||
Observable<const meshtastic::NodeStatus *> newStatus;
|
Observable<const meshtastic::NodeStatus *> newStatus;
|
||||||
@@ -151,17 +144,26 @@ class NodeDB
|
|||||||
/// write to flash
|
/// write to flash
|
||||||
/// @return true if the save was successful
|
/// @return true if the save was successful
|
||||||
bool saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS |
|
bool saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS |
|
||||||
SEGMENT_NODEDATABASE);
|
SEGMENT_NODEDATABASE)
|
||||||
|
{
|
||||||
|
FUNCTION_START("saveToDisk");
|
||||||
|
auto retVal = _saveToDisk(saveWhat);
|
||||||
|
FUNCTION_END;
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
/** Reinit radio config if needed, because either:
|
/** Reinit radio config if needed, because either:
|
||||||
* a) sometimes a buggy android app might send us bogus settings or
|
* a) sometimes a buggy android app might send us bogus settings or
|
||||||
* b) the client set factory_reset
|
* b) the client set factory_reset
|
||||||
*
|
*
|
||||||
* @param factory_reset if true, reset all settings to factory defaults
|
|
||||||
* @param is_fresh_install set to true after a fresh install, to trigger NodeInfo/Position requests
|
* @param is_fresh_install set to true after a fresh install, to trigger NodeInfo/Position requests
|
||||||
* @return true if the config was completely reset, in that case, we should send it back to the client
|
|
||||||
*/
|
*/
|
||||||
void resetRadioConfig(bool is_fresh_install = false);
|
void resetRadioConfig(bool is_fresh_install = false)
|
||||||
|
{
|
||||||
|
FUNCTION_START("resetRadioConfig");
|
||||||
|
_resetRadioConfig(is_fresh_install);
|
||||||
|
FUNCTION_END;
|
||||||
|
}
|
||||||
|
|
||||||
/// given a subpacket sniffed from the network, update our DB state
|
/// given a subpacket sniffed from the network, update our DB state
|
||||||
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
||||||
@@ -208,7 +210,13 @@ class NodeDB
|
|||||||
std::string getNodeId() const;
|
std::string getNodeId() const;
|
||||||
|
|
||||||
// @return last byte of a NodeNum, 0xFF if it ended at 0x00
|
// @return last byte of a NodeNum, 0xFF if it ended at 0x00
|
||||||
uint8_t getLastByteOfNodeNum(NodeNum num) { return (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF); }
|
uint8_t getLastByteOfNodeNum(NodeNum num)
|
||||||
|
{
|
||||||
|
FUNCTION_START("getLastByteOfNodeNum");
|
||||||
|
auto retVal = (uint8_t)((num & 0xFF) ? (num & 0xFF) : 0xFF);
|
||||||
|
FUNCTION_END;
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
/// if returns false, that means our node should send a DenyNodeNum response. If true, we think the number is okay for use
|
/// if returns false, that means our node should send a DenyNodeNum response. If true, we think the number is okay for use
|
||||||
// bool handleWantNodeNum(NodeNum n);
|
// bool handleWantNodeNum(NodeNum n);
|
||||||
@@ -227,85 +235,105 @@ class NodeDB
|
|||||||
/* Return the number of nodes we've heard from recently (within the last 2 hrs?)
|
/* Return the number of nodes we've heard from recently (within the last 2 hrs?)
|
||||||
* @param localOnly if true, ignore nodes heard via MQTT
|
* @param localOnly if true, ignore nodes heard via MQTT
|
||||||
*/
|
*/
|
||||||
size_t getNumOnlineMeshNodes(bool localOnly = false);
|
size_t getNumOnlineMeshNodes(bool localOnly = false)
|
||||||
|
{
|
||||||
|
FUNCTION_START("getNumOnlineMeshNodes");
|
||||||
|
auto retVal = _getNumOnlineMeshNodes(localOnly);
|
||||||
|
FUNCTION_END;
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(bool keepFavorites = false),
|
void resetNodes(bool keepFavorites = false), removeNodeByNum(NodeNum nodeNum);
|
||||||
removeNodeByNum(NodeNum nodeNum);
|
|
||||||
|
|
||||||
bool factoryReset(bool eraseBleBonds = false);
|
bool factoryReset(bool eraseBleBonds = false);
|
||||||
|
|
||||||
LoadFileResult loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields,
|
void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role)
|
||||||
void *dest_struct);
|
{
|
||||||
bool saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct,
|
FUNCTION_START("installRoleDefaults");
|
||||||
bool fullAtomic = true);
|
_installRoleDefaults(role);
|
||||||
|
FUNCTION_END;
|
||||||
void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role);
|
}
|
||||||
|
|
||||||
const meshtastic_NodeInfoLite *readNextMeshNode(uint32_t &readIndex);
|
const meshtastic_NodeInfoLite *readNextMeshNode(uint32_t &readIndex);
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *getMeshNodeByIndex(size_t x)
|
meshtastic_NodeInfoLite *getMeshNodeByIndex(size_t x)
|
||||||
{
|
{
|
||||||
assert(x < numMeshNodes);
|
FUNCTION_START("getMeshNodeByIndex");
|
||||||
return &meshNodes->at(x);
|
meshtastic_NodeInfoLite *retValue = nullptr;
|
||||||
|
if (x < numMeshNodes)
|
||||||
|
retValue = &meshNodes->at(x);
|
||||||
|
FUNCTION_END;
|
||||||
|
return retValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n);
|
virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n)
|
||||||
size_t getNumMeshNodes() { return numMeshNodes; }
|
{
|
||||||
|
FUNCTION_START("getMeshNode");
|
||||||
|
auto retVal = _getMeshNode(n);
|
||||||
|
FUNCTION_END;
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t getNumMeshNodes()
|
||||||
|
{
|
||||||
|
FUNCTION_START("getNumMeshNodes");
|
||||||
|
auto retVal = numMeshNodes;
|
||||||
|
FUNCTION_END;
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
UserLicenseStatus getLicenseStatus(uint32_t nodeNum);
|
UserLicenseStatus getLicenseStatus(uint32_t nodeNum);
|
||||||
|
|
||||||
size_t getMaxNodesAllocatedSize()
|
// returns true if the maximum number of nodes is reached or we are running low on memory
|
||||||
|
bool isFull()
|
||||||
{
|
{
|
||||||
meshtastic_NodeDatabase emptyNodeDatabase;
|
FUNCTION_START("isFull");
|
||||||
emptyNodeDatabase.version = DEVICESTATE_CUR_VER;
|
auto retVal = _isFull();
|
||||||
size_t nodeDatabaseSize;
|
FUNCTION_END;
|
||||||
pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &emptyNodeDatabase);
|
return retVal;
|
||||||
return nodeDatabaseSize + (MAX_NUM_NODES * meshtastic_NodeInfoLite_size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns true if the maximum number of nodes is reached or we are running low on memory
|
void clearLocalPosition()
|
||||||
bool isFull();
|
{
|
||||||
|
FUNCTION_START("clearLocalPosition");
|
||||||
void clearLocalPosition();
|
_clearLocalPosition();
|
||||||
|
FUNCTION_END;
|
||||||
|
}
|
||||||
|
|
||||||
void setLocalPosition(meshtastic_Position position, bool timeOnly = false)
|
void setLocalPosition(meshtastic_Position position, bool timeOnly = false)
|
||||||
{
|
{
|
||||||
if (timeOnly) {
|
FUNCTION_START("setLocalPosition");
|
||||||
LOG_DEBUG("Set local position time only: time=%u timestamp=%u", position.time, position.timestamp);
|
_setLocalPosition(position, timeOnly);
|
||||||
localPosition.time = position.time;
|
FUNCTION_END;
|
||||||
localPosition.timestamp = position.timestamp > 0 ? position.timestamp : position.time;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
LOG_DEBUG("Set local position: lat=%i lon=%i time=%u timestamp=%u", position.latitude_i, position.longitude_i,
|
|
||||||
position.time, position.timestamp);
|
|
||||||
localPosition = position;
|
|
||||||
if (position.latitude_i != 0 || position.longitude_i != 0) {
|
|
||||||
localPositionUpdatedSinceBoot = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasValidPosition(const meshtastic_NodeInfoLite *n);
|
bool hasValidPosition(const meshtastic_NodeInfoLite *n);
|
||||||
bool hasLocalPositionSinceBoot() const { return localPositionUpdatedSinceBoot; }
|
|
||||||
|
|
||||||
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
|
||||||
bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool backupPreferences(meshtastic_AdminMessage_BackupLocation location);
|
bool backupPreferences(meshtastic_AdminMessage_BackupLocation location);
|
||||||
bool restorePreferences(meshtastic_AdminMessage_BackupLocation location,
|
bool restorePreferences(meshtastic_AdminMessage_BackupLocation location,
|
||||||
int restoreWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS);
|
int restoreWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS);
|
||||||
|
|
||||||
/// Notify observers of changes to the DB
|
|
||||||
void notifyObservers(bool forceUpdate = false)
|
void notifyObservers(bool forceUpdate = false)
|
||||||
{
|
{
|
||||||
// Notify observers of the current node state
|
FUNCTION_START("notifyObservers");
|
||||||
const meshtastic::NodeStatus status = meshtastic::NodeStatus(getNumOnlineMeshNodes(), getNumMeshNodes(), forceUpdate);
|
_notifyObservers(forceUpdate);
|
||||||
newStatus.notifyObservers(&status);
|
FUNCTION_END;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool fakeMutex = false;
|
||||||
|
|
||||||
|
/// Notify observers of changes to the DB
|
||||||
|
void _notifyObservers(bool forceUpdate = false)
|
||||||
|
{
|
||||||
|
// Notify observers of the current node state
|
||||||
|
const meshtastic::NodeStatus status = meshtastic::NodeStatus(_getNumOnlineMeshNodes(), numMeshNodes, forceUpdate);
|
||||||
|
newStatus.notifyObservers(&status);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<meshtastic_NodeInfoLite> *meshNodes;
|
||||||
|
|
||||||
bool duplicateWarned = false;
|
bool duplicateWarned = false;
|
||||||
bool localPositionUpdatedSinceBoot = false;
|
|
||||||
uint32_t lastNodeDbSave = 0; // when we last saved our db to flash
|
uint32_t lastNodeDbSave = 0; // when we last saved our db to flash
|
||||||
uint32_t lastBackupAttempt = 0; // when we last tried a backup automatically or manually
|
uint32_t lastBackupAttempt = 0; // when we last tried a backup automatically or manually
|
||||||
uint32_t lastSort = 0; // When last sorted the nodeDB
|
uint32_t lastSort = 0; // When last sorted the nodeDB
|
||||||
@@ -338,6 +366,51 @@ class NodeDB
|
|||||||
bool saveDeviceStateToDisk();
|
bool saveDeviceStateToDisk();
|
||||||
bool saveNodeDatabaseToDisk();
|
bool saveNodeDatabaseToDisk();
|
||||||
void sortMeshDB();
|
void sortMeshDB();
|
||||||
|
|
||||||
|
void initConfigIntervals(), initModuleConfigIntervals();
|
||||||
|
|
||||||
|
size_t getMaxNodesAllocatedSize()
|
||||||
|
{
|
||||||
|
meshtastic_NodeDatabase emptyNodeDatabase;
|
||||||
|
emptyNodeDatabase.version = DEVICESTATE_CUR_VER;
|
||||||
|
size_t nodeDatabaseSize;
|
||||||
|
pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &emptyNodeDatabase);
|
||||||
|
return nodeDatabaseSize + (MAX_NUM_NODES * meshtastic_NodeInfoLite_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest);
|
||||||
|
|
||||||
|
// wrapped private functions:
|
||||||
|
|
||||||
|
bool _saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS |
|
||||||
|
SEGMENT_NODEDATABASE);
|
||||||
|
void _resetRadioConfig(bool is_fresh_install = false);
|
||||||
|
|
||||||
|
/* Return the number of nodes we've heard from recently (within the last 2 hrs?)
|
||||||
|
* @param localOnly if true, ignore nodes heard via MQTT
|
||||||
|
*/
|
||||||
|
size_t _getNumOnlineMeshNodes(bool localOnly = false);
|
||||||
|
|
||||||
|
void _installRoleDefaults(meshtastic_Config_DeviceConfig_Role role);
|
||||||
|
|
||||||
|
meshtastic_NodeInfoLite *_getMeshNode(NodeNum n);
|
||||||
|
|
||||||
|
bool _isFull();
|
||||||
|
|
||||||
|
void _clearLocalPosition();
|
||||||
|
|
||||||
|
void _setLocalPosition(meshtastic_Position position, bool timeOnly = false)
|
||||||
|
{
|
||||||
|
if (timeOnly) {
|
||||||
|
LOG_DEBUG("Set local position time only: time=%u timestamp=%u", position.time, position.timestamp);
|
||||||
|
localPosition.time = position.time;
|
||||||
|
localPosition.timestamp = position.timestamp > 0 ? position.timestamp : position.time;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LOG_DEBUG("Set local position: lat=%i lon=%i time=%u timestamp=%u", position.latitude_i, position.longitude_i,
|
||||||
|
position.time, position.timestamp);
|
||||||
|
localPosition = position;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
extern NodeDB *nodeDB;
|
extern NodeDB *nodeDB;
|
||||||
|
|||||||
@@ -296,6 +296,11 @@ bool RadioInterface::shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we are a CLIENT_BASE and the packet is from or to a favorited node, we should rebroadcast early
|
||||||
|
if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
|
||||||
|
return nodeDB->isFromOrToFavoritedNode(*p);
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -323,9 +328,9 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(meshtastic_MeshPacket *p)
|
|||||||
void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
|
void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
|
||||||
{
|
{
|
||||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||||
std::string out = DEBUG_PORT.mt_sprintf(
|
std::string out =
|
||||||
"%s (id=0x%08x fr=0x%08x to=0x%08x, transport = %u, WantAck=%d, HopLim=%d HopStart=%d Ch=0x%x", prefix, p->id, p->from,
|
DEBUG_PORT.mt_sprintf("%s (id=0x%08x fr=0x%08x to=0x%08x, transport = %u, WantAck=%d, HopLim=%d Ch=0x%x", prefix, p->id,
|
||||||
p->to, p->transport_mechanism, p->want_ack, p->hop_limit, p->hop_start, p->channel);
|
p->from, p->to, p->transport_mechanism, p->want_ack, p->hop_limit, p->channel);
|
||||||
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
||||||
auto &s = p->decoded;
|
auto &s = p->decoded;
|
||||||
|
|
||||||
@@ -498,11 +503,6 @@ void RadioInterface::applyModemConfig()
|
|||||||
cr = 5;
|
cr = 5;
|
||||||
sf = 10;
|
sf = 10;
|
||||||
break;
|
break;
|
||||||
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO:
|
|
||||||
bw = (myRegion->wideLora) ? 1625.0 : 500;
|
|
||||||
cr = 8;
|
|
||||||
sf = 11;
|
|
||||||
break;
|
|
||||||
default: // Config_LoRaConfig_ModemPreset_LONG_FAST is default. Gracefully use this is preset is something illegal.
|
default: // Config_LoRaConfig_ModemPreset_LONG_FAST is default. Gracefully use this is preset is something illegal.
|
||||||
bw = (myRegion->wideLora) ? 812.5 : 250;
|
bw = (myRegion->wideLora) ? 812.5 : 250;
|
||||||
cr = 5;
|
cr = 5;
|
||||||
@@ -539,26 +539,13 @@ void RadioInterface::applyModemConfig()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((myRegion->freqEnd - myRegion->freqStart) < bw / 1000) {
|
if ((myRegion->freqEnd - myRegion->freqStart) < bw / 1000) {
|
||||||
const float regionSpanKHz = (myRegion->freqEnd - myRegion->freqStart) * 1000.0f;
|
static const char *err_string = "Regional frequency range is smaller than bandwidth. Fall back to default preset";
|
||||||
const float requestedBwKHz = bw;
|
LOG_ERROR(err_string);
|
||||||
const bool isWideRequest = requestedBwKHz >= 499.5f; // treat as 500 kHz preset
|
|
||||||
const char *presetName =
|
|
||||||
DisplayFormatters::getModemPresetDisplayName(loraConfig.modem_preset, false, loraConfig.use_preset);
|
|
||||||
|
|
||||||
char err_string[160];
|
|
||||||
if (isWideRequest) {
|
|
||||||
snprintf(err_string, sizeof(err_string), "%s region too narrow for 500kHz preset (%s). Falling back to LongFast.",
|
|
||||||
myRegion->name, presetName);
|
|
||||||
} else {
|
|
||||||
snprintf(err_string, sizeof(err_string), "%s region span %.0fkHz < requested %.0fkHz. Falling back to LongFast.",
|
|
||||||
myRegion->name, regionSpanKHz, requestedBwKHz);
|
|
||||||
}
|
|
||||||
LOG_ERROR("%s", err_string);
|
|
||||||
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||||
|
|
||||||
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
|
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
|
||||||
cn->level = meshtastic_LogRecord_Level_ERROR;
|
cn->level = meshtastic_LogRecord_Level_ERROR;
|
||||||
snprintf(cn->message, sizeof(cn->message), "%s", err_string);
|
sprintf(cn->message, err_string);
|
||||||
service->sendClientNotification(cn);
|
service->sendClientNotification(cn);
|
||||||
|
|
||||||
// Set to default modem preset
|
// Set to default modem preset
|
||||||
|
|||||||
@@ -150,9 +150,7 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
|
|||||||
PacketId nakId = (c && c->error_reason != meshtastic_Routing_Error_NONE) ? p->decoded.request_id : 0;
|
PacketId nakId = (c && c->error_reason != meshtastic_Routing_Error_NONE) ? p->decoded.request_id : 0;
|
||||||
|
|
||||||
// We intentionally don't check wasSeenRecently, because it is harmless to delete non existent retransmission records
|
// We intentionally don't check wasSeenRecently, because it is harmless to delete non existent retransmission records
|
||||||
if ((ackId || nakId) &&
|
if (ackId || nakId) {
|
||||||
// Implicit ACKs from MQTT should not stop retransmissions
|
|
||||||
!(isFromUs(p) && p->transport_mechanism == meshtastic_MeshPacket_TransportMechanism_TRANSPORT_MQTT)) {
|
|
||||||
LOG_DEBUG("Received a %s for 0x%x, stopping retransmissions", ackId ? "ACK" : "NAK", ackId);
|
LOG_DEBUG("Received a %s for 0x%x, stopping retransmissions", ackId ? "ACK" : "NAK", ackId);
|
||||||
if (ackId) {
|
if (ackId) {
|
||||||
stopRetransmission(p->to, ackId);
|
stopRetransmission(p->to, ackId);
|
||||||
|
|||||||
@@ -526,10 +526,6 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
|||||||
#elif ARCH_PORTDUINO
|
#elif ARCH_PORTDUINO
|
||||||
if (portduino_config.traceFilename != "" || portduino_config.logoutputlevel == level_trace) {
|
if (portduino_config.traceFilename != "" || portduino_config.logoutputlevel == level_trace) {
|
||||||
LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str());
|
LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str());
|
||||||
} else if (portduino_config.JSONFilename != "") {
|
|
||||||
if (portduino_config.JSONFilter == (_meshtastic_PortNum)0 || portduino_config.JSONFilter == p->decoded.portnum) {
|
|
||||||
JSONFile << MeshPacketSerializer::JsonSerialize(p, false) << std::endl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
return DecodeState::DECODE_SUCCESS;
|
return DecodeState::DECODE_SUCCESS;
|
||||||
@@ -692,7 +688,7 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
|
|||||||
|
|
||||||
// Store a copy of encrypted packet for MQTT
|
// Store a copy of encrypted packet for MQTT
|
||||||
DEBUG_HEAP_BEFORE;
|
DEBUG_HEAP_BEFORE;
|
||||||
p_encrypted = packetPool.allocCopy(*p);
|
meshtastic_MeshPacket *p_encrypted = packetPool.allocCopy(*p);
|
||||||
DEBUG_HEAP_AFTER("Router::handleReceived", p_encrypted);
|
DEBUG_HEAP_AFTER("Router::handleReceived", p_encrypted);
|
||||||
|
|
||||||
// Take those raw bytes and convert them back into a well structured protobuf we can understand
|
// Take those raw bytes and convert them back into a well structured protobuf we can understand
|
||||||
@@ -758,7 +754,6 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
|
|||||||
}
|
}
|
||||||
|
|
||||||
packetPool.release(p_encrypted); // Release the encrypted packet
|
packetPool.release(p_encrypted); // Release the encrypted packet
|
||||||
p_encrypted = nullptr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Router::perhapsHandleReceived(meshtastic_MeshPacket *p)
|
void Router::perhapsHandleReceived(meshtastic_MeshPacket *p)
|
||||||
|
|||||||
@@ -91,9 +91,6 @@ class Router : protected concurrency::OSThread, protected PacketHistory
|
|||||||
before us */
|
before us */
|
||||||
uint32_t rxDupe = 0, txRelayCanceled = 0;
|
uint32_t rxDupe = 0, txRelayCanceled = 0;
|
||||||
|
|
||||||
// pointer to the encrypted packet
|
|
||||||
meshtastic_MeshPacket *p_encrypted = nullptr;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class RoutingModule;
|
friend class RoutingModule;
|
||||||
|
|
||||||
|
|||||||
@@ -293,8 +293,7 @@ typedef enum _meshtastic_Config_LoRaConfig_RegionCode {
|
|||||||
typedef enum _meshtastic_Config_LoRaConfig_ModemPreset {
|
typedef enum _meshtastic_Config_LoRaConfig_ModemPreset {
|
||||||
/* Long Range - Fast */
|
/* Long Range - Fast */
|
||||||
meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST = 0,
|
meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST = 0,
|
||||||
/* Long Range - Slow
|
/* Long Range - Slow */
|
||||||
Deprecated in 2.7: Unpopular slow preset. */
|
|
||||||
meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW = 1,
|
meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW = 1,
|
||||||
/* Very Long Range - Slow
|
/* Very Long Range - Slow
|
||||||
Deprecated in 2.5: Works only with txco and is unusably slow */
|
Deprecated in 2.5: Works only with txco and is unusably slow */
|
||||||
@@ -312,10 +311,7 @@ typedef enum _meshtastic_Config_LoRaConfig_ModemPreset {
|
|||||||
/* Short Range - Turbo
|
/* Short Range - Turbo
|
||||||
This is the fastest preset and the only one with 500kHz bandwidth.
|
This is the fastest preset and the only one with 500kHz bandwidth.
|
||||||
It is not legal to use in all regions due to this wider bandwidth. */
|
It is not legal to use in all regions due to this wider bandwidth. */
|
||||||
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO = 8,
|
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO = 8
|
||||||
/* Long Range - Turbo
|
|
||||||
This preset performs similarly to LongFast, but with 500Khz bandwidth. */
|
|
||||||
meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO = 9
|
|
||||||
} meshtastic_Config_LoRaConfig_ModemPreset;
|
} meshtastic_Config_LoRaConfig_ModemPreset;
|
||||||
|
|
||||||
typedef enum _meshtastic_Config_BluetoothConfig_PairingMode {
|
typedef enum _meshtastic_Config_BluetoothConfig_PairingMode {
|
||||||
@@ -693,8 +689,8 @@ extern "C" {
|
|||||||
#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_BR_902+1))
|
#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_BR_902+1))
|
||||||
|
|
||||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_MIN meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST
|
#define _meshtastic_Config_LoRaConfig_ModemPreset_MIN meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST
|
||||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO
|
#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO
|
||||||
#define _meshtastic_Config_LoRaConfig_ModemPreset_ARRAYSIZE ((meshtastic_Config_LoRaConfig_ModemPreset)(meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO+1))
|
#define _meshtastic_Config_LoRaConfig_ModemPreset_ARRAYSIZE ((meshtastic_Config_LoRaConfig_ModemPreset)(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO+1))
|
||||||
|
|
||||||
#define _meshtastic_Config_BluetoothConfig_PairingMode_MIN meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN
|
#define _meshtastic_Config_BluetoothConfig_PairingMode_MIN meshtastic_Config_BluetoothConfig_PairingMode_RANDOM_PIN
|
||||||
#define _meshtastic_Config_BluetoothConfig_PairingMode_MAX meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN
|
#define _meshtastic_Config_BluetoothConfig_PairingMode_MAX meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user