mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-28 13:40:32 +00:00
Compare commits
115 Commits
dontfavori
...
flat-noded
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e85cddef94 | ||
|
|
1bfa9ed4c4 | ||
|
|
b18794e98d | ||
|
|
f4e260e0f1 | ||
|
|
14463043bd | ||
|
|
376dc7ef3a | ||
|
|
c051c56544 | ||
|
|
d3976e7461 | ||
|
|
a4c92d9fd5 | ||
|
|
186cbe61bb | ||
|
|
0e3e8b7607 | ||
|
|
451e52b541 | ||
|
|
d743ba8e75 | ||
|
|
626dce8323 | ||
|
|
0100eeea67 | ||
|
|
5640179ce2 | ||
|
|
066da492d5 | ||
|
|
2b8806486d | ||
|
|
9ae545918c | ||
|
|
5374291c3c | ||
|
|
38b0c1588a | ||
|
|
f329de04c4 | ||
|
|
b09fa31492 | ||
|
|
a2a0150ee8 | ||
|
|
9ae92724a9 | ||
|
|
9cf369c5d0 | ||
|
|
441a7c5b20 | ||
|
|
2ca03fbf4b | ||
|
|
ef298814f2 | ||
|
|
8d31fc5ec6 | ||
|
|
f9433a31d1 | ||
|
|
7232dddd69 | ||
|
|
10de230dba | ||
|
|
d18f3f7a65 | ||
|
|
567b8ea1c2 | ||
|
|
d39d1917ad | ||
|
|
b202559d37 | ||
|
|
85ea22ac38 | ||
|
|
15257b017c | ||
|
|
59864dd09d | ||
|
|
edcdb2dcb2 | ||
|
|
6c09cf9d3d | ||
|
|
ef4cb2abfb | ||
|
|
c34f94abda | ||
|
|
a8d1a90e16 | ||
|
|
501c296e75 | ||
|
|
79a91578b7 | ||
|
|
ec5e79585b | ||
|
|
438e170b03 | ||
|
|
17cd83085b | ||
|
|
43e0c35466 | ||
|
|
b7bdcbe43e | ||
|
|
df063f40ff | ||
|
|
6e3be132f2 | ||
|
|
84bb1e33a6 | ||
|
|
955347bf50 | ||
|
|
4284fc2aec | ||
|
|
034aaa376a | ||
|
|
0aa11d810c | ||
|
|
4df6627ab1 | ||
|
|
7212fb9caa | ||
|
|
4118e1c0f6 | ||
|
|
a62fed3289 | ||
|
|
e9590003f4 | ||
|
|
7d2744fae0 | ||
|
|
beaebda4de | ||
|
|
36c2178570 | ||
|
|
1c0c6b2736 | ||
|
|
602945f66b | ||
|
|
50f9be9a2b | ||
|
|
b86827967e | ||
|
|
8fe98db5dd | ||
|
|
531cad5e88 | ||
|
|
b707001873 | ||
|
|
85afd706fd | ||
|
|
e76013fb60 | ||
|
|
b25797e1b3 | ||
|
|
bdb3fb1477 | ||
|
|
7eca061f01 | ||
|
|
77e0a24838 | ||
|
|
6cad393688 | ||
|
|
0725b46744 | ||
|
|
4d86bbafe6 | ||
|
|
112b294ef6 | ||
|
|
5ba04ade2d | ||
|
|
6a6c409b9a | ||
|
|
69db3bd11c | ||
|
|
7b14b173d9 | ||
|
|
45bf2468a9 | ||
|
|
ce2e08e0d8 | ||
|
|
3e40d7896b | ||
|
|
03f69b3b77 | ||
|
|
83954293d8 | ||
|
|
91621427f1 | ||
|
|
8d1b9c9dce | ||
|
|
538c05ad6c | ||
|
|
f6370bea8f | ||
|
|
468247fb94 | ||
|
|
3ae7e54681 | ||
|
|
0a124b7f3d | ||
|
|
7def82595d | ||
|
|
3a67204f6d | ||
|
|
d1b66782d1 | ||
|
|
718fd118b0 | ||
|
|
75f7ded12c | ||
|
|
7f78a624cd | ||
|
|
17324fa725 | ||
|
|
001654e90a | ||
|
|
4f817d69eb | ||
|
|
de83b448f9 | ||
|
|
c145be8e05 | ||
|
|
28f53d132a | ||
|
|
f045ca2303 | ||
|
|
b6830a68a0 | ||
|
|
13c4c2037d |
@@ -1,7 +1,7 @@
|
||||
# trunk-ignore-all(terrascan/AC_DOCKER_0002): Known terrascan issue
|
||||
# trunk-ignore-all(hadolint/DL3008): Do not pin apt package versions
|
||||
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
|
||||
FROM mcr.microsoft.com/devcontainers/cpp:2-debian-12
|
||||
FROM mcr.microsoft.com/devcontainers/cpp:2-debian-13
|
||||
|
||||
USER root
|
||||
|
||||
|
||||
2
.github/actions/setup-base/action.yml
vendored
2
.github/actions/setup-base/action.yml
vendored
@@ -5,7 +5,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
|
||||
2
.github/workflows/build_debian_src.yml
vendored
2
.github/workflows/build_debian_src.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
path: meshtasticd
|
||||
|
||||
2
.github/workflows/build_firmware.yml
vendored
2
.github/workflows/build_firmware.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
outputs:
|
||||
artifact-id: ${{ steps.upload.outputs.artifact-id }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
|
||||
6
.github/workflows/build_one_arch.yml
vendored
6
.github/workflows/build_one_arch.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
setup:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.x
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Get release version string
|
||||
run: |
|
||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
needs: [version, build]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
6
.github/workflows/build_one_target.yml
vendored
6
.github/workflows/build_one_target.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.x
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
if: ${{ inputs.target != '' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Get release version string
|
||||
run: |
|
||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
@@ -114,7 +114,7 @@ jobs:
|
||||
needs: [version, build]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
2
.github/workflows/docker_build.yml
vendored
2
.github/workflows/docker_build.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
runs-on: ${{ inputs.runs-on }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
|
||||
2
.github/workflows/docker_manifest.yml
vendored
2
.github/workflows/docker_manifest.yml
vendored
@@ -83,7 +83,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
|
||||
2
.github/workflows/hook_copr.yml
vendored
2
.github/workflows/hook_copr.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{ github.ref }}
|
||||
|
||||
14
.github/workflows/main_matrix.yml
vendored
14
.github/workflows/main_matrix.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
- check
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.x
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Get release version string
|
||||
run: |
|
||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Build base
|
||||
id: base
|
||||
uses: ./.github/actions/setup-base
|
||||
@@ -163,7 +163,7 @@ jobs:
|
||||
needs: [version, build]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
@@ -242,7 +242,7 @@ jobs:
|
||||
- package-pio-deps-native-tft
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
@@ -311,7 +311,7 @@ jobs:
|
||||
needs: [release-artifacts, version]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
@@ -366,7 +366,7 @@ jobs:
|
||||
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
|
||||
14
.github/workflows/merge_queue.yml
vendored
14
.github/workflows/merge_queue.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
- check
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: 3.x
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Get release version string
|
||||
run: |
|
||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Build base
|
||||
id: base
|
||||
uses: ./.github/actions/setup-base
|
||||
@@ -142,7 +142,7 @@ jobs:
|
||||
needs: [version, build]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
@@ -221,7 +221,7 @@ jobs:
|
||||
- package-pio-deps-native-tft
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
@@ -290,7 +290,7 @@ jobs:
|
||||
needs: [release-artifacts, version]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
@@ -345,7 +345,7 @@ jobs:
|
||||
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
|
||||
4
.github/workflows/nightly.yml
vendored
4
.github/workflows/nightly.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Trunk Check
|
||||
uses: trunk-io/trunk-action@v1
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
pull-requests: write # For trunk to create PRs
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Trunk Upgrade
|
||||
uses: trunk-io/trunk-action/upgrade@v1
|
||||
|
||||
2
.github/workflows/package_obs.yml
vendored
2
.github/workflows/package_obs.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
needs: build-debian-src
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
path: meshtasticd
|
||||
|
||||
2
.github/workflows/package_pio_deps.yml
vendored
2
.github/workflows/package_pio_deps.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
|
||||
2
.github/workflows/package_ppa.yml
vendored
2
.github/workflows/package_ppa.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
needs: build-debian-src
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
path: meshtasticd
|
||||
|
||||
2
.github/workflows/pr_tests.yml
vendored
2
.github/workflows/pr_tests.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
checks: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
|
||||
5
.github/workflows/release_channels.yml
vendored
5
.github/workflows/release_channels.yml
vendored
@@ -60,7 +60,10 @@ jobs:
|
||||
shell: bash
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
# Always use master branch for version bumps
|
||||
ref: master
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v6
|
||||
|
||||
2
.github/workflows/sec_sast_semgrep_cron.yml
vendored
2
.github/workflows/sec_sast_semgrep_cron.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
steps:
|
||||
# step 1
|
||||
- name: clone application source code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# step 2
|
||||
- name: full scan
|
||||
|
||||
2
.github/workflows/sec_sast_semgrep_pull.yml
vendored
2
.github/workflows/sec_sast_semgrep_pull.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
steps:
|
||||
# step 1
|
||||
- name: clone application source code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
8
.github/workflows/test_native.yml
vendored
8
.github/workflows/test_native.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
name: Native Simulator Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
name: Native PlatformIO Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
- platformio-tests
|
||||
if: always()
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
@@ -143,7 +143,7 @@ jobs:
|
||||
merge-multiple: true
|
||||
|
||||
- name: Test Report
|
||||
uses: dorny/test-reporter@v2.1.1
|
||||
uses: dorny/test-reporter@v2.2.0
|
||||
with:
|
||||
name: PlatformIO Tests
|
||||
path: testreport.xml
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
runs-on: test-runner
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
# - uses: actions/setup-python@v5
|
||||
# with:
|
||||
|
||||
2
.github/workflows/trunk_annotate_pr.yml
vendored
2
.github/workflows/trunk_annotate_pr.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Trunk Check
|
||||
uses: trunk-io/trunk-action@v1
|
||||
|
||||
2
.github/workflows/trunk_check.yml
vendored
2
.github/workflows/trunk_check.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Trunk Check
|
||||
uses: trunk-io/trunk-action@v1
|
||||
|
||||
2
.github/workflows/trunk_format_pr.yml
vendored
2
.github/workflows/trunk_format_pr.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
2
.github/workflows/update_protobufs.yml
vendored
2
.github/workflows/update_protobufs.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
|
||||
@@ -8,15 +8,15 @@ plugins:
|
||||
uri: https://github.com/trunk-io/plugins
|
||||
lint:
|
||||
enabled:
|
||||
- checkov@3.2.489
|
||||
- renovate@41.169.1
|
||||
- checkov@3.2.492
|
||||
- renovate@42.5.4
|
||||
- prettier@3.6.2
|
||||
- trufflehog@3.90.12
|
||||
- trufflehog@3.90.13
|
||||
- yamllint@1.37.1
|
||||
- bandit@1.8.6
|
||||
- trivy@0.67.2
|
||||
- taplo@0.10.0
|
||||
- ruff@0.14.3
|
||||
- ruff@0.14.4
|
||||
- isort@7.0.0
|
||||
- markdownlint@0.45.0
|
||||
- oxipng@9.1.5
|
||||
@@ -26,9 +26,9 @@ lint:
|
||||
- hadolint@2.14.0
|
||||
- shfmt@3.6.0
|
||||
- shellcheck@0.11.0
|
||||
- black@25.9.0
|
||||
- black@25.11.0
|
||||
- git-diff-check
|
||||
- gitleaks@8.28.0
|
||||
- gitleaks@8.29.0
|
||||
- clang-format@16.0.3
|
||||
ignore:
|
||||
- linters: [ALL]
|
||||
|
||||
@@ -37,4 +37,3 @@ Join our community and help improve Meshtastic! 🚀
|
||||
## Stats
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ ARG PIO_ENV=native
|
||||
ENV PIP_ROOT_USER_ACTION=ignore
|
||||
|
||||
RUN apk --no-cache add \
|
||||
bash g++ libstdc++-dev linux-headers zip git ca-certificates libgpiod-dev yaml-cpp-dev bluez-dev \
|
||||
bash g++ libstdc++-dev linux-headers zip git ca-certificates libbsd-dev \
|
||||
libgpiod-dev yaml-cpp-dev bluez-dev \
|
||||
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
|
||||
libx11-dev libinput-dev libxkbcommon-dev \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
@@ -40,8 +41,8 @@ LABEL org.opencontainers.image.title="Meshtastic" \
|
||||
USER root
|
||||
|
||||
RUN apk --no-cache add \
|
||||
shadow libstdc++ libgpiod yaml-cpp libusb i2c-tools libuv \
|
||||
libx11 libinput libxkbcommon \
|
||||
shadow libstdc++ libbsd libgpiod yaml-cpp libusb \
|
||||
i2c-tools libuv libx11 libinput libxkbcommon \
|
||||
&& rm -rf /var/cache/apk/* \
|
||||
&& mkdir -p /var/lib/meshtasticd \
|
||||
&& mkdir -p /etc/meshtasticd/config.d \
|
||||
|
||||
@@ -7,7 +7,7 @@ extends = arduino_base
|
||||
platform_packages =
|
||||
; our custom Git version until they merge our PR
|
||||
# TODO renovate
|
||||
platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#e13f5820002a4fb2a5e6754b42ace185277e5adf
|
||||
platformio/framework-arduinoadafruitnrf52 @ https://github.com/meshtastic/Adafruit_nRF52_Arduino#c770c8a16a351b55b86e347a3d9d7b74ad0bbf39
|
||||
; Don't renovate toolchain-gccarmnoneeabi
|
||||
platformio/toolchain-gccarmnoneeabi@~1.90301.0
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ lib_deps =
|
||||
${environmental_base.lib_deps}
|
||||
${environmental_extra.lib_deps}
|
||||
# renovate: datasource=git-refs depName=Kongduino-Adafruit_nRFCrypto packageName=https://github.com/Kongduino/Adafruit_nRFCrypto gitBranch=master
|
||||
https://github.com/Kongduino/Adafruit_nRFCrypto/archive/5f838d2709461a2c981f642917aa50254a25c14c.zip
|
||||
https://github.com/Kongduino/Adafruit_nRFCrypto/archive/8cde7189b5ead9dcd49f72601b43b969c0bbc06e.zip
|
||||
|
||||
; Common NRF52 debugging settings follow. See the Meshtastic developer docs for how to connect SWD debugging probes to your board.
|
||||
|
||||
|
||||
@@ -37,6 +37,9 @@ build_flags =
|
||||
-DRADIOLIB_EXCLUDE_LR11X0=1
|
||||
-DHAL_DAC_MODULE_ONLY
|
||||
-DHAL_RNG_MODULE_ENABLED
|
||||
-Wl,--wrap=__assert_func
|
||||
-Wl,--wrap=strerror
|
||||
-Wl,--wrap=_tzset_unlocked_r
|
||||
|
||||
build_src_filter =
|
||||
${arduino_base.build_src_filter} -<platform/esp32/> -<nimble/> -<mesh/api/> -<mesh/wifi/> -<mesh/http/> -<modules/esp32> -<mesh/eth/> -<input> -<buzz> -<modules/RemoteHardwareModule.cpp> -<platform/nrf52> -<platform/portduino> -<platform/rp2xx0> -<mesh/raspihttp>
|
||||
|
||||
@@ -87,6 +87,15 @@
|
||||
</screenshots>
|
||||
|
||||
<releases>
|
||||
<release version="2.7.16" date="2025-11-19">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.16</url>
|
||||
</release>
|
||||
<release version="2.7.15" date="2025-11-13">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.15</url>
|
||||
</release>
|
||||
<release version="2.7.14" date="2025-11-03">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.14</url>
|
||||
</release>
|
||||
<release version="2.7.13" date="2025-10-11">
|
||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.13</url>
|
||||
</release>
|
||||
|
||||
53
boards/ThinkNode-M6.json
Normal file
53
boards/ThinkNode-M6.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_ELECROW_M6 -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x4405"],
|
||||
["0x239A", "0x0029"],
|
||||
["0x239A", "0x002A"]
|
||||
],
|
||||
"usb_product": "elecrow_thinknode_m6",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "ELECROW-ThinkNode-M6",
|
||||
"variants_dir": "variants",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": ["bluetooth"],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"onboard_tools": ["jlink"],
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52840-mdk-rs"
|
||||
},
|
||||
"frameworks": ["arduino"],
|
||||
"name": "ELECROW ThinkNode M6",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://www.elecrow.com/thinknode-m6-outdoor-solar-power-for-lora-powered-by-nrf52840-supports-gps.html",
|
||||
"vendor": "ELECROW"
|
||||
}
|
||||
19
debian/changelog
vendored
19
debian/changelog
vendored
@@ -1,3 +1,22 @@
|
||||
meshtasticd (2.7.16.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.16
|
||||
|
||||
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 19 Nov 2025 16:12:32 +0000
|
||||
|
||||
|
||||
meshtasticd (2.7.15.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.15
|
||||
|
||||
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Thu, 13 Nov 2025 12:31:57 +0000
|
||||
|
||||
meshtasticd (2.7.14.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.14
|
||||
|
||||
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Mon, 03 Nov 2025 16:11:31 +0000
|
||||
|
||||
meshtasticd (2.7.13.0) unstable; urgency=medium
|
||||
|
||||
* Version 2.7.13
|
||||
|
||||
1
debian/control
vendored
1
debian/control
vendored
@@ -3,6 +3,7 @@ Section: misc
|
||||
Priority: optional
|
||||
Maintainer: Austin Lane <vidplace7@gmail.com>
|
||||
Build-Depends: debhelper-compat (= 13),
|
||||
libc6-dev (>= 2.38) | libbsd-dev,
|
||||
lsb-release,
|
||||
tar,
|
||||
gzip,
|
||||
|
||||
@@ -33,6 +33,7 @@ BuildRequires: python3dist(grpcio[protobuf])
|
||||
BuildRequires: python3dist(grpcio-tools)
|
||||
BuildRequires: git-core
|
||||
BuildRequires: gcc-c++
|
||||
BuildRequires: (glibc-devel >= 2.38) or pkgconfig(libbsd-overlay)
|
||||
BuildRequires: pkgconfig(yaml-cpp)
|
||||
BuildRequires: pkgconfig(libgpiod)
|
||||
BuildRequires: pkgconfig(bluez)
|
||||
@@ -49,6 +50,13 @@ BuildRequires: pkgconfig(x11)
|
||||
BuildRequires: pkgconfig(libinput)
|
||||
BuildRequires: pkgconfig(xkbcommon-x11)
|
||||
|
||||
# libbsd is needed on older Fedora/RHEL to provide 'strlcpy'
|
||||
%if 0%{?fedora} >= 39 || 0%{?rhel} >= 10
|
||||
BuildRequires: glibc-devel >= 2.38
|
||||
%else
|
||||
BuildRequires: pkgconfig(libbsd-overlay)
|
||||
%endif
|
||||
|
||||
Requires: systemd-udev
|
||||
|
||||
%description
|
||||
|
||||
@@ -62,7 +62,7 @@ monitor_speed = 115200
|
||||
monitor_filters = direct
|
||||
lib_deps =
|
||||
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
|
||||
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/0cbc26b1f8f61957af0475f486b362eafe7cc4e2.zip
|
||||
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/2887bf4a19f64d92c984dcc8fd5ca7429e425e4a.zip
|
||||
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
|
||||
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
|
||||
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
|
||||
@@ -115,12 +115,13 @@ lib_deps =
|
||||
[radiolib_base]
|
||||
lib_deps =
|
||||
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
||||
jgromes/RadioLib@7.3.0
|
||||
# jgromes/RadioLib@7.4.0
|
||||
https://github.com/jgromes/RadioLib/archive/536c7267362e2c1345be7054ba45e503252975ff.zip
|
||||
|
||||
[device-ui_base]
|
||||
lib_deps =
|
||||
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
||||
https://github.com/meshtastic/device-ui/archive/19b7855e9a1d9deff37391659ca7194e4ef57c43.zip
|
||||
https://github.com/meshtastic/device-ui/archive/28167c67dfd13015a0b5eef1828f95fe8e3ab7c3.zip
|
||||
|
||||
; Common libs for environmental measurements in telemetry module
|
||||
[environmental_base]
|
||||
@@ -181,6 +182,8 @@ lib_deps =
|
||||
dfrobot/DFRobot_BMM150@1.0.0
|
||||
# renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561
|
||||
adafruit/Adafruit TSL2561@1.1.2
|
||||
# renovate: datasource=custom.pio depName=BH1750_WE packageName=wollewald/BH1750_WE@^1.1.10
|
||||
wollewald/BH1750_WE@^1.1.10
|
||||
|
||||
; (not included in native / portduino)
|
||||
[environmental_extra]
|
||||
|
||||
Submodule protobufs updated: bf149bbdcc...52fa252f1e
@@ -53,8 +53,8 @@ class GPSStatus : public Status
|
||||
int32_t getLatitude() const
|
||||
{
|
||||
if (config.position.fixed_position) {
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
return node->position.latitude_i;
|
||||
meshtastic_NodeDetail *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
return node ? node->latitude_i : 0;
|
||||
} else {
|
||||
return p.latitude_i;
|
||||
}
|
||||
@@ -63,8 +63,8 @@ class GPSStatus : public Status
|
||||
int32_t getLongitude() const
|
||||
{
|
||||
if (config.position.fixed_position) {
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
return node->position.longitude_i;
|
||||
meshtastic_NodeDetail *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
return node ? node->longitude_i : 0;
|
||||
} else {
|
||||
return p.longitude_i;
|
||||
}
|
||||
@@ -73,8 +73,8 @@ class GPSStatus : public Status
|
||||
int32_t getAltitude() const
|
||||
{
|
||||
if (config.position.fixed_position) {
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
return node->position.altitude;
|
||||
meshtastic_NodeDetail *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
return node ? node->altitude : 0;
|
||||
} else {
|
||||
return p.altitude;
|
||||
}
|
||||
|
||||
@@ -14,16 +14,16 @@ class NodeStatus : public Status
|
||||
CallbackObserver<NodeStatus, const NodeStatus *> statusObserver =
|
||||
CallbackObserver<NodeStatus, const NodeStatus *>(this, &NodeStatus::updateStatus);
|
||||
|
||||
uint8_t numOnline = 0;
|
||||
uint8_t numTotal = 0;
|
||||
uint16_t numOnline = 0;
|
||||
uint16_t numTotal = 0;
|
||||
|
||||
uint8_t lastNumTotal = 0;
|
||||
uint16_t lastNumTotal = 0;
|
||||
|
||||
public:
|
||||
bool forceUpdate = false;
|
||||
|
||||
NodeStatus() { statusType = STATUS_TYPE_NODE; }
|
||||
NodeStatus(uint8_t numOnline, uint8_t numTotal, bool forceUpdate = false) : Status()
|
||||
NodeStatus(uint16_t numOnline, uint16_t numTotal, bool forceUpdate = false) : Status()
|
||||
{
|
||||
this->forceUpdate = forceUpdate;
|
||||
this->numOnline = numOnline;
|
||||
@@ -34,11 +34,11 @@ class NodeStatus : public Status
|
||||
|
||||
void observe(Observable<const NodeStatus *> *source) { statusObserver.observe(source); }
|
||||
|
||||
uint8_t getNumOnline() const { return numOnline; }
|
||||
uint16_t getNumOnline() const { return numOnline; }
|
||||
|
||||
uint8_t getNumTotal() const { return numTotal; }
|
||||
uint16_t getNumTotal() const { return numTotal; }
|
||||
|
||||
uint8_t getLastNumTotal() const { return lastNumTotal; }
|
||||
uint16_t getLastNumTotal() const { return lastNumTotal; }
|
||||
|
||||
bool matches(const NodeStatus *newStatus) const
|
||||
{
|
||||
@@ -56,7 +56,7 @@ class NodeStatus : public Status
|
||||
numTotal = newStatus->getNumTotal();
|
||||
}
|
||||
if (isDirty || newStatus->forceUpdate) {
|
||||
LOG_DEBUG("Node status update: %d online, %d total", numOnline, numTotal);
|
||||
LOG_DEBUG("Node status update: %u online, %u total", numOnline, numTotal);
|
||||
onNewStatus.notifyObservers(this);
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -194,7 +194,7 @@ static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level se
|
||||
|
||||
#ifdef BATTERY_PIN
|
||||
|
||||
static void adcEnable()
|
||||
void battery_adcEnable()
|
||||
{
|
||||
#ifdef ADC_CTRL // enable adc voltage divider when we need to read
|
||||
#ifdef ADC_USE_PULLUP
|
||||
@@ -214,7 +214,7 @@ static void adcEnable()
|
||||
#endif
|
||||
}
|
||||
|
||||
static void adcDisable()
|
||||
static void battery_adcDisable()
|
||||
{
|
||||
#ifdef ADC_CTRL // disable adc voltage divider when we need to read
|
||||
#ifdef ADC_USE_PULLUP
|
||||
@@ -320,7 +320,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
uint32_t raw = 0;
|
||||
float scaled = 0;
|
||||
|
||||
adcEnable();
|
||||
battery_adcEnable();
|
||||
#ifdef ARCH_ESP32 // ADC block for espressif platforms
|
||||
raw = espAdcRead();
|
||||
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
|
||||
@@ -332,7 +332,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
raw = raw / BATTERY_SENSE_SAMPLES;
|
||||
scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw;
|
||||
#endif
|
||||
adcDisable();
|
||||
battery_adcDisable();
|
||||
|
||||
if (!initial_read_done) {
|
||||
// Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
|
||||
@@ -906,13 +906,8 @@ void Power::readPowerStatus()
|
||||
low_voltage_counter++;
|
||||
LOG_DEBUG("Low voltage counter: %d/10", low_voltage_counter);
|
||||
if (low_voltage_counter > 10) {
|
||||
#ifdef ARCH_NRF52
|
||||
// We can't trigger deep sleep on NRF52, it's freezing the board
|
||||
LOG_DEBUG("Low voltage detected, but not trigger deep sleep");
|
||||
#else
|
||||
LOG_INFO("Low voltage detected, trigger deep sleep");
|
||||
powerFSM.trigger(EVENT_LOW_BATTERY);
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
low_voltage_counter = 0;
|
||||
@@ -1552,4 +1547,4 @@ bool Power::meshSolarInit()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -50,6 +50,7 @@ void consolePrintf(const char *format, ...)
|
||||
|
||||
SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), concurrency::OSThread("SerialConsole")
|
||||
{
|
||||
api_type = TYPE_SERIAL;
|
||||
assert(!console);
|
||||
console = this;
|
||||
canWrite = false; // We don't send packets to our port until it has talked to us first
|
||||
|
||||
@@ -228,6 +228,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define ICM20948_ADDR_ALT 0x68
|
||||
#define BHI260AP_ADDR 0x28
|
||||
#define BMM150_ADDR 0x13
|
||||
#define DA217_ADDR 0x26
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// LED
|
||||
@@ -250,6 +251,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
// -----------------------------------------------------------------------------
|
||||
#define FT6336U_ADDR 0x48
|
||||
#define CST328_ADDR 0x1A
|
||||
#define CHSC6X_ADDR 0x2E
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected)
|
||||
|
||||
@@ -82,7 +82,10 @@ class ScanI2C
|
||||
BHI260AP,
|
||||
BMM150,
|
||||
TSL2561,
|
||||
DRV2605
|
||||
DRV2605,
|
||||
BH1750,
|
||||
DA217,
|
||||
CHSC6X
|
||||
} DeviceType;
|
||||
|
||||
// typedef uint8_t DeviceAddress;
|
||||
|
||||
@@ -106,6 +106,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation
|
||||
if (i2cBus->available())
|
||||
i2cBus->read();
|
||||
}
|
||||
LOG_DEBUG("Register value: 0x%x", value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -377,14 +378,13 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
}
|
||||
case SHT31_4x_ADDR: // same as OPT3001_ADDR_ALT
|
||||
case SHT31_4x_ADDR_ALT: // same as OPT3001_ADDR
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
|
||||
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0x11f3 || registerValue == 0xe9c ||
|
||||
registerValue == 0xc8d) {
|
||||
type = SHT4X;
|
||||
logFoundDevice("SHT4X", (uint8_t)addr.address);
|
||||
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2);
|
||||
if (registerValue == 0x5449) {
|
||||
type = OPT3001;
|
||||
logFoundDevice("OPT3001", (uint8_t)addr.address);
|
||||
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2) != 0) { // unique SHT4x serial number
|
||||
type = SHT4X;
|
||||
logFoundDevice("SHT4X", (uint8_t)addr.address);
|
||||
} else {
|
||||
type = SHT31;
|
||||
logFoundDevice("SHT31", (uint8_t)addr.address);
|
||||
@@ -465,8 +465,23 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
break;
|
||||
|
||||
SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700", (uint8_t)addr.address);
|
||||
case TCA9555_ADDR:
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x01), 1);
|
||||
if (registerValue == 0x13) {
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
|
||||
if (registerValue == 0x81) {
|
||||
type = DA217;
|
||||
logFoundDevice("DA217", (uint8_t)addr.address);
|
||||
} else {
|
||||
type = TCA9555;
|
||||
logFoundDevice("TCA9555", (uint8_t)addr.address);
|
||||
}
|
||||
} else {
|
||||
type = TCA9555;
|
||||
logFoundDevice("TCA9555", (uint8_t)addr.address);
|
||||
}
|
||||
break;
|
||||
case TSL25911_ADDR:
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x12), 1);
|
||||
if (registerValue == 0x50) {
|
||||
@@ -485,7 +500,26 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
SCAN_SIMPLE_CASE(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(CST328_ADDR, CST328, "CST328", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(LTR553ALS_ADDR, LTR553ALS, "LTR553ALS", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(CHSC6X_ADDR, CHSC6X, "CHSC6X", (uint8_t)addr.address);
|
||||
case LTR553ALS_ADDR:
|
||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x86), 1); // Part ID register
|
||||
if (registerValue == 0x92) { // LTR553ALS Part ID
|
||||
type = LTR553ALS;
|
||||
logFoundDevice("LTR553ALS", (uint8_t)addr.address);
|
||||
} else {
|
||||
// Test BH1750 - send power on command
|
||||
i2cBus->beginTransmission(addr.address);
|
||||
i2cBus->write(0x01); // Power On command
|
||||
uint8_t bh1750_error = i2cBus->endTransmission();
|
||||
if (bh1750_error == 0) {
|
||||
type = BH1750;
|
||||
logFoundDevice("BH1750", (uint8_t)addr.address);
|
||||
} else {
|
||||
LOG_INFO("Device found at address 0x%x was not able to be enumerated", (uint8_t)addr.address);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
SCAN_SIMPLE_CASE(BHI260AP_ADDR, BHI260AP, "BHI260AP", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(SCD4X_ADDR, SCD4X, "SCD4X", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(BMM150_ADDR, BMM150, "BMM150", (uint8_t)addr.address);
|
||||
|
||||
@@ -240,6 +240,9 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
|
||||
buffer[bytesRead] = b;
|
||||
bytesRead++;
|
||||
if ((bytesRead == 767) || (b == '\r')) {
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG(debugmsg.c_str());
|
||||
#endif
|
||||
if (strnstr((char *)buffer, message, bytesRead) != nullptr) {
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG("Found: %s", message); // Log the found message
|
||||
@@ -247,9 +250,6 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
|
||||
return GNSS_RESPONSE_OK;
|
||||
} else {
|
||||
bytesRead = 0;
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG(debugmsg.c_str());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1275,6 +1275,24 @@ GnssModel_t GPS::probe(int serialSpeed)
|
||||
memset(&ublox_info, 0, sizeof(ublox_info));
|
||||
delay(100);
|
||||
|
||||
#if defined(PIN_GPS_RESET) && PIN_GPS_RESET != -1
|
||||
digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms
|
||||
delay(10);
|
||||
digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
|
||||
|
||||
// attempt to detect the chip based on boot messages
|
||||
std::vector<ChipInfo> passive_detect = {
|
||||
{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
|
||||
{"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
|
||||
{"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352},
|
||||
{"UC6580", "UC6580", GNSS_MODEL_UC6580},
|
||||
// as L76K is sort of a last ditch effort, we won't attempt to detect it by startup messages for now.
|
||||
/*{"L76K", "SW=URANUS", GNSS_MODEL_MTK}*/};
|
||||
GnssModel_t detectedDriver = getProbeResponse(500, passive_detect, serialSpeed);
|
||||
if (detectedDriver != GNSS_MODEL_UNKNOWN) {
|
||||
return detectedDriver;
|
||||
}
|
||||
#endif
|
||||
// Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
|
||||
_serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
|
||||
delay(20);
|
||||
@@ -1473,12 +1491,12 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipI
|
||||
}
|
||||
|
||||
if (c == ',' || (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n')) {
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG(response);
|
||||
#endif
|
||||
// check if we can see our chips
|
||||
for (const auto &chipInfo : responseMap) {
|
||||
if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG(response);
|
||||
#endif
|
||||
LOG_INFO("%s detected", chipInfo.chipName.c_str());
|
||||
delete[] response; // Cleanup before return
|
||||
return chipInfo.driver;
|
||||
@@ -1486,6 +1504,9 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipI
|
||||
}
|
||||
}
|
||||
if (responseLen >= 2 && response[responseLen - 2] == '\r' && response[responseLen - 1] == '\n') {
|
||||
#ifdef GPS_DEBUG
|
||||
LOG_DEBUG(response);
|
||||
#endif
|
||||
// Reset the response buffer for the next potential message
|
||||
responseLen = 0;
|
||||
response[0] = '\0';
|
||||
@@ -1572,8 +1593,6 @@ GPS *GPS::createGps()
|
||||
|
||||
#ifdef PIN_GPS_RESET
|
||||
pinMode(PIN_GPS_RESET, OUTPUT);
|
||||
digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms
|
||||
delay(10);
|
||||
digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -310,7 +310,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
|
||||
#ifdef BUILD_EPOCH
|
||||
if (tv.tv_sec < BUILD_EPOCH) {
|
||||
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
|
||||
LOG_WARN("Ignore time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
|
||||
LOG_WARN("Ignore time (%lu) before build epoch (%lu)!", printableEpoch, BUILD_EPOCH);
|
||||
lastTimeValidationWarning = millis();
|
||||
}
|
||||
return RTCSetResultInvalidTime;
|
||||
@@ -319,7 +319,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
|
||||
// Calculate max allowed time safely to avoid overflow in logging
|
||||
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
|
||||
uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
|
||||
LOG_WARN("Ignore time (%ld) too far in the future (build epoch: %ld, max allowed: %ld)!", printableEpoch,
|
||||
LOG_WARN("Ignore time (%lu) too far in the future (build epoch: %lu, max allowed: %lu)!", printableEpoch,
|
||||
(uint32_t)BUILD_EPOCH, maxAllowedPrintable);
|
||||
lastTimeValidationWarning = millis();
|
||||
}
|
||||
|
||||
@@ -1106,8 +1106,8 @@ void Screen::setFrames(FrameFocus focus)
|
||||
std::vector<FrameCallback> favoriteFrames;
|
||||
|
||||
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
|
||||
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
|
||||
const meshtastic_NodeDetail *nodeDetail = nodeDB->getMeshNodeByIndex(i);
|
||||
if (nodeDetail && nodeDetail->num != nodeDB->getNodeNum() && detailIsFavorite(*nodeDetail)) {
|
||||
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
|
||||
}
|
||||
}
|
||||
@@ -1460,10 +1460,10 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
||||
forceDisplay(); // Forces screen redraw
|
||||
}
|
||||
// === Prepare banner content ===
|
||||
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
|
||||
const meshtastic_NodeDetail *node = nodeDB->getMeshNode(packet->from);
|
||||
const meshtastic_Channel channel =
|
||||
channels.getByIndex(packet->channel ? packet->channel : channels.getPrimaryIndex());
|
||||
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
|
||||
const char *longName = (node && detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER)) ? node->long_name : nullptr;
|
||||
|
||||
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "configuration.h"
|
||||
#if HAS_SCREEN
|
||||
#include "MeshService.h"
|
||||
#include "RTC.h"
|
||||
#include "draw/NodeListRenderer.h"
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
#include "graphics/draw/UIRenderer.h"
|
||||
@@ -398,6 +400,43 @@ const int *getTextPositions(OLEDDisplay *display)
|
||||
return textPositions;
|
||||
}
|
||||
|
||||
// *************************
|
||||
// * Common Footer Drawing *
|
||||
// *************************
|
||||
void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y)
|
||||
{
|
||||
bool drawConnectionState = false;
|
||||
if (service->api_state == service->STATE_BLE || service->api_state == service->STATE_WIFI ||
|
||||
service->api_state == service->STATE_SERIAL || service->api_state == service->STATE_PACKET ||
|
||||
service->api_state == service->STATE_HTTP || service->api_state == service->STATE_ETH) {
|
||||
drawConnectionState = true;
|
||||
}
|
||||
|
||||
if (drawConnectionState) {
|
||||
if (isHighResolution) {
|
||||
const int scale = 2;
|
||||
const int bytesPerRow = (connection_icon_width + 7) / 8;
|
||||
int iconX = 0;
|
||||
int iconY = SCREEN_HEIGHT - (connection_icon_height * 2);
|
||||
|
||||
for (int yy = 0; yy < connection_icon_height; ++yy) {
|
||||
const uint8_t *rowPtr = connection_icon + yy * bytesPerRow;
|
||||
for (int xx = 0; xx < connection_icon_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 {
|
||||
display->drawXbm(0, SCREEN_HEIGHT - connection_icon_height, connection_icon_width, connection_icon_height,
|
||||
connection_icon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isAllowedPunctuation(char c)
|
||||
{
|
||||
const std::string allowed = ".,!?;:-_()[]{}'\"@#$/\\&+=%~^ ";
|
||||
|
||||
@@ -52,6 +52,9 @@ void drawRoundedHighlight(OLEDDisplay *display, int16_t x, int16_t y, int16_t w,
|
||||
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool force_no_invert = false,
|
||||
bool show_date = false);
|
||||
|
||||
// Shared battery/time/mail header
|
||||
void drawCommonFooter(OLEDDisplay *display, int16_t x, int16_t y);
|
||||
|
||||
const int *getTextPositions(OLEDDisplay *display);
|
||||
|
||||
bool isAllowedPunctuation(char c);
|
||||
|
||||
@@ -422,7 +422,57 @@ static LGFX *tft = nullptr;
|
||||
|
||||
#elif defined(ST7789_CS)
|
||||
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
||||
#ifdef HELTEC_V4_TFT
|
||||
#include "chsc6x.h"
|
||||
#include "lgfx/v1/Touch.hpp"
|
||||
namespace lgfx
|
||||
{
|
||||
inline namespace v1
|
||||
{
|
||||
class TOUCH_CHSC6X : public ITouch
|
||||
{
|
||||
public:
|
||||
TOUCH_CHSC6X(void)
|
||||
{
|
||||
_cfg.i2c_addr = TOUCH_SLAVE_ADDRESS;
|
||||
_cfg.x_min = 0;
|
||||
_cfg.x_max = 240;
|
||||
_cfg.y_min = 0;
|
||||
_cfg.y_max = 320;
|
||||
};
|
||||
|
||||
bool init(void) override
|
||||
{
|
||||
if (chsc6xTouch == nullptr) {
|
||||
chsc6xTouch = new chsc6x(&Wire1, TOUCH_SDA_PIN, TOUCH_SCL_PIN, TOUCH_INT_PIN, TOUCH_RST_PIN);
|
||||
}
|
||||
chsc6xTouch->chsc6x_init();
|
||||
return true;
|
||||
};
|
||||
|
||||
uint_fast8_t getTouchRaw(touch_point_t *tp, uint_fast8_t count) override
|
||||
{
|
||||
uint16_t raw_x, raw_y;
|
||||
if (chsc6xTouch->chsc6x_read_touch_info(&raw_x, &raw_y) == 0) {
|
||||
tp[0].x = 320 - 1 - raw_y;
|
||||
tp[0].y = 240 - 1 - raw_x;
|
||||
tp[0].size = 1;
|
||||
tp[0].id = 1;
|
||||
return 1;
|
||||
}
|
||||
tp[0].size = 0;
|
||||
return 0;
|
||||
};
|
||||
|
||||
void wakeup(void) override{};
|
||||
void sleep(void) override{};
|
||||
|
||||
private:
|
||||
chsc6x *chsc6xTouch = nullptr;
|
||||
};
|
||||
} // namespace v1
|
||||
} // namespace lgfx
|
||||
#endif
|
||||
class LGFX : public lgfx::LGFX_Device
|
||||
{
|
||||
lgfx::Panel_ST7789 _panel_instance;
|
||||
@@ -431,6 +481,8 @@ class LGFX : public lgfx::LGFX_Device
|
||||
#if HAS_TOUCHSCREEN
|
||||
#if defined(T_WATCH_S3) || defined(ELECROW)
|
||||
lgfx::Touch_FT5x06 _touch_instance;
|
||||
#elif defined(HELTEC_V4_TFT)
|
||||
lgfx::TOUCH_CHSC6X _touch_instance;
|
||||
#else
|
||||
lgfx::Touch_GT911 _touch_instance;
|
||||
#endif
|
||||
@@ -464,9 +516,9 @@ class LGFX : public lgfx::LGFX_Device
|
||||
{ // Set the display panel control.
|
||||
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
|
||||
|
||||
cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable)
|
||||
cfg.pin_rst = -1; // Pin number where RST is connected (-1 = disable)
|
||||
cfg.pin_busy = -1; // Pin number where BUSY is connected (-1 = disable)
|
||||
cfg.pin_cs = ST7789_CS; // Pin number where CS is connected (-1 = disable)
|
||||
cfg.pin_rst = ST7789_RESET; // Pin number where RST is connected (-1 = disable)
|
||||
cfg.pin_busy = ST7789_BUSY; // Pin number where BUSY is connected (-1 = disable)
|
||||
|
||||
// The following setting values are general initial values for each panel, so please comment out any
|
||||
// unknown items and try them.
|
||||
|
||||
@@ -101,3 +101,23 @@ void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength)
|
||||
else
|
||||
snprintf(timeStr, maxLength, "unknown age");
|
||||
}
|
||||
|
||||
void getUptimeStr(uint32_t uptimeMillis, const char *prefix, char *uptimeStr, uint8_t maxLength, bool includeSecs)
|
||||
{
|
||||
uint32_t days = uptimeMillis / 86400000;
|
||||
uint32_t hours = (uptimeMillis % 86400000) / 3600000;
|
||||
uint32_t mins = (uptimeMillis % 3600000) / 60000;
|
||||
uint32_t secs = (uptimeMillis % 60000) / 1000;
|
||||
|
||||
if (days) {
|
||||
snprintf(uptimeStr, maxLength, "%s: %ud %uh", prefix, days, hours);
|
||||
} else if (hours) {
|
||||
snprintf(uptimeStr, maxLength, "%s: %uh %um", prefix, hours, mins);
|
||||
} else if (!includeSecs) {
|
||||
snprintf(uptimeStr, maxLength, "%s: %um", prefix, mins);
|
||||
} else if (mins) {
|
||||
snprintf(uptimeStr, maxLength, "%s: %um %us", prefix, mins, secs);
|
||||
} else {
|
||||
snprintf(uptimeStr, maxLength, "%s: %us", prefix, secs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,3 +24,10 @@ bool deltaToTimestamp(uint32_t secondsAgo, uint8_t *hours, uint8_t *minutes, int
|
||||
* @param maxLength Maximum length of the resulting string buffer
|
||||
*/
|
||||
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength);
|
||||
|
||||
/**
|
||||
* Get a compact human-readable string that only shows the largest non-zero time components.
|
||||
* For example, 0 days 1 hour 2 minutes will display as "1h 2m" but 1 day 2 hours 3 minutes
|
||||
* will display as "1d 2h".
|
||||
*/
|
||||
void getUptimeStr(uint32_t uptimeMillis, const char *prefix, char *uptimeStr, uint8_t maxLength, bool includeSecs = false);
|
||||
|
||||
@@ -194,17 +194,12 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
||||
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
|
||||
int line = 0;
|
||||
|
||||
#ifdef T_WATCH_S3
|
||||
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
|
||||
graphics::ClockRenderer::drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14);
|
||||
}
|
||||
#endif
|
||||
|
||||
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
|
||||
char timeString[16];
|
||||
int hour = 0;
|
||||
int minute = 0;
|
||||
int second = 0;
|
||||
|
||||
if (rtc_sec > 0) {
|
||||
long hms = rtc_sec % SEC_PER_DAY;
|
||||
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
|
||||
@@ -215,11 +210,11 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
||||
}
|
||||
|
||||
bool isPM = hour >= 12;
|
||||
// hour = hour > 12 ? hour - 12 : hour;
|
||||
if (config.display.use_12h_clock) {
|
||||
hour %= 12;
|
||||
if (hour == 0)
|
||||
if (hour == 0) {
|
||||
hour = 12;
|
||||
}
|
||||
snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute);
|
||||
} else {
|
||||
snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute);
|
||||
@@ -229,24 +224,56 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
||||
char secondString[8];
|
||||
snprintf(secondString, sizeof(secondString), "%02d", second);
|
||||
|
||||
#ifdef T_WATCH_S3
|
||||
float scale = 1.5;
|
||||
#elif defined(CHATTER_2)
|
||||
float scale = 1.1;
|
||||
#else
|
||||
float scale = 0.75;
|
||||
if (isHighResolution) {
|
||||
scale = 1.5;
|
||||
}
|
||||
#endif
|
||||
static bool scaleInitialized = false;
|
||||
static float scale = 0.75f;
|
||||
static float segmentWidth = SEGMENT_WIDTH * 0.75f;
|
||||
static float segmentHeight = SEGMENT_HEIGHT * 0.75f;
|
||||
|
||||
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
|
||||
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
|
||||
if (!scaleInitialized) {
|
||||
float screenwidth_target_ratio = 0.80f; // Target 80% of display width (adjustable)
|
||||
float max_scale = 3.5f; // Safety limit to avoid runaway scaling
|
||||
float step = 0.05f; // Step increment per iteration
|
||||
|
||||
float target_width = display->getWidth() * screenwidth_target_ratio;
|
||||
float target_height =
|
||||
display->getHeight() -
|
||||
(isHighResolution
|
||||
? 46
|
||||
: 33); // Be careful adjusting this number, we have to account for header and the text under the time
|
||||
|
||||
float calculated_width_size = 0.0f;
|
||||
float calculated_height_size = 0.0f;
|
||||
|
||||
while (true) {
|
||||
segmentWidth = SEGMENT_WIDTH * scale;
|
||||
segmentHeight = SEGMENT_HEIGHT * scale;
|
||||
|
||||
calculated_width_size = segmentHeight + ((segmentWidth + (segmentHeight * 2) + 4) * 4);
|
||||
calculated_height_size = segmentHeight + ((segmentHeight + (segmentHeight * 2) + 4) * 2);
|
||||
|
||||
if (calculated_width_size >= target_width || calculated_height_size >= target_height || scale >= max_scale) {
|
||||
break;
|
||||
}
|
||||
|
||||
scale += step;
|
||||
}
|
||||
|
||||
// If we overshot width, back off one step and recompute segment sizes
|
||||
if (calculated_width_size > target_width || calculated_height_size > target_height) {
|
||||
scale -= step;
|
||||
segmentWidth = SEGMENT_WIDTH * scale;
|
||||
segmentHeight = SEGMENT_HEIGHT * scale;
|
||||
}
|
||||
|
||||
scaleInitialized = true;
|
||||
}
|
||||
|
||||
size_t len = strlen(timeString);
|
||||
|
||||
// calculate hours:minutes string width
|
||||
uint16_t timeStringWidth = strlen(timeString) * 5;
|
||||
uint16_t timeStringWidth = len * 5; // base spacing between characters
|
||||
|
||||
for (uint8_t i = 0; i < strlen(timeString); i++) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
char character = timeString[i];
|
||||
|
||||
if (character == ':') {
|
||||
@@ -257,19 +284,21 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
||||
}
|
||||
|
||||
uint16_t hourMinuteTextX = (display->getWidth() / 2) - (timeStringWidth / 2);
|
||||
|
||||
uint16_t startingHourMinuteTextX = hourMinuteTextX;
|
||||
|
||||
uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2);
|
||||
uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2) + 2;
|
||||
|
||||
// iterate over characters in hours:minutes string and draw segmented characters
|
||||
for (uint8_t i = 0; i < strlen(timeString); i++) {
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
char character = timeString[i];
|
||||
|
||||
if (character == ':') {
|
||||
drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale);
|
||||
|
||||
hourMinuteTextX += segmentHeight + 6;
|
||||
if (scale >= 2.0f) {
|
||||
hourMinuteTextX += (uint16_t)(4.5f * scale);
|
||||
}
|
||||
} else {
|
||||
drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character - '0', scale);
|
||||
|
||||
@@ -279,34 +308,27 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
||||
hourMinuteTextX += 5;
|
||||
}
|
||||
|
||||
// draw seconds string
|
||||
// draw seconds string + AM/PM
|
||||
display->setFont(FONT_SMALL);
|
||||
int xOffset = (isHighResolution) ? 0 : -1;
|
||||
if (hour >= 10) {
|
||||
xOffset += (isHighResolution) ? 32 : 18;
|
||||
}
|
||||
int yOffset = (isHighResolution) ? 3 : 1;
|
||||
#ifdef SENSECAP_INDICATOR
|
||||
yOffset -= 3;
|
||||
#endif
|
||||
#ifdef T_DECK
|
||||
yOffset -= 5;
|
||||
#endif
|
||||
|
||||
if (config.display.use_12h_clock) {
|
||||
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2,
|
||||
isPM ? "pm" : "am");
|
||||
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - 1, isPM ? "pm" : "am");
|
||||
}
|
||||
|
||||
#ifndef USE_EINK
|
||||
xOffset = (isHighResolution) ? 18 : 10;
|
||||
display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - yOffset,
|
||||
if (scale >= 2.0f) {
|
||||
xOffset -= (int)(4.5f * scale);
|
||||
}
|
||||
display->drawString(startingHourMinuteTextX + timeStringWidth - xOffset, (display->getHeight() - hourMinuteTextY) - 1,
|
||||
secondString);
|
||||
#endif
|
||||
}
|
||||
|
||||
void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
|
||||
{
|
||||
display->drawFastImage(x, y, 18, 14, bluetoothConnectedIcon);
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
// Draw an analog clock
|
||||
@@ -319,11 +341,6 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
|
||||
int line = 0;
|
||||
|
||||
#ifdef T_WATCH_S3
|
||||
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
|
||||
drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14);
|
||||
}
|
||||
#endif
|
||||
// clock face center coordinates
|
||||
int16_t centerX = display->getWidth() / 2;
|
||||
int16_t centerY = display->getHeight() / 2;
|
||||
@@ -516,6 +533,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
display->drawLine(centerX, centerY, secondX, secondY);
|
||||
#endif
|
||||
}
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
} // namespace ClockRenderer
|
||||
|
||||
@@ -24,7 +24,6 @@ void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int heig
|
||||
|
||||
// UI elements for clock displays
|
||||
// void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode = true, float scale = 1);
|
||||
void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y);
|
||||
|
||||
} // namespace ClockRenderer
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "../Screen.h"
|
||||
#include "DebugRenderer.h"
|
||||
#include "FSCommon.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "Throttle.h"
|
||||
#include "UIRenderer.h"
|
||||
@@ -10,6 +11,7 @@
|
||||
#include "gps/RTC.h"
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
#include "graphics/TimeFormatters.h"
|
||||
#include "graphics/images.h"
|
||||
#include "main.h"
|
||||
#include "mesh/Channels.h"
|
||||
@@ -223,6 +225,8 @@ void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i
|
||||
|
||||
display->drawString(x, getTextPositions(display)[line++], "URL: http://meshtastic.local");
|
||||
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
|
||||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||
#ifdef SHOW_REDRAWS
|
||||
if (heartbeat)
|
||||
@@ -503,6 +507,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
||||
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++],
|
||||
chUtilPercentage);
|
||||
#endif
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
// ****************************
|
||||
@@ -642,27 +647,48 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
|
||||
int textWidth = display->getStringWidth(appversionstr);
|
||||
int nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||
|
||||
display->drawString(nameX, getTextPositions(display)[line], appversionstr);
|
||||
#if !defined(M5STACK_UNITC6L)
|
||||
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line < 4)) { // Only show uptime if the screen can show it
|
||||
line += 1;
|
||||
display->drawString(nameX, getTextPositions(display)[line++], appversionstr);
|
||||
|
||||
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show uptime if the screen can show it
|
||||
char uptimeStr[32] = "";
|
||||
uint32_t uptime = millis() / 1000;
|
||||
uint32_t days = uptime / 86400;
|
||||
uint32_t hours = (uptime % 86400) / 3600;
|
||||
uint32_t mins = (uptime % 3600) / 60;
|
||||
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
|
||||
if (days)
|
||||
snprintf(uptimeStr, sizeof(uptimeStr), " Up: %ud %uh", days, hours);
|
||||
else if (hours)
|
||||
snprintf(uptimeStr, sizeof(uptimeStr), " Up: %uh %um", hours, mins);
|
||||
else
|
||||
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins);
|
||||
getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr));
|
||||
textWidth = display->getStringWidth(uptimeStr);
|
||||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||
display->drawString(nameX, getTextPositions(display)[line], uptimeStr);
|
||||
display->drawString(nameX, getTextPositions(display)[line++], uptimeStr);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show API state if the screen can show it
|
||||
char api_state[32] = "";
|
||||
const char *clientWord = nullptr;
|
||||
|
||||
// Determine if narrow or wide screen
|
||||
if (isHighResolution) {
|
||||
clientWord = "Client";
|
||||
} else {
|
||||
clientWord = "App";
|
||||
}
|
||||
snprintf(api_state, sizeof(api_state), "No %ss Connected", clientWord);
|
||||
|
||||
if (service->api_state == service->STATE_BLE) {
|
||||
snprintf(api_state, sizeof(api_state), "%s Connected (BLE)", clientWord);
|
||||
} else if (service->api_state == service->STATE_WIFI) {
|
||||
snprintf(api_state, sizeof(api_state), "%s Connected (WiFi)", clientWord);
|
||||
} else if (service->api_state == service->STATE_SERIAL) {
|
||||
snprintf(api_state, sizeof(api_state), "%s Connected (Serial)", clientWord);
|
||||
} else if (service->api_state == service->STATE_PACKET) {
|
||||
snprintf(api_state, sizeof(api_state), "%s Connected (Internal)", clientWord);
|
||||
} else if (service->api_state == service->STATE_HTTP) {
|
||||
snprintf(api_state, sizeof(api_state), "%s Connected (HTTP)", clientWord);
|
||||
} else if (service->api_state == service->STATE_ETH) {
|
||||
snprintf(api_state, sizeof(api_state), "%s Connected (Ethernet)", clientWord);
|
||||
}
|
||||
if (api_state[0] != '\0') {
|
||||
display->drawString((SCREEN_WIDTH - display->getStringWidth(api_state)) / 2, getTextPositions(display)[line++],
|
||||
api_state);
|
||||
}
|
||||
}
|
||||
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
// ****************************
|
||||
@@ -694,4 +720,4 @@ void drawChirpy(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int1
|
||||
|
||||
} // namespace DebugRenderer
|
||||
} // namespace graphics
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -119,6 +119,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
|
||||
auto changes = SEGMENT_CONFIG;
|
||||
|
||||
// This is needed as we wait til picking the LoRa region to generate keys for the first time.
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
|
||||
if (!owner.is_licensed) {
|
||||
bool keygenSuccess = false;
|
||||
if (config.security.private_key.size == 32) {
|
||||
@@ -139,6 +140,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
|
||||
memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
config.lora.tx_enabled = true;
|
||||
initRegion();
|
||||
if (myRegion->dutyCycle < 100) {
|
||||
@@ -574,21 +576,16 @@ void menuHandler::textMessageBaseMenu()
|
||||
|
||||
void menuHandler::systemBaseMenu()
|
||||
{
|
||||
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, FrameToggles, Test, enumEnd };
|
||||
enum optionsNumbers { Back, Notifications, ScreenOptions, Bluetooth, PowerMenu, Test, enumEnd };
|
||||
static const char *optionsArray[enumEnd] = {"Back"};
|
||||
static int optionsEnumArray[enumEnd] = {Back};
|
||||
int options = 1;
|
||||
|
||||
optionsArray[options] = "Notifications";
|
||||
optionsEnumArray[options++] = Notifications;
|
||||
#if defined(ST7789_CS) || defined(ST7796_CS) || defined(USE_OLED) || defined(USE_SSD1306) || defined(USE_SH1106) || \
|
||||
defined(USE_SH1107) || defined(HELTEC_MESH_NODE_T114) || defined(HELTEC_VISION_MASTER_T190) || HAS_TFT
|
||||
optionsArray[options] = "Screen Options";
|
||||
optionsArray[options] = "Display Options";
|
||||
optionsEnumArray[options++] = ScreenOptions;
|
||||
#endif
|
||||
|
||||
optionsArray[options] = "Frame Visiblity Toggle";
|
||||
optionsEnumArray[options++] = FrameToggles;
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
optionsArray[options] = "Bluetooth";
|
||||
#else
|
||||
@@ -626,9 +623,6 @@ void menuHandler::systemBaseMenu()
|
||||
} else if (selected == PowerMenu) {
|
||||
menuHandler::menuQueue = menuHandler::power_menu;
|
||||
screen->runNow();
|
||||
} else if (selected == FrameToggles) {
|
||||
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||
screen->runNow();
|
||||
} else if (selected == Test) {
|
||||
menuHandler::menuQueue = menuHandler::test_menu;
|
||||
screen->runNow();
|
||||
@@ -790,17 +784,24 @@ void menuHandler::nodeNameLengthMenu()
|
||||
|
||||
void menuHandler::resetNodeDBMenu()
|
||||
{
|
||||
static const char *optionsArray[] = {"Back", "Confirm"};
|
||||
static const char *optionsArray[] = {"Back", "Reset All", "Preserve Favorites"};
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Confirm Reset NodeDB";
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 2;
|
||||
bannerOptions.optionsCount = 3;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == 1) {
|
||||
if (selected == 1 || selected == 2) {
|
||||
disableBluetooth();
|
||||
screen->setFrames(Screen::FOCUS_DEFAULT);
|
||||
}
|
||||
if (selected == 1) {
|
||||
LOG_INFO("Initiate node-db reset");
|
||||
nodeDB->resetNodes();
|
||||
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
|
||||
} else if (selected == 2) {
|
||||
LOG_INFO("Initiate node-db reset but keeping favorites");
|
||||
nodeDB->resetNodes(1);
|
||||
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
|
||||
}
|
||||
};
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
@@ -936,7 +937,9 @@ void menuHandler::BluetoothToggleMenu()
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 3;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == 1 || selected == 2) {
|
||||
if (selected == 0)
|
||||
return;
|
||||
else if (selected != (config.bluetooth.enabled ? 1 : 2)) {
|
||||
InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0};
|
||||
inputBroker->injectInputEvent(&event);
|
||||
}
|
||||
@@ -1188,8 +1191,8 @@ void menuHandler::removeFavoriteMenu()
|
||||
BannerOverlayOptions bannerOptions;
|
||||
std::string message = "Unfavorite This Node?\n";
|
||||
auto node = nodeDB->getMeshNode(graphics::UIRenderer::currentFavoriteNodeNum);
|
||||
if (node && node->has_user) {
|
||||
message += sanitizeString(node->user.long_name).substr(0, 15);
|
||||
if (node && detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER)) {
|
||||
message += sanitizeString(node->long_name).substr(0, 15);
|
||||
}
|
||||
bannerOptions.message = message.c_str();
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
@@ -1330,7 +1333,7 @@ void menuHandler::screenOptionsMenu()
|
||||
hasSupportBrightness = false;
|
||||
#endif
|
||||
|
||||
enum optionsNumbers { Back, NodeNameLength, Brightness, ScreenColor };
|
||||
enum optionsNumbers { Back, NodeNameLength, Brightness, ScreenColor, FrameToggles, DisplayUnits };
|
||||
static const char *optionsArray[5] = {"Back"};
|
||||
static int optionsEnumArray[5] = {Back};
|
||||
int options = 1;
|
||||
@@ -1352,8 +1355,14 @@ void menuHandler::screenOptionsMenu()
|
||||
optionsEnumArray[options++] = ScreenColor;
|
||||
#endif
|
||||
|
||||
optionsArray[options] = "Frame Visibility Toggle";
|
||||
optionsEnumArray[options++] = FrameToggles;
|
||||
|
||||
optionsArray[options] = "Display Units";
|
||||
optionsEnumArray[options++] = DisplayUnits;
|
||||
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Screen Options";
|
||||
bannerOptions.message = "Display Options";
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = options;
|
||||
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||
@@ -1367,6 +1376,12 @@ void menuHandler::screenOptionsMenu()
|
||||
} else if (selected == NodeNameLength) {
|
||||
menuHandler::menuQueue = menuHandler::node_name_length_menu;
|
||||
screen->runNow();
|
||||
} else if (selected == FrameToggles) {
|
||||
menuHandler::menuQueue = menuHandler::FrameToggles;
|
||||
screen->runNow();
|
||||
} else if (selected == DisplayUnits) {
|
||||
menuHandler::menuQueue = menuHandler::DisplayUnits;
|
||||
screen->runNow();
|
||||
} else {
|
||||
menuQueue = system_base_menu;
|
||||
screen->runNow();
|
||||
@@ -1444,8 +1459,10 @@ void menuHandler::keyVerificationFinalPrompt()
|
||||
options.notificationType = graphics::notificationTypeEnum::selection_picker;
|
||||
options.bannerCallback = [=](int selected) {
|
||||
if (selected == 1) {
|
||||
auto remoteNodePtr = nodeDB->getMeshNode(keyVerificationModule->getCurrentRemoteNode());
|
||||
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
|
||||
meshtastic_NodeDetail *remoteNodePtr = nodeDB->getMeshNode(keyVerificationModule->getCurrentRemoteNode());
|
||||
if (remoteNodePtr) {
|
||||
detailSetFlag(*remoteNodePtr, NODEDETAIL_FLAG_IS_KEY_MANUALLY_VERIFIED, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
screen->showOverlayBanner(options);
|
||||
@@ -1578,6 +1595,34 @@ void menuHandler::FrameToggles_menu()
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
void menuHandler::DisplayUnits_menu()
|
||||
{
|
||||
enum optionsNumbers { Back, MetricUnits, ImperialUnits };
|
||||
|
||||
static const char *optionsArray[] = {"Back", "Metric", "Imperial"};
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = " Select display units";
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsCount = 3;
|
||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL)
|
||||
bannerOptions.InitialSelected = 2;
|
||||
else
|
||||
bannerOptions.InitialSelected = 1;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == MetricUnits) {
|
||||
config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_METRIC;
|
||||
service->reloadConfig(SEGMENT_CONFIG);
|
||||
} else if (selected == ImperialUnits) {
|
||||
config.display.units = meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL;
|
||||
service->reloadConfig(SEGMENT_CONFIG);
|
||||
} else {
|
||||
menuHandler::menuQueue = menuHandler::screen_options_menu;
|
||||
screen->runNow();
|
||||
}
|
||||
};
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
||||
{
|
||||
if (menuQueue != menu_none)
|
||||
@@ -1692,6 +1737,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
||||
case FrameToggles:
|
||||
FrameToggles_menu();
|
||||
break;
|
||||
case DisplayUnits:
|
||||
DisplayUnits_menu();
|
||||
break;
|
||||
case throttle_message:
|
||||
screen->showSimpleBanner("Too Many Attempts\nTry again in 60 seconds.", 5000);
|
||||
break;
|
||||
@@ -1706,4 +1754,4 @@ void menuHandler::saveUIConfig()
|
||||
|
||||
} // namespace graphics
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -44,7 +44,8 @@ class menuHandler
|
||||
trace_route_menu,
|
||||
throttle_message,
|
||||
node_name_length_menu,
|
||||
FrameToggles
|
||||
FrameToggles,
|
||||
DisplayUnits
|
||||
};
|
||||
static screenMenus menuQueue;
|
||||
|
||||
@@ -88,6 +89,7 @@ class menuHandler
|
||||
static void powerMenu();
|
||||
static void nodeNameLengthMenu();
|
||||
static void FrameToggles_menu();
|
||||
static void DisplayUnits_menu();
|
||||
static void textMessageMenu();
|
||||
|
||||
private:
|
||||
|
||||
@@ -213,22 +213,23 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
#else
|
||||
display->drawString(center_text, getTextPositions(display)[2], messageString);
|
||||
#endif
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
return;
|
||||
}
|
||||
|
||||
// === Header Construction ===
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
|
||||
const meshtastic_NodeDetail *node = nodeDB->getMeshNode(getFrom(&mp));
|
||||
char headerStr[80];
|
||||
const char *sender = "???";
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
if (node && node->has_user)
|
||||
sender = node->user.short_name;
|
||||
if (node && detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER))
|
||||
sender = node->short_name;
|
||||
#else
|
||||
if (node && node->has_user) {
|
||||
if (SCREEN_WIDTH >= 200 && strlen(node->user.long_name) > 0) {
|
||||
sender = node->user.long_name;
|
||||
if (node && detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER)) {
|
||||
if (SCREEN_WIDTH >= 200 && node->long_name[0] != '\0') {
|
||||
sender = node->long_name;
|
||||
} else {
|
||||
sender = node->user.short_name;
|
||||
sender = node->short_name;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -423,6 +424,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
// Draw header at the end to sort out overlapping elements
|
||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||
#endif
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth)
|
||||
|
||||
@@ -53,23 +53,29 @@ static int scrollIndex = 0;
|
||||
// Utility Functions
|
||||
// =============================
|
||||
|
||||
const char *getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node)
|
||||
const char *getSafeNodeName(OLEDDisplay *display, const meshtastic_NodeDetail *node)
|
||||
{
|
||||
const char *name = NULL;
|
||||
static char nodeName[16] = "?";
|
||||
if (config.display.use_long_node_name == true) {
|
||||
if (node->has_user && strlen(node->user.long_name) > 0) {
|
||||
name = node->user.long_name;
|
||||
} else {
|
||||
snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
|
||||
if (!node) {
|
||||
strncpy(nodeName, "?", sizeof(nodeName));
|
||||
return nodeName;
|
||||
}
|
||||
|
||||
const bool hasUser = detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER);
|
||||
const char *name = nullptr;
|
||||
if (config.display.use_long_node_name) {
|
||||
if (hasUser && node->long_name[0] != '\0') {
|
||||
name = node->long_name;
|
||||
}
|
||||
} else {
|
||||
if (node->has_user && strlen(node->user.short_name) > 0) {
|
||||
name = node->user.short_name;
|
||||
} else {
|
||||
snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
|
||||
if (hasUser && node->short_name[0] != '\0') {
|
||||
name = node->short_name;
|
||||
}
|
||||
}
|
||||
if (!name) {
|
||||
snprintf(nodeName, sizeof(nodeName), "(%04X)", static_cast<uint16_t>(node->num & 0xFFFF));
|
||||
name = nodeName;
|
||||
}
|
||||
|
||||
// Use sanitizeString() function and copy directly into nodeName
|
||||
std::string sanitized_name = sanitizeString(name ? name : "");
|
||||
@@ -164,7 +170,7 @@ void drawScrollbar(OLEDDisplay *display, int visibleNodeRows, int totalEntries,
|
||||
// Entry Renderers
|
||||
// =============================
|
||||
|
||||
void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
||||
void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeDetail *node, int16_t x, int16_t y, int columnWidth)
|
||||
{
|
||||
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||
int timeOffset = (isHighResolution) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7);
|
||||
@@ -189,7 +195,7 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawString(x + ((isHighResolution) ? 6 : 3), y, nodeName);
|
||||
if (node->is_favorite) {
|
||||
if (node && detailIsFavorite(*node)) {
|
||||
if (isHighResolution) {
|
||||
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
||||
} else {
|
||||
@@ -204,7 +210,7 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
||||
display->drawString(rightEdge - textWidth, y, timeStr);
|
||||
}
|
||||
|
||||
void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
||||
void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeDetail *node, int16_t x, int16_t y, int columnWidth)
|
||||
{
|
||||
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||
|
||||
@@ -220,7 +226,7 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
display->drawStringMaxWidth(x + ((isHighResolution) ? 6 : 3), y, nameMaxWidth, nodeName);
|
||||
if (node->is_favorite) {
|
||||
if (node && detailIsFavorite(*node)) {
|
||||
if (isHighResolution) {
|
||||
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
||||
} else {
|
||||
@@ -243,7 +249,7 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
||||
|
||||
// Draw hop count
|
||||
char hopStr[6] = "";
|
||||
if (node->has_hops_away && node->hops_away > 0)
|
||||
if (detailHasFlag(*node, NODEDETAIL_FLAG_HAS_HOPS_AWAY) && node->hops_away > 0)
|
||||
snprintf(hopStr, sizeof(hopStr), "[%d]", node->hops_away);
|
||||
|
||||
if (hopStr[0] != '\0') {
|
||||
@@ -253,7 +259,7 @@ 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_NodeDetail *node, int16_t x, int16_t y, int columnWidth)
|
||||
{
|
||||
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||
int nameMaxWidth = columnWidth - (isHighResolution ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
|
||||
@@ -261,12 +267,12 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
||||
const char *nodeName = getSafeNodeName(display, node);
|
||||
char distStr[10] = "";
|
||||
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
meshtastic_NodeDetail *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
if (nodeDB->hasValidPosition(ourNode) && nodeDB->hasValidPosition(node)) {
|
||||
double lat1 = ourNode->position.latitude_i * 1e-7;
|
||||
double lon1 = ourNode->position.longitude_i * 1e-7;
|
||||
double lat2 = node->position.latitude_i * 1e-7;
|
||||
double lon2 = node->position.longitude_i * 1e-7;
|
||||
double lat1 = ourNode->latitude_i * 1e-7;
|
||||
double lon1 = ourNode->longitude_i * 1e-7;
|
||||
double lat2 = node->latitude_i * 1e-7;
|
||||
double lon2 = node->longitude_i * 1e-7;
|
||||
|
||||
double earthRadiusKm = 6371.0;
|
||||
double dLat = (lat2 - lat1) * DEG_TO_RAD;
|
||||
@@ -312,7 +318,7 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawStringMaxWidth(x + ((isHighResolution) ? 6 : 3), y, nameMaxWidth, nodeName);
|
||||
if (node->is_favorite) {
|
||||
if (node && detailIsFavorite(*node)) {
|
||||
if (isHighResolution) {
|
||||
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
||||
} else {
|
||||
@@ -329,7 +335,7 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
||||
}
|
||||
}
|
||||
|
||||
void drawEntryDynamic(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
||||
void drawEntryDynamic(OLEDDisplay *display, meshtastic_NodeDetail *node, int16_t x, int16_t y, int columnWidth)
|
||||
{
|
||||
switch (currentMode) {
|
||||
case MODE_LAST_HEARD:
|
||||
@@ -346,7 +352,7 @@ void drawEntryDynamic(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
||||
}
|
||||
}
|
||||
|
||||
void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth)
|
||||
void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeDetail *node, int16_t x, int16_t y, int columnWidth)
|
||||
{
|
||||
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||
|
||||
@@ -358,7 +364,7 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawStringMaxWidth(x + ((isHighResolution) ? 6 : 3), y, nameMaxWidth, nodeName);
|
||||
if (node->is_favorite) {
|
||||
if (node && detailIsFavorite(*node)) {
|
||||
if (isHighResolution) {
|
||||
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
||||
} else {
|
||||
@@ -367,7 +373,7 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
||||
}
|
||||
}
|
||||
|
||||
void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth, float myHeading,
|
||||
void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeDetail *node, int16_t x, int16_t y, int columnWidth, float myHeading,
|
||||
double userLat, double userLon)
|
||||
{
|
||||
if (!nodeDB->hasValidPosition(node))
|
||||
@@ -379,8 +385,8 @@ void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
||||
int centerX = x + columnWidth - arrowXOffset;
|
||||
int centerY = y + FONT_HEIGHT_SMALL / 2;
|
||||
|
||||
double nodeLat = node->position.latitude_i * 1e-7;
|
||||
double nodeLon = node->position.longitude_i * 1e-7;
|
||||
double nodeLat = node->latitude_i * 1e-7;
|
||||
double nodeLon = node->longitude_i * 1e-7;
|
||||
float bearing = GeoCoord::bearing(userLat, userLon, nodeLat, nodeLon);
|
||||
float bearingToNode = RAD_TO_DEG * bearing;
|
||||
float relativeBearing = fmod((bearingToNode - myHeading + 360), 360);
|
||||
@@ -425,6 +431,12 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
||||
{
|
||||
const int COMMON_HEADER_HEIGHT = FONT_HEIGHT_SMALL - 1;
|
||||
const int rowYOffset = FONT_HEIGHT_SMALL - 3;
|
||||
bool locationScreen = false;
|
||||
|
||||
if (strcmp(title, "Bearings") == 0)
|
||||
locationScreen = true;
|
||||
else if (strcmp(title, "Distance") == 0)
|
||||
locationScreen = true;
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
int columnWidth = display->getWidth();
|
||||
#else
|
||||
@@ -440,7 +452,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
||||
|
||||
int totalEntries = nodeDB->getNumMeshNodes();
|
||||
int totalRowsAvailable = (display->getHeight() - y) / rowYOffset;
|
||||
|
||||
int numskipped = 0;
|
||||
int visibleNodeRows = totalRowsAvailable;
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
int totalColumns = 1;
|
||||
@@ -460,12 +472,17 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
||||
int rowCount = 0;
|
||||
|
||||
for (int i = startIndex; i < endIndex; ++i) {
|
||||
meshtastic_NodeDetail *candidate = nodeDB->getMeshNodeByIndex(i);
|
||||
if (locationScreen && candidate && !detailHasFlag(*candidate, NODEDETAIL_FLAG_HAS_POSITION)) {
|
||||
numskipped++;
|
||||
continue;
|
||||
}
|
||||
int xPos = x + (col * columnWidth);
|
||||
int yPos = y + yOffset;
|
||||
renderer(display, nodeDB->getMeshNodeByIndex(i), xPos, yPos, columnWidth);
|
||||
renderer(display, candidate, xPos, yPos, columnWidth);
|
||||
|
||||
if (extras) {
|
||||
extras(display, nodeDB->getMeshNodeByIndex(i), xPos, yPos, columnWidth, heading, lat, lon);
|
||||
extras(display, candidate, xPos, yPos, columnWidth, heading, lat, lon);
|
||||
}
|
||||
|
||||
lastNodeY = std::max(lastNodeY, yPos + FONT_HEIGHT_SMALL);
|
||||
@@ -482,6 +499,9 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
||||
}
|
||||
}
|
||||
|
||||
// This should correct the scrollbar
|
||||
totalEntries -= numskipped;
|
||||
|
||||
#if !defined(M5STACK_UNITC6L)
|
||||
// Draw column separator
|
||||
if (shownCount > 0) {
|
||||
@@ -492,6 +512,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
||||
#endif
|
||||
const int scrollStartY = y + 3;
|
||||
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
// =============================
|
||||
@@ -564,9 +585,12 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state,
|
||||
{
|
||||
float heading = 0;
|
||||
bool validHeading = false;
|
||||
auto ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
double lat = DegD(ourNode->position.latitude_i);
|
||||
double lon = DegD(ourNode->position.longitude_i);
|
||||
meshtastic_NodeDetail *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
if (!ourNode || !nodeDB->hasValidPosition(ourNode)) {
|
||||
return;
|
||||
}
|
||||
double lat = DegD(ourNode->latitude_i);
|
||||
double lon = DegD(ourNode->longitude_i);
|
||||
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
display->clear();
|
||||
|
||||
@@ -20,8 +20,8 @@ class Screen;
|
||||
namespace NodeListRenderer
|
||||
{
|
||||
// Entry renderer function types
|
||||
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 (*EntryRenderer)(OLEDDisplay *, meshtastic_NodeDetail *, int16_t, int16_t, int);
|
||||
typedef void (*NodeExtrasRenderer)(OLEDDisplay *, meshtastic_NodeDetail *, int16_t, int16_t, int, float, double, double);
|
||||
|
||||
// Node list mode enumeration
|
||||
enum NodeListMode { MODE_LAST_HEARD = 0, MODE_HOP_SIGNAL = 1, MODE_DISTANCE = 2, MODE_COUNT = 3 };
|
||||
@@ -32,14 +32,14 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
||||
double lon = 0);
|
||||
|
||||
// Entry renderers
|
||||
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 drawNodeDistance(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 drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeDetail *node, int16_t x, int16_t y, int columnWidth);
|
||||
void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeDetail *node, int16_t x, int16_t y, int columnWidth);
|
||||
void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeDetail *node, int16_t x, int16_t y, int columnWidth);
|
||||
void drawEntryDynamic(OLEDDisplay *display, meshtastic_NodeDetail *node, int16_t x, int16_t y, int columnWidth);
|
||||
void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeDetail *node, int16_t x, int16_t y, int columnWidth);
|
||||
|
||||
// Extras renderers
|
||||
void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16_t x, int16_t y, int columnWidth, float myHeading,
|
||||
void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeDetail *node, int16_t x, int16_t y, int columnWidth, float myHeading,
|
||||
double userLat, double userLon);
|
||||
|
||||
// Screen frame functions
|
||||
@@ -51,7 +51,7 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state,
|
||||
|
||||
// Utility functions
|
||||
const char *getCurrentModeTitle(int screenWidth);
|
||||
const char *getSafeNodeName(meshtastic_NodeInfoLite *node);
|
||||
const char *getSafeNodeName(OLEDDisplay *display, const meshtastic_NodeDetail *node);
|
||||
void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields);
|
||||
|
||||
// Bitmap drawing function
|
||||
|
||||
@@ -300,14 +300,17 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
|
||||
int scratchLineNum = 0;
|
||||
for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) {
|
||||
char temp_name[16] = {0};
|
||||
if (nodeDB->getMeshNodeByIndex(i + 1)->has_user) {
|
||||
std::string sanitized = sanitizeString(nodeDB->getMeshNodeByIndex(i + 1)->user.long_name);
|
||||
meshtastic_NodeDetail *candidate = nodeDB->getMeshNodeByIndex(i + 1);
|
||||
if (candidate && detailHasFlag(*candidate, NODEDETAIL_FLAG_HAS_USER) && candidate->long_name[0] != '\0') {
|
||||
std::string sanitized = sanitizeString(candidate->long_name);
|
||||
strncpy(temp_name, sanitized.c_str(), sizeof(temp_name) - 1);
|
||||
} else if (candidate) {
|
||||
snprintf(temp_name, sizeof(temp_name), "(%04X)", static_cast<uint16_t>(candidate->num & 0xFFFF));
|
||||
} else {
|
||||
snprintf(temp_name, sizeof(temp_name), "(%04X)", (uint16_t)(nodeDB->getMeshNodeByIndex(i + 1)->num & 0xFFFF));
|
||||
strncpy(temp_name, "(?)", sizeof(temp_name) - 1);
|
||||
}
|
||||
if (i == curSelected) {
|
||||
selectedNodenum = nodeDB->getMeshNodeByIndex(i + 1)->num;
|
||||
if (candidate && i == curSelected) {
|
||||
selectedNodenum = candidate->num;
|
||||
if (isHighResolution) {
|
||||
strncpy(scratchLineBuffer[scratchLineNum], "> ", 3);
|
||||
strncpy(scratchLineBuffer[scratchLineNum] + 2, temp_name, 36);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "graphics/Screen.h"
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
#include "graphics/TimeFormatters.h"
|
||||
#include "graphics/images.h"
|
||||
#include "main.h"
|
||||
#include "target_specific.h"
|
||||
@@ -26,22 +27,22 @@ static uint32_t lastSwitchTime = 0;
|
||||
namespace graphics
|
||||
{
|
||||
NodeNum UIRenderer::currentFavoriteNodeNum = 0;
|
||||
std::vector<meshtastic_NodeInfoLite *> graphics::UIRenderer::favoritedNodes;
|
||||
std::vector<meshtastic_NodeDetail *> graphics::UIRenderer::favoritedNodes;
|
||||
|
||||
void graphics::UIRenderer::rebuildFavoritedNodes()
|
||||
{
|
||||
favoritedNodes.clear();
|
||||
size_t total = nodeDB->getNumMeshNodes();
|
||||
for (size_t i = 0; i < total; i++) {
|
||||
meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
|
||||
meshtastic_NodeDetail *n = nodeDB->getMeshNodeByIndex(i);
|
||||
if (!n || n->num == nodeDB->getNodeNum())
|
||||
continue;
|
||||
if (n->is_favorite)
|
||||
if (detailIsFavorite(*n))
|
||||
favoritedNodes.push_back(n);
|
||||
}
|
||||
|
||||
std::sort(favoritedNodes.begin(), favoritedNodes.end(),
|
||||
[](const meshtastic_NodeInfoLite *a, const meshtastic_NodeInfoLite *b) { return a->num < b->num; });
|
||||
[](const meshtastic_NodeDetail *a, const meshtastic_NodeDetail *b) { return a->num < b->num; });
|
||||
}
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
@@ -288,8 +289,8 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
||||
if (nodeIndex < 0 || nodeIndex >= (int)favoritedNodes.size())
|
||||
return;
|
||||
|
||||
meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex];
|
||||
if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite)
|
||||
meshtastic_NodeDetail *node = favoritedNodes[nodeIndex];
|
||||
if (!node || node->num == nodeDB->getNodeNum() || !detailIsFavorite(*node))
|
||||
return;
|
||||
display->clear();
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
@@ -302,7 +303,9 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
||||
#endif
|
||||
currentFavoriteNodeNum = node->num;
|
||||
// === Create the shortName and title string ===
|
||||
const char *shortName = (node->has_user && haveGlyphs(node->user.short_name)) ? node->user.short_name : "Node";
|
||||
const bool hasUser = detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER);
|
||||
const char *shortNameCandidate = (hasUser && node->short_name[0] != '\0') ? node->short_name : nullptr;
|
||||
const char *shortName = (shortNameCandidate && haveGlyphs(shortNameCandidate)) ? shortNameCandidate : "Node";
|
||||
char titlestr[32] = {0};
|
||||
snprintf(titlestr, sizeof(titlestr), "Fav: %s", shortName);
|
||||
|
||||
@@ -320,9 +323,9 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
||||
std::string usernameStr;
|
||||
// === 1. Long Name (always try to show first) ===
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
const char *username = (node->has_user && node->user.long_name[0]) ? node->user.short_name : nullptr;
|
||||
const char *username = (hasUser && node->long_name[0]) ? node->short_name : nullptr;
|
||||
#else
|
||||
const char *username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr;
|
||||
const char *username = (hasUser && node->long_name[0]) ? node->long_name : nullptr;
|
||||
#endif
|
||||
|
||||
if (username) {
|
||||
@@ -348,7 +351,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
||||
haveSignal = true;
|
||||
}
|
||||
// If hops is valid (>0), show right after signal
|
||||
if (node->hops_away > 0) {
|
||||
if (detailHasFlag(*node, NODEDETAIL_FLAG_HAS_HOPS_AWAY) && node->hops_away > 0) {
|
||||
size_t len = strlen(signalHopsStr);
|
||||
// Decide between "1 Hop" and "N Hops"
|
||||
if (haveSignal) {
|
||||
@@ -382,33 +385,23 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
||||
#if !defined(M5STACK_UNITC6L)
|
||||
// === 4. Uptime (only show if metric is present) ===
|
||||
char uptimeStr[32] = "";
|
||||
if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) {
|
||||
uint32_t uptime = node->device_metrics.uptime_seconds;
|
||||
uint32_t days = uptime / 86400;
|
||||
uint32_t hours = (uptime % 86400) / 3600;
|
||||
uint32_t mins = (uptime % 3600) / 60;
|
||||
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
|
||||
if (days)
|
||||
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %ud %uh", days, hours);
|
||||
else if (hours)
|
||||
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %uh %um", hours, mins);
|
||||
else
|
||||
snprintf(uptimeStr, sizeof(uptimeStr), " Uptime: %um", mins);
|
||||
if (detailHasFlag(*node, NODEDETAIL_FLAG_HAS_UPTIME)) {
|
||||
getUptimeStr(node->uptime_seconds * 1000, " Up", uptimeStr, sizeof(uptimeStr));
|
||||
}
|
||||
if (uptimeStr[0] && line < 5) {
|
||||
display->drawString(x, getTextPositions(display)[line++], uptimeStr);
|
||||
}
|
||||
|
||||
// === 5. Distance (only if both nodes have GPS position) ===
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
meshtastic_NodeDetail *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
char distStr[24] = ""; // Make buffer big enough for any string
|
||||
bool haveDistance = false;
|
||||
|
||||
if (nodeDB->hasValidPosition(ourNode) && nodeDB->hasValidPosition(node)) {
|
||||
double lat1 = ourNode->position.latitude_i * 1e-7;
|
||||
double lon1 = ourNode->position.longitude_i * 1e-7;
|
||||
double lat2 = node->position.latitude_i * 1e-7;
|
||||
double lon2 = node->position.longitude_i * 1e-7;
|
||||
double lat1 = ourNode->latitude_i * 1e-7;
|
||||
double lon1 = ourNode->longitude_i * 1e-7;
|
||||
double lat2 = node->latitude_i * 1e-7;
|
||||
double lon2 = node->longitude_i * 1e-7;
|
||||
double earthRadiusKm = 6371.0;
|
||||
double dLat = (lat2 - lat1) * DEG_TO_RAD;
|
||||
double dLon = (lon2 - lon1) * DEG_TO_RAD;
|
||||
@@ -466,6 +459,10 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
||||
showCompass = true;
|
||||
}
|
||||
if (showCompass) {
|
||||
const double ourLatDeg = DegD(ourNode->latitude_i);
|
||||
const double ourLonDeg = DegD(ourNode->longitude_i);
|
||||
const double nodeLatDeg = DegD(node->latitude_i);
|
||||
const double nodeLonDeg = DegD(node->longitude_i);
|
||||
const int16_t topY = getTextPositions(display)[1];
|
||||
const int16_t bottomY = SCREEN_HEIGHT - (FONT_HEIGHT_SMALL - 1);
|
||||
const int16_t usableHeight = bottomY - topY - 5;
|
||||
@@ -476,16 +473,10 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
||||
const int16_t compassX = x + SCREEN_WIDTH - compassRadius - 8;
|
||||
const int16_t compassY = topY + (usableHeight / 2) + ((FONT_HEIGHT_SMALL - 1) / 2) + 2;
|
||||
|
||||
const auto &op = ourNode->position;
|
||||
float myHeading = screen->hasHeading() ? screen->getHeading() * PI / 180
|
||||
: screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
float myHeading =
|
||||
screen->hasHeading() ? screen->getHeading() * PI / 180 : screen->estimatedHeading(ourLatDeg, ourLonDeg);
|
||||
|
||||
const auto &p = node->position;
|
||||
/* unused
|
||||
float d =
|
||||
GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
*/
|
||||
float bearing = GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i));
|
||||
float bearing = GeoCoord::bearing(ourLatDeg, ourLonDeg, nodeLatDeg, nodeLonDeg);
|
||||
if (uiconfig.compass_mode == meshtastic_CompassMode_FREEZE_HEADING) {
|
||||
myHeading = 0;
|
||||
} else {
|
||||
@@ -529,20 +520,21 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
||||
int compassX = x + SCREEN_WIDTH / 2;
|
||||
int compassY = yBelowContent + availableHeight / 2;
|
||||
|
||||
const auto &op = ourNode->position;
|
||||
const double ourLatDeg = DegD(ourNode->latitude_i);
|
||||
const double ourLonDeg = DegD(ourNode->longitude_i);
|
||||
float myHeading = 0;
|
||||
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
|
||||
myHeading = screen->hasHeading() ? screen->getHeading() * PI / 180
|
||||
: screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
myHeading =
|
||||
screen->hasHeading() ? screen->getHeading() * PI / 180 : screen->estimatedHeading(ourLatDeg, ourLonDeg);
|
||||
}
|
||||
graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
|
||||
|
||||
const auto &p = node->position;
|
||||
const double nodeLatDeg = DegD(node->latitude_i);
|
||||
const double nodeLonDeg = DegD(node->longitude_i);
|
||||
/* unused
|
||||
float d =
|
||||
GeoCoord::latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||
float d = GeoCoord::latLongToMeter(nodeLatDeg, nodeLonDeg, ourLatDeg, ourLonDeg);
|
||||
*/
|
||||
float bearing = GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i));
|
||||
float bearing = GeoCoord::bearing(ourLatDeg, ourLonDeg, nodeLatDeg, nodeLonDeg);
|
||||
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING)
|
||||
bearing -= myHeading;
|
||||
graphics::CompassRenderer::drawNodeHeading(display, compassX, compassY, compassRadius * 2, bearing);
|
||||
@@ -552,6 +544,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
||||
// else show nothing
|
||||
}
|
||||
#endif
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
// ****************************
|
||||
@@ -563,7 +556,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
int line = 1;
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
meshtastic_NodeDetail *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
|
||||
// === Header ===
|
||||
#if defined(M5STACK_UNITC6L)
|
||||
@@ -591,18 +584,8 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
||||
drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
|
||||
#endif
|
||||
char uptimeStr[32] = "";
|
||||
uint32_t uptime = millis() / 1000;
|
||||
uint32_t days = uptime / 86400;
|
||||
uint32_t hours = (uptime % 86400) / 3600;
|
||||
uint32_t mins = (uptime % 3600) / 60;
|
||||
// Show as "Up: 2d 3h", "Up: 5h 14m", or "Up: 37m"
|
||||
#if !defined(M5STACK_UNITC6L)
|
||||
if (days)
|
||||
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %ud %uh", days, hours);
|
||||
else if (hours)
|
||||
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %uh %um", hours, mins);
|
||||
else
|
||||
snprintf(uptimeStr, sizeof(uptimeStr), "Up: %um", mins);
|
||||
getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr));
|
||||
#endif
|
||||
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
|
||||
|
||||
@@ -741,8 +724,8 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
||||
int yOffset = (isHighResolution) ? 0 : 5;
|
||||
std::string longNameStr;
|
||||
|
||||
if (ourNode && ourNode->has_user && strlen(ourNode->user.long_name) > 0) {
|
||||
longNameStr = sanitizeString(ourNode->user.long_name);
|
||||
if (ourNode && detailHasFlag(*ourNode, NODEDETAIL_FLAG_HAS_USER) && ourNode->long_name[0] != '\0') {
|
||||
longNameStr = sanitizeString(ourNode->long_name);
|
||||
}
|
||||
char shortnameble[35];
|
||||
snprintf(shortnameble, sizeof(shortnameble), "%s",
|
||||
@@ -771,6 +754,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
||||
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
|
||||
}
|
||||
#endif
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
// Start Functions to write date/time to the screen
|
||||
@@ -998,8 +982,6 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
||||
config.display.heading_bold = false;
|
||||
|
||||
const char *displayLine = ""; // Initialize to empty string by default
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
|
||||
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||||
if (config.position.fixed_position) {
|
||||
displayLine = "Fixed GPS";
|
||||
@@ -1046,36 +1028,17 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
||||
if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) {
|
||||
// === Second Row: Last GPS Fix ===
|
||||
if (gpsStatus->getLastFixMillis() > 0) {
|
||||
uint32_t delta = (millis() - gpsStatus->getLastFixMillis()) / 1000; // seconds since last fix
|
||||
uint32_t days = delta / 86400;
|
||||
uint32_t hours = (delta % 86400) / 3600;
|
||||
uint32_t mins = (delta % 3600) / 60;
|
||||
uint32_t secs = delta % 60;
|
||||
|
||||
char buf[32];
|
||||
uint32_t delta = millis() - gpsStatus->getLastFixMillis();
|
||||
char uptimeStr[32];
|
||||
#if defined(USE_EINK)
|
||||
// E-Ink: skip seconds, show only days/hours/mins
|
||||
if (days > 0) {
|
||||
snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours);
|
||||
} else if (hours > 0) {
|
||||
snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "Last: %um", mins);
|
||||
}
|
||||
getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), false);
|
||||
#else
|
||||
// Non E-Ink: include seconds where useful
|
||||
if (days > 0) {
|
||||
snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours);
|
||||
} else if (hours > 0) {
|
||||
snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins);
|
||||
} else if (mins > 0) {
|
||||
snprintf(buf, sizeof(buf), "Last: %um %us", mins, secs);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "Last: %us", secs);
|
||||
}
|
||||
getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), true);
|
||||
#endif
|
||||
|
||||
display->drawString(0, getTextPositions(display)[line++], buf);
|
||||
display->drawString(0, getTextPositions(display)[line++], uptimeStr);
|
||||
} else {
|
||||
display->drawString(0, getTextPositions(display)[line++], "Last: ?");
|
||||
}
|
||||
@@ -1183,6 +1146,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
||||
}
|
||||
#endif
|
||||
#endif // HAS_GPS
|
||||
graphics::drawCommonFooter(display, x, y);
|
||||
}
|
||||
|
||||
#ifdef USERPREFS_OEM_TEXT
|
||||
@@ -1267,7 +1231,13 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
|
||||
if (totalIcons == 0)
|
||||
return;
|
||||
|
||||
const size_t iconsPerPage = (SCREEN_WIDTH + spacing) / (iconSize + spacing);
|
||||
const int navPadding = isHighResolution ? 24 : 12; // padding per side
|
||||
|
||||
int usableWidth = SCREEN_WIDTH - (navPadding * 2);
|
||||
if (usableWidth < iconSize)
|
||||
usableWidth = iconSize;
|
||||
|
||||
const size_t iconsPerPage = usableWidth / (iconSize + spacing);
|
||||
const size_t currentPage = currentFrame / iconsPerPage;
|
||||
const size_t pageStart = currentPage * iconsPerPage;
|
||||
const size_t pageEnd = min(pageStart + iconsPerPage, totalIcons);
|
||||
@@ -1338,6 +1308,47 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
|
||||
display->setColor(WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
// Compact arrow drawer
|
||||
auto drawArrow = [&](bool rightSide) {
|
||||
display->setColor(WHITE);
|
||||
|
||||
const int offset = isHighResolution ? 3 : 1;
|
||||
const int halfH = rectHeight / 2;
|
||||
|
||||
const int top = (y - 2) + (rectHeight - halfH) / 2;
|
||||
const int bottom = top + halfH - 1;
|
||||
const int midY = top + (halfH / 2);
|
||||
|
||||
const int maxW = 4;
|
||||
|
||||
// Determine left X coordinate
|
||||
int baseX = rightSide ? (rectX + rectWidth + offset) : // right arrow
|
||||
(rectX - offset - 1); // left arrow
|
||||
|
||||
for (int yy = top; yy <= bottom; yy++) {
|
||||
int dist = abs(yy - midY);
|
||||
int lineW = maxW - (dist * maxW / (halfH / 2));
|
||||
if (lineW < 1)
|
||||
lineW = 1;
|
||||
|
||||
if (rightSide) {
|
||||
display->drawHorizontalLine(baseX, yy, lineW);
|
||||
} else {
|
||||
display->drawHorizontalLine(baseX - lineW + 1, yy, lineW);
|
||||
}
|
||||
}
|
||||
};
|
||||
// Right arrow
|
||||
if (pageEnd < totalIcons) {
|
||||
drawArrow(true);
|
||||
}
|
||||
|
||||
// Left arrow
|
||||
if (pageStart > 0) {
|
||||
drawArrow(false);
|
||||
}
|
||||
|
||||
// Knock the corners off the square
|
||||
display->setColor(BLACK);
|
||||
display->drawRect(rectX, y - 2, 1, 1);
|
||||
@@ -1372,4 +1383,4 @@ std::string UIRenderer::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t mi
|
||||
|
||||
} // namespace graphics
|
||||
|
||||
#endif // HAS_SCREEN
|
||||
#endif // HAS_SCREEN
|
||||
|
||||
@@ -63,7 +63,7 @@ class UIRenderer
|
||||
static void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
|
||||
static NodeNum currentFavoriteNodeNum;
|
||||
static std::vector<meshtastic_NodeInfoLite *> favoritedNodes;
|
||||
static std::vector<meshtastic_NodeDetail *> favoritedNodes;
|
||||
static void rebuildFavoritedNodes();
|
||||
|
||||
// OEM screens
|
||||
|
||||
@@ -18,6 +18,8 @@ const Emote emotes[] = {
|
||||
{"\U0001F642", Slightly_Smiling, Slightly_Smiling_width, Slightly_Smiling_height}, // 🙂 Slightly Smiling 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
|
||||
{"\U0001F60D", Heart_eyes, Heart_eyes_width, Heart_eyes_height}, // 😍 Heart Eyes
|
||||
{"\U0001F970", heart_smile, heart_smile_width, heart_smile_height}, // 🥰 Smiling Face with Hearts
|
||||
|
||||
// --- Question/Alert ---
|
||||
{"\u2753", question, question_width, question_height}, // ❓ Question Mark
|
||||
@@ -30,11 +32,15 @@ const Emote emotes[] = {
|
||||
{"\U0001F605", haha, haha_width, haha_height}, // 😅 Smiling with Sweat
|
||||
{"\U0001F604", Grinning_SmilingEyes2, Grinning_SmilingEyes2_width,
|
||||
Grinning_SmilingEyes2_height}, // 😄 Grinning Face with Smiling Eyes
|
||||
{"\U0001F62D", Loudly_Crying_Face, Loudly_Crying_Face_width, Loudly_Crying_Face_height}, // 😭 Loudly Crying Face
|
||||
|
||||
// --- Gestures and People ---
|
||||
{"\U0001F44B", wave_icon, wave_icon_width, wave_icon_height}, // 👋 Waving Hand
|
||||
{"\U0001F920", cowboy, cowboy_width, cowboy_height}, // 🤠 Cowboy Hat Face
|
||||
{"\U0001F3A7", deadmau5, deadmau5_width, deadmau5_height}, // 🎧 Headphones
|
||||
{"\U0001F44B", wave_icon, wave_icon_width, wave_icon_height}, // 👋 Waving Hand
|
||||
{"\u270C\uFE0F", peace_sign, peace_sign_width, peace_sign_height}, // ✌️ Victory Hand
|
||||
{"\U0001F596", vulcan_salute, vulcan_salute_width, vulcan_salute_height}, // 🖖 Vulcan Salute
|
||||
{"\U0001F64F", Praying, Praying_width, Praying_height}, // 🙏 Praying Hands
|
||||
{"\U0001F920", cowboy, cowboy_width, cowboy_height}, // 🤠 Cowboy Hat Face
|
||||
{"\U0001F3A7", deadmau5, deadmau5_width, deadmau5_height}, // 🎧 Headphones
|
||||
|
||||
// --- Weather ---
|
||||
{"\u2600", sun, sun_width, sun_height}, // ☀ Sun (without variation selector)
|
||||
@@ -45,8 +51,12 @@ const Emote emotes[] = {
|
||||
|
||||
// --- Misc Faces ---
|
||||
{"\U0001F608", devil, devil_width, devil_height}, // 😈 Smiling Face with Horns
|
||||
{"\U0001F921", clown, clown_width, clown_height}, // 🤡 Clown Face
|
||||
{"\U0001F916", robo, robo_width, robo_height}, // 🤖 Robot Face
|
||||
|
||||
// --- Hearts (Multiple Unicode Aliases) ---
|
||||
{"\u2665", heart, heart_width, heart_height}, // ♥ Black Heart Suit
|
||||
{"\u2665\uFE0F", heart, heart_width, heart_height}, // ♥️ Black Heart Suit (emoji presentation)
|
||||
{"\u2764\uFE0F", heart, heart_width, heart_height}, // ❤️ Red Heart
|
||||
{"\U0001F9E1", heart, heart_width, heart_height}, // 🧡 Orange Heart
|
||||
{"\U00002763", heart, heart_width, heart_height}, // ❣ Heart Exclamation
|
||||
@@ -57,223 +67,166 @@ const Emote emotes[] = {
|
||||
{"\U0001F498", heart, heart_width, heart_height}, // 💘 Heart with Arrow
|
||||
|
||||
// --- Objects ---
|
||||
{"\U0001F4A9", poo, poo_width, poo_height}, // 💩 Pile of Poo
|
||||
{"\U0001F514", bell_icon, bell_icon_width, bell_icon_height} // 🔔 Bell
|
||||
{"\U0001F4A9", poo, poo_width, poo_height}, // 💩 Pile of Poo
|
||||
{"\U0001F514", bell_icon, bell_icon_width, bell_icon_height}, // 🔔 Bell
|
||||
{"\U0001F36A", cookie, cookie_width, cookie_height}, // 🍪 Cookie
|
||||
{"\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
|
||||
#endif
|
||||
};
|
||||
|
||||
const int numEmotes = sizeof(emotes) / sizeof(emotes[0]);
|
||||
|
||||
#ifndef EXCLUDE_EMOJI
|
||||
const unsigned char thumbup[] PROGMEM = {
|
||||
0x00, 0x1C, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x80, 0x09, 0x00, 0x00,
|
||||
0xC0, 0x08, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00,
|
||||
0x0C, 0xCE, 0x7F, 0x00, 0x04, 0x20, 0x80, 0x00, 0x02, 0x20, 0x80, 0x00, 0x02, 0x60, 0xC0, 0x00, 0x01, 0xF8, 0xFF, 0x01,
|
||||
0x01, 0x08, 0x00, 0x01, 0x01, 0x08, 0x00, 0x01, 0x01, 0xF8, 0xFF, 0x00, 0x01, 0x10, 0x80, 0x00, 0x01, 0x18, 0x80, 0x00,
|
||||
0x02, 0x30, 0xC0, 0x00, 0x06, 0xE0, 0x3F, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x38, 0x20, 0x10, 0x00, 0xE0, 0xCF, 0x1F, 0x00,
|
||||
};
|
||||
const unsigned char thumbup[] PROGMEM = {0x00, 0x03, 0x80, 0x04, 0x80, 0x04, 0x40, 0x04, 0x20, 0x02, 0x18,
|
||||
0x02, 0x06, 0x3F, 0x06, 0x40, 0x06, 0x70, 0x06, 0x40, 0x06, 0x70,
|
||||
0x06, 0x40, 0x06, 0x30, 0x08, 0x20, 0xF0, 0x1F, 0x00, 0x00};
|
||||
|
||||
const unsigned char thumbdown[] PROGMEM = {
|
||||
0xE0, 0xCF, 0x1F, 0x00, 0x38, 0x20, 0x10, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x06, 0xE0, 0x3F, 0x00, 0x02, 0x30, 0xC0, 0x00,
|
||||
0x01, 0x18, 0x80, 0x00, 0x01, 0x10, 0x80, 0x00, 0x01, 0xF8, 0xFF, 0x00, 0x01, 0x08, 0x00, 0x01, 0x01, 0x08, 0x00, 0x01,
|
||||
0x01, 0xF8, 0xFF, 0x01, 0x02, 0x60, 0xC0, 0x00, 0x02, 0x20, 0x80, 0x00, 0x04, 0x20, 0x80, 0x00, 0x0C, 0xCE, 0x7F, 0x00,
|
||||
0x18, 0x02, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0xC0, 0x08, 0x00, 0x00,
|
||||
0x80, 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
|
||||
};
|
||||
const unsigned char thumbdown[] PROGMEM = {0xF0, 0x1F, 0x08, 0x20, 0x06, 0x30, 0x06, 0x40, 0x06, 0x70, 0x06,
|
||||
0x40, 0x06, 0x70, 0x06, 0x40, 0x06, 0x3F, 0x18, 0x02, 0x20, 0x02,
|
||||
0x40, 0x04, 0x80, 0x04, 0x80, 0x04, 0x00, 0x03, 0x00, 0x00};
|
||||
|
||||
const unsigned char Smiling_Eyes[] PROGMEM = {
|
||||
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
|
||||
0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xff, 0xff, 0xcf, 0xfc, 0xff, 0xff, 0xcf,
|
||||
0x7e, 0xf8, 0xc3, 0xdf, 0x3e, 0xf0, 0x81, 0xdf, 0xbf, 0xf7, 0xbd, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0x3f, 0xff,
|
||||
0x6f, 0xff, 0xdf, 0xfe, 0x6f, 0xff, 0xdf, 0xfe, 0x9f, 0xff, 0x3f, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf,
|
||||
0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
|
||||
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x07, 0xc0};
|
||||
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,
|
||||
0x04, 0x20, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char Grinning[] PROGMEM = {
|
||||
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
|
||||
0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf9, 0xf3, 0xcf, 0xfc, 0xf0, 0xe1, 0xcf,
|
||||
0xfe, 0xf0, 0xe1, 0xdf, 0xfe, 0xf0, 0xe1, 0xdf, 0xff, 0xf0, 0xe1, 0xff, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x80, 0xff, 0xbe, 0xff, 0xbf, 0xdf, 0x7e, 0x00, 0xc0, 0xdf,
|
||||
0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
|
||||
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
|
||||
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,
|
||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char Slightly_Smiling[] PROGMEM = {
|
||||
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
|
||||
0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf9, 0xf3, 0xcf, 0xfc, 0xf0, 0xe1, 0xcf,
|
||||
0xfe, 0xf0, 0xe1, 0xdf, 0xfe, 0xf0, 0xe1, 0xdf, 0xff, 0xf0, 0xe1, 0xff, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf,
|
||||
0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
|
||||
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
|
||||
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,
|
||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char Winking_Face[] PROGMEM = {
|
||||
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
|
||||
0xf0, 0xf0, 0xff, 0xc3, 0x78, 0xef, 0xc3, 0xc7, 0xb8, 0xdf, 0xbd, 0xcf, 0xfc, 0xf9, 0x7f, 0xcf, 0xfc, 0xf0, 0xff, 0xcf,
|
||||
0xfe, 0xf0, 0xc3, 0xdf, 0xfe, 0xf0, 0x81, 0xdf, 0xff, 0xf0, 0xbf, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0x7e, 0xff, 0xdf, 0xdf,
|
||||
0x7c, 0xff, 0xdf, 0xcf, 0xfc, 0xfe, 0xef, 0xcf, 0xf8, 0xf9, 0xf7, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
|
||||
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x07, 0xc0};
|
||||
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,
|
||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char Grinning_Smiling_Eyes[] PROGMEM = {
|
||||
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
|
||||
0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0xfc, 0xf8, 0xe3, 0xcf, 0x7c, 0xf7, 0xdd, 0xcf,
|
||||
0xbe, 0xef, 0xbe, 0xdf, 0xbe, 0xef, 0xbe, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1f, 0x00, 0x00, 0xff, 0x5e, 0x55, 0x55, 0xdf, 0x5e, 0x55, 0x55, 0xdf,
|
||||
0x3c, 0x00, 0x80, 0xcf, 0x7c, 0x55, 0xd5, 0xcf, 0xf8, 0x54, 0xe5, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
|
||||
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
|
||||
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,
|
||||
0x24, 0x24, 0xC4, 0x23, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char question[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, 0xFF, 0x01, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0xE0, 0xFF, 0x07, 0x00,
|
||||
0xE0, 0xC3, 0x0F, 0x00, 0xF0, 0x81, 0x0F, 0x00, 0xF0, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x80, 0x0F, 0x00,
|
||||
0x00, 0xC0, 0x0F, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x7C, 0x00, 0x00,
|
||||
0x00, 0x3C, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
const unsigned char heart_smile[] PROGMEM = {0x00, 0x00, 0x6C, 0x07, 0x7C, 0x18, 0x7C, 0x20, 0x38, 0x24, 0x52,
|
||||
0x0A, 0x02, 0xD8, 0x02, 0xF8, 0x22, 0xFC, 0x20, 0x74, 0xDB, 0x23,
|
||||
0x1F, 0x00, 0x1F, 0x20, 0x0E, 0x18, 0xE4, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char bang[] PROGMEM = {
|
||||
0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x07, 0xF8, 0x3F, 0xFF, 0x07, 0xF8, 0x3F,
|
||||
0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F,
|
||||
0xFE, 0x03, 0xF0, 0x1F, 0xFE, 0x03, 0xF0, 0x1F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F,
|
||||
0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x01, 0xE0, 0x0F, 0xFC, 0x01, 0xE0, 0x0F,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xC0, 0x03, 0xFC, 0x03, 0xF0, 0x0F, 0xFE, 0x03, 0xF0, 0x1F,
|
||||
0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFC, 0x03, 0xF0, 0x0F, 0xF8, 0x01, 0xE0, 0x07,
|
||||
};
|
||||
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,
|
||||
0x24, 0x24, 0xC4, 0x23, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char haha[] PROGMEM = {
|
||||
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0xe0, 0xf9, 0xf3, 0xc0,
|
||||
0xf0, 0xfe, 0xef, 0xc1, 0x38, 0xff, 0x9f, 0xc3, 0xd8, 0xff, 0x7f, 0xc3, 0xfc, 0xf8, 0xe3, 0xc7, 0x7c, 0xf7, 0xdd, 0xcf,
|
||||
0xbe, 0xef, 0xbe, 0xcf, 0xfe, 0xff, 0xff, 0xcf, 0xef, 0xff, 0xff, 0xde, 0xe7, 0xff, 0xff, 0xdc, 0xeb, 0xff, 0xff, 0xda,
|
||||
0xed, 0xff, 0xff, 0xd6, 0xee, 0xff, 0xff, 0xce, 0x36, 0x00, 0x80, 0xcd, 0xb8, 0xff, 0xbf, 0xc3, 0x7e, 0x00, 0xc0, 0xdf,
|
||||
0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
|
||||
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
|
||||
const unsigned char question[] PROGMEM = {0xE0, 0x07, 0x10, 0x08, 0x08, 0x10, 0x88, 0x11, 0x48, 0x12, 0x48,
|
||||
0x12, 0x48, 0x12, 0x30, 0x11, 0x80, 0x08, 0x40, 0x04, 0x40, 0x02,
|
||||
0xC0, 0x03, 0x00, 0x00, 0xC0, 0x03, 0x40, 0x02, 0x80, 0x01};
|
||||
|
||||
const unsigned char ROFL[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0xc0, 0x00, 0xfc, 0x07, 0xc0, 0x00, 0xff, 0x1f, 0xc0, 0x80, 0xff, 0x7f, 0xc0, 0xc0, 0xff, 0xff, 0xc0,
|
||||
0xe0, 0x9f, 0xff, 0xc1, 0xf0, 0x9f, 0xff, 0xc0, 0xf8, 0x9f, 0x7f, 0xcb, 0xf8, 0x9f, 0xbf, 0xcb, 0xfc, 0x9f, 0xdf, 0xdb,
|
||||
0xfc, 0x1f, 0x08, 0xdc, 0xfe, 0x1f, 0xf8, 0xfe, 0xfe, 0xff, 0xff, 0xfe, 0x1e, 0xf0, 0x7f, 0xfe, 0x1e, 0xf0, 0xbf, 0xfe,
|
||||
0xfe, 0xf3, 0xdf, 0xfe, 0xfe, 0xf3, 0x6f, 0xfe, 0xfe, 0xf3, 0x37, 0xfe, 0xfe, 0xeb, 0x1b, 0xfe, 0xfc, 0xef, 0x0d, 0xde,
|
||||
0xfc, 0xe7, 0x06, 0xcf, 0xf8, 0x6b, 0x83, 0xcf, 0xf8, 0x0d, 0xc0, 0xc7, 0xf0, 0xed, 0xff, 0xc7, 0xe0, 0xee, 0xff, 0xc3,
|
||||
0xc0, 0xee, 0xff, 0xc1, 0x80, 0xee, 0xff, 0xc0, 0x00, 0xe6, 0x3f, 0xc0, 0x00, 0xf0, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0xc0};
|
||||
const unsigned char bang[] PROGMEM = {0x30, 0x0C, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x48,
|
||||
0x12, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x48, 0x12, 0x30, 0x0C,
|
||||
0x00, 0x00, 0x30, 0x0C, 0x48, 0x12, 0x30, 0x0C, 0x00, 0x00};
|
||||
|
||||
const unsigned char Smiling_Closed_Eyes[] PROGMEM = {
|
||||
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0xff, 0xc0, 0xe0, 0xff, 0xff, 0xc1,
|
||||
0xf0, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xcf, 0x7c, 0xfe, 0xcf, 0xcf, 0xfc, 0xfc, 0xe7, 0xcf,
|
||||
0xfe, 0xf9, 0xf3, 0xdf, 0xfe, 0xf3, 0xf9, 0xdf, 0xff, 0xf9, 0xf3, 0xff, 0xff, 0xfc, 0xe7, 0xff, 0x7f, 0xfe, 0xcf, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x80, 0xff, 0xbe, 0xff, 0xbf, 0xdf, 0x7e, 0x00, 0xc0, 0xdf,
|
||||
0x7c, 0x00, 0xc0, 0xcf, 0xfc, 0x00, 0xe0, 0xcf, 0xf8, 0x01, 0xf0, 0xc7, 0xf8, 0x03, 0xf8, 0xc7, 0xf0, 0xff, 0xff, 0xc3,
|
||||
0xe0, 0xff, 0xff, 0xc1, 0xc0, 0xff, 0xff, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
|
||||
const unsigned char haha[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x24, 0x24, 0x52,
|
||||
0x4A, 0x0A, 0x50, 0x0E, 0x70, 0xF2, 0x4F, 0x12, 0x48, 0x32, 0x44,
|
||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char Grinning_SmilingEyes2[] PROGMEM = {
|
||||
0x00, 0xf8, 0x03, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0xe0, 0xff, 0xff, 0xc0,
|
||||
0xf0, 0xff, 0xff, 0xc1, 0xf8, 0xff, 0xff, 0xc3, 0xf8, 0xff, 0xff, 0xc3, 0xfc, 0xf8, 0xe3, 0xc7, 0x7c, 0xf7, 0xdd, 0xc7,
|
||||
0xbe, 0xef, 0xbe, 0xcf, 0xfe, 0xff, 0xff, 0xcf, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf,
|
||||
0xff, 0xff, 0xff, 0xdf, 0xff, 0xff, 0xff, 0xdf, 0x3f, 0x00, 0x80, 0xdf, 0xbe, 0xff, 0xbf, 0xcf, 0x7e, 0x00, 0xc0, 0xcf,
|
||||
0x7c, 0x00, 0xc0, 0xc7, 0xfc, 0x00, 0xe0, 0xc7, 0xf8, 0x01, 0xf0, 0xc3, 0xf8, 0x03, 0xf8, 0xc3, 0xf0, 0xff, 0xff, 0xc1,
|
||||
0xe0, 0xff, 0xff, 0xc0, 0xc0, 0xff, 0x7f, 0xc0, 0x80, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0};
|
||||
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,
|
||||
0xE4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char wave_icon[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0xc0, 0xc1, 0x00, 0x00, 0x00, 0xc7,
|
||||
0x00, 0x00, 0x1e, 0xcc, 0x00, 0x00, 0x30, 0xc8, 0x00, 0x00, 0x60, 0xd8, 0x00, 0x08, 0xc0, 0xd0, 0x00, 0x1a, 0x81, 0xd1,
|
||||
0x00, 0x36, 0x03, 0xd3, 0x80, 0x6d, 0x06, 0xd2, 0x00, 0xdb, 0x0c, 0xc2, 0x80, 0xb6, 0x1d, 0xc0, 0x80, 0x6d, 0x1f, 0xc0,
|
||||
0x00, 0xdb, 0x3f, 0xc0, 0x00, 0xf6, 0x7f, 0xc0, 0x00, 0xfc, 0x7f, 0xc0, 0x08, 0xf8, 0x7f, 0xc0, 0x48, 0xf0, 0x7f, 0xc0,
|
||||
0x48, 0xe0, 0x7f, 0xc0, 0xc8, 0xc0, 0x3f, 0xc0, 0x98, 0x81, 0x1f, 0xc0, 0x10, 0x03, 0x00, 0xc0, 0x30, 0x0e, 0x00, 0xc0,
|
||||
0x20, 0x38, 0x00, 0xc0, 0xe0, 0x00, 0x00, 0xc0, 0x80, 0x07, 0x00, 0xc0, 0x00, 0x1e, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0};
|
||||
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,
|
||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char cowboy[] PROGMEM = {
|
||||
0x00, 0x0c, 0x0c, 0xc0, 0x00, 0x02, 0x10, 0xc0, 0x00, 0x01, 0x20, 0xc0, 0xbc, 0x00, 0x40, 0xcf, 0xc2, 0x01, 0xe0, 0xd0,
|
||||
0x01, 0x01, 0x20, 0xe0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0,
|
||||
0xc1, 0x3f, 0xff, 0xe0, 0xe1, 0xff, 0xff, 0xe1, 0xf2, 0xf3, 0xf3, 0xd3, 0xf4, 0xf1, 0xe3, 0xcb, 0xfc, 0xf1, 0xe3, 0xc7,
|
||||
0xf8, 0xf1, 0xe3, 0xc7, 0xf8, 0xf1, 0xe3, 0xc7, 0xf8, 0xfb, 0xf7, 0xc7, 0xf8, 0xff, 0xff, 0xc7, 0xf8, 0xff, 0xff, 0xc7,
|
||||
0x70, 0xf8, 0x8f, 0xc3, 0x70, 0x03, 0xb0, 0xc3, 0x70, 0xfe, 0xbf, 0xc3, 0x60, 0x00, 0x80, 0xc1, 0xc0, 0x00, 0xc0, 0xc0,
|
||||
0x80, 0x01, 0x60, 0xc0, 0x00, 0x07, 0x38, 0xc0, 0x00, 0xfe, 0x1f, 0xc0, 0x00, 0xf0, 0x03, 0xc0, 0x00, 0x00, 0x00, 0xc0};
|
||||
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,
|
||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char deadmau5[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00,
|
||||
0x00, 0xFC, 0x03, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x80, 0xFF, 0x0F, 0x00, 0xC0, 0xFF, 0xFF, 0x00, 0xE0, 0xFF, 0x3F, 0x00,
|
||||
0xE0, 0xFF, 0xFF, 0x01, 0xF0, 0xFF, 0x7F, 0x00, 0xF0, 0xFF, 0xFF, 0x03, 0xF8, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x07,
|
||||
0xFC, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0xFE, 0xFF, 0xFF, 0x00,
|
||||
0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xE0, 0xFF, 0x3F, 0xFC,
|
||||
0x0F, 0xFF, 0x7F, 0x00, 0xC0, 0xFF, 0x1F, 0xF8, 0x0F, 0xFC, 0x3F, 0x00, 0x80, 0xFF, 0x0F, 0xF8, 0x1F, 0xFC, 0x1F, 0x00,
|
||||
0x00, 0xFF, 0x0F, 0xFC, 0x3F, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x1F, 0xFF, 0xFF, 0xFE, 0x01, 0x00, 0x00, 0x00, 0xFC, 0xFF,
|
||||
0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,
|
||||
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0xC0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x80, 0x07, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
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,
|
||||
0x54, 0x2A, 0x94, 0x29, 0x18, 0x18, 0xF0, 0x0F, 0x00, 0x00};
|
||||
|
||||
const unsigned char sun[] PROGMEM = {
|
||||
0x00, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x30, 0xC0, 0x00, 0x03,
|
||||
0x70, 0x00, 0x80, 0x03, 0xF0, 0x00, 0xC0, 0x03, 0xF0, 0xF8, 0xC7, 0x03, 0xE0, 0xFC, 0xCF, 0x01, 0x00, 0xFE, 0x1F, 0x00,
|
||||
0x00, 0xFF, 0x3F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x8E, 0xFF, 0x7F, 0x1C, 0x9F, 0xFF, 0x7F, 0x3E,
|
||||
0x9F, 0xFF, 0x7F, 0x3E, 0x8E, 0xFF, 0x7F, 0x1C, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0x3F, 0x00,
|
||||
0x00, 0xFE, 0x1F, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0xC0, 0xF9, 0xE7, 0x00, 0xE0, 0x01, 0xE0, 0x01, 0xF0, 0x01, 0xE0, 0x03,
|
||||
0xF0, 0xC0, 0xC0, 0x03, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x00, 0x00,
|
||||
};
|
||||
const unsigned char wave_icon[] PROGMEM = {0x00, 0x00, 0xC0, 0x18, 0x30, 0x21, 0x48, 0x5A, 0x94, 0x64, 0x24,
|
||||
0x25, 0x4A, 0x24, 0x12, 0x44, 0x22, 0x44, 0x04, 0x40, 0x08, 0x40,
|
||||
0x12, 0x40, 0x22, 0x20, 0xC4, 0x10, 0x18, 0x0F, 0x00, 0x00};
|
||||
|
||||
const unsigned char rain[] PROGMEM = {
|
||||
0xC0, 0x0F, 0xC0, 0x00, 0x40, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x03, 0x38, 0x00,
|
||||
0x00, 0x0E, 0x0C, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0x20,
|
||||
0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x30, 0x02, 0x00,
|
||||
0x00, 0x10, 0x06, 0x00, 0x00, 0x08, 0xFC, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x01, 0x80, 0x00, 0x01, 0x00,
|
||||
0xC0, 0xC0, 0x81, 0x03, 0xA0, 0x60, 0xC1, 0x03, 0x90, 0x20, 0x41, 0x01, 0xF0, 0xE0, 0xC0, 0x01, 0x60, 0x4C,
|
||||
0x98, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00, 0x0B, 0x12, 0x00, 0x00, 0x09, 0x1A, 0x00, 0x00, 0x06, 0x0E, 0x00,
|
||||
};
|
||||
const unsigned char cowboy[] PROGMEM = {0x70, 0x0E, 0x8F, 0xF1, 0x11, 0x88, 0x21, 0x84, 0xC2, 0x43, 0x1E,
|
||||
0x78, 0xE2, 0x47, 0x42, 0x42, 0x12, 0x48, 0x12, 0x48, 0x22, 0x44,
|
||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char cloud[] PROGMEM = {
|
||||
0x00, 0x80, 0x07, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0x70, 0x30, 0x00, 0x00, 0x10, 0x60, 0x00, 0x80, 0x1F, 0x40, 0x00,
|
||||
0xC0, 0x0F, 0xC0, 0x00, 0xC0, 0x00, 0x80, 0x00, 0x60, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x01,
|
||||
0x20, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x08, 0x06, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x10,
|
||||
0x02, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20,
|
||||
0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x10,
|
||||
0x02, 0x00, 0x00, 0x10, 0x06, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x0C, 0xFC, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x03,
|
||||
};
|
||||
const unsigned char deadmau5[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0xE4, 0x27, 0x12, 0x48, 0x0A,
|
||||
0x50, 0x0E, 0x70, 0x11, 0x88, 0x19, 0x98, 0x19, 0x98, 0x19, 0x98,
|
||||
0x19, 0x98, 0x19, 0x98, 0x11, 0x88, 0x0E, 0x70, 0x00, 0x00};
|
||||
|
||||
const unsigned char fog[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x3C, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0x87, 0xC7, 0xC3, 0x01, 0x03, 0xFE, 0x80, 0x01,
|
||||
0x00, 0x38, 0x00, 0x00, 0xFC, 0x00, 0x7E, 0x00, 0xFF, 0x83, 0xFF, 0x01, 0x03, 0xFF, 0x81, 0x01, 0x00, 0x7C, 0x00, 0x00,
|
||||
0xF8, 0x00, 0x3E, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0x87, 0xC7, 0xC3, 0x01, 0x03, 0xFE, 0x80, 0x01, 0x00, 0x38, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
const unsigned char sun[] PROGMEM = {0x00, 0x00, 0x80, 0x01, 0xEC, 0x37, 0xFC, 0x3F, 0xF8, 0x1F, 0xFC,
|
||||
0x3F, 0xFE, 0x7F, 0xFC, 0x3F, 0xFC, 0x3F, 0xFE, 0x7F, 0xFC, 0x3F,
|
||||
0xF8, 0x1F, 0xFC, 0x3F, 0xEC, 0x37, 0x80, 0x01, 0x00, 0x00};
|
||||
|
||||
const unsigned char devil[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xe0, 0x03, 0x00, 0x00, 0xf0, 0x0f, 0xfc, 0x0f, 0xfc,
|
||||
0x3f, 0xff, 0x3f, 0xff, 0xfe, 0xff, 0xff, 0xdf, 0xfe, 0xff, 0xff, 0xdf, 0xfe, 0xff, 0xff, 0xdf, 0xfc, 0xff, 0xff, 0xcf,
|
||||
0xfc, 0xff, 0xff, 0xcf, 0xf8, 0xff, 0xff, 0xc7, 0xf0, 0xff, 0xff, 0xc3, 0xf0, 0xff, 0xff, 0xc3, 0xf0, 0xf1, 0xe3, 0xc3,
|
||||
0xf0, 0xe7, 0xf9, 0xc3, 0xf0, 0xe7, 0xf9, 0xc3, 0xf0, 0xe3, 0xf1, 0xc3, 0xf0, 0xe3, 0xf1, 0xc3, 0xf0, 0xe7, 0xf9, 0xc3,
|
||||
0xf0, 0xff, 0xff, 0xc3, 0xe0, 0xfd, 0xef, 0xc1, 0xe0, 0xf3, 0xf3, 0xc1, 0xc0, 0x07, 0xf8, 0xc0, 0x80, 0x1f, 0x7e, 0xc0,
|
||||
0x00, 0xff, 0x3f, 0xc0, 0x00, 0xfe, 0x0f, 0xc0, 0x00, 0xf8, 0x03, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0};
|
||||
const unsigned char rain[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x38, 0x1F, 0xFC, 0x3F, 0xFE,
|
||||
0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0x00, 0x00, 0x48, 0x12,
|
||||
0x48, 0x12, 0x24, 0x09, 0x24, 0x09, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
const unsigned char heart[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0xF0, 0x00, 0xF8, 0x0F, 0xFC, 0x07, 0xFC, 0x1F, 0x06, 0x0E, 0xFE, 0x3F, 0x03, 0x18,
|
||||
0xFE, 0xFF, 0x7F, 0x10, 0xFF, 0xFF, 0xFF, 0x31, 0xFF, 0xFF, 0xFF, 0x33, 0xFF, 0xFF, 0xFF, 0x37, 0xFF, 0xFF, 0xFF, 0x37,
|
||||
0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFE, 0xFF, 0xFF, 0x1F, 0xFE, 0xFF, 0xFF, 0x1F,
|
||||
0xFC, 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x03, 0xF0, 0xFF, 0xFF, 0x03,
|
||||
0xE0, 0xFF, 0xFF, 0x01, 0xC0, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x00, 0xFE, 0x1F, 0x00,
|
||||
0x00, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x07, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00,
|
||||
};
|
||||
const unsigned char cloud[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x38, 0x1F, 0xFC,
|
||||
0x3F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0xF8, 0x1F,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
const unsigned char poo[] PROGMEM = {
|
||||
0x00, 0x1c, 0x00, 0xc0, 0x00, 0x7c, 0x00, 0xc0, 0x00, 0xfc, 0x00, 0xc0, 0x00, 0x7c, 0x03, 0xc0, 0x00, 0xbe, 0x03, 0xc0,
|
||||
0x00, 0xdf, 0x0f, 0xc0, 0x80, 0xcf, 0x0f, 0xc0, 0xc0, 0xf1, 0x0f, 0xc0, 0x60, 0xfc, 0x0f, 0xc0, 0x30, 0xff, 0x07, 0xc0,
|
||||
0x90, 0xff, 0x3b, 0xc0, 0xc0, 0xff, 0x7d, 0xc0, 0xf8, 0xff, 0xfc, 0xc0, 0xf8, 0x3f, 0xf0, 0xc0, 0x78, 0x88, 0xc0, 0xc0,
|
||||
0x20, 0xe3, 0x18, 0xc0, 0x98, 0xe7, 0xbc, 0xc1, 0x9c, 0x64, 0xa4, 0xc3, 0x9e, 0x64, 0xa4, 0xc7, 0xbe, 0xe4, 0xa4, 0xc7,
|
||||
0xbc, 0x27, 0xbc, 0xc7, 0x38, 0x03, 0xd9, 0xc3, 0x00, 0xf0, 0x63, 0xc0, 0xf8, 0xfc, 0x3f, 0xcf, 0xfc, 0xff, 0x87, 0xdf,
|
||||
0xfe, 0xff, 0xe0, 0xdf, 0xfc, 0x1f, 0xfe, 0xdf, 0xf8, 0x07, 0xf8, 0xcf, 0xf0, 0x03, 0xe0, 0xc7, 0x00, 0x00, 0x00, 0xc0};
|
||||
const unsigned char fog[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x88, 0x88, 0x54, 0x55, 0x22, 0x22, 0x00,
|
||||
0x00, 0x44, 0x44, 0xAA, 0x2A, 0x11, 0x11, 0x00, 0x00, 0x88, 0x88,
|
||||
0x54, 0x55, 0x22, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
const unsigned char bell_icon[] PROGMEM = {
|
||||
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b11110000,
|
||||
0b00000011, 0b00000000, 0b00000000, 0b11111100, 0b00001111, 0b00000000, 0b00000000, 0b00001111, 0b00111100, 0b00000000,
|
||||
0b00000000, 0b00000011, 0b00110000, 0b00000000, 0b10000000, 0b00000001, 0b01100000, 0b00000000, 0b11000000, 0b00000000,
|
||||
0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000,
|
||||
0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000,
|
||||
0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b11000000, 0b00000000,
|
||||
0b11000000, 0b00000000, 0b11000000, 0b00000000, 0b01000000, 0b00000000, 0b10000000, 0b00000000, 0b01100000, 0b00000000,
|
||||
0b10000000, 0b00000001, 0b01110000, 0b00000000, 0b10000000, 0b00000011, 0b00110000, 0b00000000, 0b00000000, 0b00000011,
|
||||
0b00011000, 0b00000000, 0b00000000, 0b00000110, 0b11110000, 0b11111111, 0b11111111, 0b00000011, 0b00000000, 0b00001100,
|
||||
0b00001100, 0b00000000, 0b00000000, 0b00011000, 0b00000110, 0b00000000, 0b00000000, 0b11111000, 0b00000111, 0b00000000,
|
||||
0b00000000, 0b11100000, 0b00000001, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000,
|
||||
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000};
|
||||
const unsigned char devil[] PROGMEM = {0x06, 0x60, 0xCA, 0x53, 0x32, 0x4C, 0x22, 0x44, 0x44, 0x22, 0x3A,
|
||||
0x5C, 0x32, 0x4C, 0x52, 0x4A, 0x72, 0x4E, 0x02, 0x40, 0x22, 0x44,
|
||||
0xC4, 0x23, 0x04, 0x20, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char heart[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x3C, 0x3C, 0x7E, 0x7E, 0xFE, 0x7F, 0xFE,
|
||||
0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x3F, 0xF8, 0x1F, 0xF8, 0x1F,
|
||||
0xF0, 0x0F, 0xE0, 0x07, 0xC0, 0x03, 0x80, 0x01, 0x00, 0x00};
|
||||
|
||||
const unsigned char poo[] PROGMEM = {0x00, 0x00, 0x80, 0x01, 0x40, 0x02, 0x20, 0x04, 0x10, 0x04, 0xF0,
|
||||
0x08, 0x10, 0x10, 0x48, 0x12, 0x08, 0x18, 0xE8, 0x21, 0x1C, 0x40,
|
||||
0x42, 0x42, 0x82, 0x41, 0x02, 0x30, 0xFC, 0x0F, 0x00, 0x00};
|
||||
|
||||
const unsigned char bell_icon[] PROGMEM = {0x00, 0x00, 0x80, 0x01, 0x80, 0x01, 0xE0, 0x07, 0xF0, 0x0F, 0xF0,
|
||||
0x0F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFC, 0x3F,
|
||||
0xFC, 0x3F, 0xFE, 0x7F, 0xFE, 0x7F, 0x80, 0x01, 0x00, 0x00};
|
||||
|
||||
const unsigned char cookie[] PROGMEM = {0x00, 0x00, 0xE0, 0x07, 0x18, 0x18, 0x04, 0x20, 0x34, 0x22, 0x32,
|
||||
0x40, 0x02, 0x58, 0x82, 0x5B, 0x92, 0x43, 0x82, 0x43, 0x02, 0x40,
|
||||
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,
|
||||
0x1F, 0xFE, 0x3E, 0x7E, 0x3E, 0x3E, 0x7C, 0x1E, 0x78, 0x1E, 0x70,
|
||||
0x1C, 0x70, 0x1C, 0x70, 0x38, 0x38, 0x30, 0x38, 0x60, 0x0C};
|
||||
|
||||
const unsigned char peace_sign[] PROGMEM = {0xC0, 0x30, 0x40, 0x29, 0x40, 0x25, 0x40, 0x15, 0x40, 0x12, 0x38,
|
||||
0x0A, 0x54, 0x68, 0x54, 0x58, 0x54, 0x44, 0x3C, 0x22, 0x04, 0x22,
|
||||
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,
|
||||
0x09, 0x98, 0x19, 0x94, 0x29, 0xA4, 0x25, 0xA4, 0x25, 0x84, 0x21,
|
||||
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,
|
||||
0x1F, 0x80, 0x3F, 0xE0, 0xFF, 0x80, 0x3F, 0x10, 0x1F, 0x10, 0x0E,
|
||||
0x38, 0x04, 0xFE, 0x04, 0x38, 0x00, 0x10, 0x00, 0x10, 0x00};
|
||||
|
||||
const unsigned char clown[] PROGMEM = {0x00, 0x00, 0xEE, 0x77, 0x1A, 0x58, 0x06, 0x60, 0x24, 0x24, 0x72,
|
||||
0x4E, 0x22, 0x44, 0x82, 0x41, 0x82, 0x41, 0x1A, 0x58, 0xF2, 0x4F,
|
||||
0x14, 0x28, 0xE4, 0x27, 0x18, 0x18, 0xE0, 0x07, 0x00, 0x00};
|
||||
|
||||
const unsigned char robo[] PROGMEM = {0x80, 0x01, 0xC0, 0x03, 0x80, 0x01, 0xFC, 0x3F, 0x04, 0x20, 0x74,
|
||||
0x2E, 0x52, 0x4A, 0x72, 0x4E, 0x02, 0x40, 0x02, 0x40, 0xA2, 0x4A,
|
||||
0x52, 0x45, 0x04, 0x20, 0x04, 0x20, 0xFC, 0x3F, 0x00, 0x00};
|
||||
|
||||
const unsigned char hole[] PROGMEM = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x0F, 0x3C, 0x3C,
|
||||
0x06, 0x60, 0x0C, 0x30, 0xF0, 0x0F, 0x00, 0x00, 0x00, 0x00};
|
||||
|
||||
const unsigned char bowling[] PROGMEM = {0x00, 0x38, 0x00, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x28, 0x00,
|
||||
0x38, 0x00, 0x28, 0x78, 0x44, 0x84, 0x82, 0x22, 0x83, 0x52, 0x83,
|
||||
0x02, 0x83, 0x02, 0x45, 0x84, 0x44, 0x78, 0x38, 0x00, 0x00};
|
||||
|
||||
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,
|
||||
0x04, 0x12, 0x08, 0x10, 0x10, 0x08, 0xE0, 0x07, 0x00, 0x00};
|
||||
#endif
|
||||
|
||||
} // namespace graphics
|
||||
|
||||
@@ -17,98 +17,150 @@ extern const int numEmotes;
|
||||
|
||||
#ifndef EXCLUDE_EMOJI
|
||||
// === Emote Bitmaps ===
|
||||
#define thumbs_height 25
|
||||
#define thumbs_width 25
|
||||
#define thumbs_height 16
|
||||
#define thumbs_width 16
|
||||
extern const unsigned char thumbup[] PROGMEM;
|
||||
extern const unsigned char thumbdown[] PROGMEM;
|
||||
|
||||
#define Smiling_Eyes_height 30
|
||||
#define Smiling_Eyes_width 30
|
||||
#define Smiling_Eyes_height 16
|
||||
#define Smiling_Eyes_width 16
|
||||
extern const unsigned char Smiling_Eyes[] PROGMEM;
|
||||
|
||||
#define Grinning_height 30
|
||||
#define Grinning_width 30
|
||||
#define Grinning_height 16
|
||||
#define Grinning_width 16
|
||||
extern const unsigned char Grinning[] PROGMEM;
|
||||
|
||||
#define Slightly_Smiling_height 30
|
||||
#define Slightly_Smiling_width 30
|
||||
#define Slightly_Smiling_height 16
|
||||
#define Slightly_Smiling_width 16
|
||||
extern const unsigned char Slightly_Smiling[] PROGMEM;
|
||||
|
||||
#define Winking_Face_height 30
|
||||
#define Winking_Face_width 30
|
||||
#define Winking_Face_height 16
|
||||
#define Winking_Face_width 16
|
||||
extern const unsigned char Winking_Face[] PROGMEM;
|
||||
|
||||
#define Grinning_Smiling_Eyes_height 30
|
||||
#define Grinning_Smiling_Eyes_width 30
|
||||
#define Grinning_Smiling_Eyes_height 16
|
||||
#define Grinning_Smiling_Eyes_width 16
|
||||
extern const unsigned char Grinning_Smiling_Eyes[] PROGMEM;
|
||||
|
||||
#define question_height 25
|
||||
#define question_width 25
|
||||
#define heart_smile_height 16
|
||||
#define heart_smile_width 16
|
||||
extern const unsigned char heart_smile[] PROGMEM;
|
||||
|
||||
#define Heart_eyes_height 16
|
||||
#define Heart_eyes_width 16
|
||||
extern const unsigned char Heart_eyes[] PROGMEM;
|
||||
|
||||
#define question_height 16
|
||||
#define question_width 16
|
||||
extern const unsigned char question[] PROGMEM;
|
||||
|
||||
#define bang_height 30
|
||||
#define bang_width 30
|
||||
#define bang_height 16
|
||||
#define bang_width 16
|
||||
extern const unsigned char bang[] PROGMEM;
|
||||
|
||||
#define haha_height 30
|
||||
#define haha_width 30
|
||||
#define haha_height 16
|
||||
#define haha_width 16
|
||||
extern const unsigned char haha[] PROGMEM;
|
||||
|
||||
#define ROFL_height 30
|
||||
#define ROFL_width 30
|
||||
#define ROFL_height 16
|
||||
#define ROFL_width 16
|
||||
extern const unsigned char ROFL[] PROGMEM;
|
||||
|
||||
#define Smiling_Closed_Eyes_height 30
|
||||
#define Smiling_Closed_Eyes_width 30
|
||||
#define Smiling_Closed_Eyes_height 16
|
||||
#define Smiling_Closed_Eyes_width 16
|
||||
extern const unsigned char Smiling_Closed_Eyes[] PROGMEM;
|
||||
|
||||
#define Grinning_SmilingEyes2_height 30
|
||||
#define Grinning_SmilingEyes2_width 30
|
||||
#define Grinning_SmilingEyes2_height 16
|
||||
#define Grinning_SmilingEyes2_width 16
|
||||
extern const unsigned char Grinning_SmilingEyes2[] PROGMEM;
|
||||
|
||||
#define wave_icon_height 30
|
||||
#define wave_icon_width 30
|
||||
#define Loudly_Crying_Face_height 16
|
||||
#define Loudly_Crying_Face_width 16
|
||||
extern const unsigned char Loudly_Crying_Face[] PROGMEM;
|
||||
|
||||
#define wave_icon_height 16
|
||||
#define wave_icon_width 16
|
||||
extern const unsigned char wave_icon[] PROGMEM;
|
||||
|
||||
#define cowboy_height 30
|
||||
#define cowboy_width 30
|
||||
#define cowboy_height 16
|
||||
#define cowboy_width 16
|
||||
extern const unsigned char cowboy[] PROGMEM;
|
||||
|
||||
#define deadmau5_height 30
|
||||
#define deadmau5_width 60
|
||||
#define deadmau5_height 16
|
||||
#define deadmau5_width 16
|
||||
extern const unsigned char deadmau5[] PROGMEM;
|
||||
|
||||
#define sun_height 30
|
||||
#define sun_width 30
|
||||
#define sun_height 16
|
||||
#define sun_width 16
|
||||
extern const unsigned char sun[] PROGMEM;
|
||||
|
||||
#define rain_height 30
|
||||
#define rain_width 30
|
||||
#define rain_height 16
|
||||
#define rain_width 16
|
||||
extern const unsigned char rain[] PROGMEM;
|
||||
|
||||
#define cloud_height 30
|
||||
#define cloud_width 30
|
||||
#define cloud_height 16
|
||||
#define cloud_width 16
|
||||
extern const unsigned char cloud[] PROGMEM;
|
||||
|
||||
#define fog_height 25
|
||||
#define fog_width 25
|
||||
#define fog_height 16
|
||||
#define fog_width 16
|
||||
extern const unsigned char fog[] PROGMEM;
|
||||
|
||||
#define devil_height 30
|
||||
#define devil_width 30
|
||||
#define devil_height 16
|
||||
#define devil_width 16
|
||||
extern const unsigned char devil[] PROGMEM;
|
||||
|
||||
#define heart_height 30
|
||||
#define heart_width 30
|
||||
#define heart_height 16
|
||||
#define heart_width 16
|
||||
extern const unsigned char heart[] PROGMEM;
|
||||
|
||||
#define poo_height 30
|
||||
#define poo_width 30
|
||||
#define poo_height 16
|
||||
#define poo_width 16
|
||||
extern const unsigned char poo[] PROGMEM;
|
||||
|
||||
#define bell_icon_width 30
|
||||
#define bell_icon_height 30
|
||||
#define bell_icon_width 16
|
||||
#define bell_icon_height 16
|
||||
extern const unsigned char bell_icon[] PROGMEM;
|
||||
|
||||
#define cookie_width 16
|
||||
#define cookie_height 16
|
||||
extern const unsigned char cookie[] PROGMEM;
|
||||
|
||||
#define Fire_width 16
|
||||
#define Fire_height 16
|
||||
extern const unsigned char Fire[] PROGMEM;
|
||||
|
||||
#define peace_sign_width 16
|
||||
#define peace_sign_height 16
|
||||
extern const unsigned char peace_sign[] PROGMEM;
|
||||
|
||||
#define Praying_width 16
|
||||
#define Praying_height 16
|
||||
extern const unsigned char Praying[] PROGMEM;
|
||||
|
||||
#define Sparkles_width 16
|
||||
#define Sparkles_height 16
|
||||
extern const unsigned char Sparkles[] PROGMEM;
|
||||
|
||||
#define clown_width 16
|
||||
#define clown_height 16
|
||||
extern const unsigned char clown[] PROGMEM;
|
||||
|
||||
#define robo_width 16
|
||||
#define robo_height 16
|
||||
extern const unsigned char robo[] PROGMEM;
|
||||
|
||||
#define hole_width 16
|
||||
#define hole_height 16
|
||||
extern const unsigned char hole[] PROGMEM;
|
||||
|
||||
#define bowling_width 16
|
||||
#define bowling_height 16
|
||||
extern const unsigned char bowling[] PROGMEM;
|
||||
|
||||
#define vulcan_salute_width 16
|
||||
#define vulcan_salute_height 16
|
||||
extern const unsigned char vulcan_salute[] PROGMEM;
|
||||
#endif // EXCLUDE_EMOJI
|
||||
|
||||
} // namespace graphics
|
||||
} // namespace graphics
|
||||
@@ -360,6 +360,10 @@ const uint8_t chirpy_hirez[] = {
|
||||
#define chirpy_small_image_height 8
|
||||
const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
|
||||
|
||||
#define connection_icon_width 7
|
||||
#define connection_icon_height 5
|
||||
const uint8_t connection_icon[] = {0x36, 0x41, 0x5D, 0x41, 0x36};
|
||||
|
||||
#ifdef M5STACK_UNITC6L
|
||||
#include "img/icon_small.xbm"
|
||||
#else
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "main.h"
|
||||
|
||||
#include "RTC.h"
|
||||
#include "mesh/NodeDB.h"
|
||||
|
||||
using namespace NicheGraphics;
|
||||
|
||||
@@ -333,13 +334,13 @@ std::string InkHUD::Applet::parse(std::string text)
|
||||
// Get the best version of a node's short name available to us
|
||||
// Parses any non-ascii chars
|
||||
// Swaps for last-four of node-id if the real short name is unknown or can't be rendered (emoji)
|
||||
std::string InkHUD::Applet::parseShortName(meshtastic_NodeInfoLite *node)
|
||||
std::string InkHUD::Applet::parseShortName(meshtastic_NodeDetail *node)
|
||||
{
|
||||
assert(node);
|
||||
|
||||
// Use the true shortname if known, and doesn't contain any unprintable characters (emoji, etc.)
|
||||
if (node->has_user) {
|
||||
std::string parsed = parse(node->user.short_name);
|
||||
if (detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER) && node->short_name[0] != '\0') {
|
||||
std::string parsed = parse(node->short_name);
|
||||
if (isPrintable(parsed))
|
||||
return parsed;
|
||||
}
|
||||
@@ -640,7 +641,9 @@ uint16_t InkHUD::Applet::getActiveNodeCount()
|
||||
|
||||
// For each node in db
|
||||
for (uint16_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||
const meshtastic_NodeDetail *node = nodeDB->getMeshNodeByIndex(i);
|
||||
if (!node)
|
||||
continue;
|
||||
|
||||
// Check if heard recently, and not our own node
|
||||
if (sinceLastSeen(node) < settings->recentlyActiveSeconds && node->num != nodeDB->getNodeNum())
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include <GFX.h> // GFXRoot drawing lib
|
||||
|
||||
#include "mesh/MeshTypes.h"
|
||||
#include "mesh/generated/meshtastic/deviceonly.pb.h"
|
||||
|
||||
#include "./AppletFont.h"
|
||||
#include "./Applets/System/Notification/Notification.h" // The notification object, not the applet
|
||||
@@ -133,15 +134,15 @@ class Applet : public GFX
|
||||
void drawLogo(int16_t centerX, int16_t centerY, uint16_t width, uint16_t height,
|
||||
Color color = BLACK); // Draw the Meshtastic logo
|
||||
|
||||
std::string hexifyNodeNum(NodeNum num); // Style as !0123abdc
|
||||
SignalStrength getSignalStrength(float snr, float rssi); // Interpret SNR and RSSI, as an easy to understand value
|
||||
std::string getTimeString(uint32_t epochSeconds); // Human readable
|
||||
std::string getTimeString(); // Current time, human readable
|
||||
uint16_t getActiveNodeCount(); // Duration determined by user, in onscreen menu
|
||||
std::string localizeDistance(uint32_t meters); // Human readable distance, imperial or metric
|
||||
std::string parse(std::string text); // Handle text which might contain special chars
|
||||
std::string parseShortName(meshtastic_NodeInfoLite *node); // Get the shortname, or a substitute if has unprintable chars
|
||||
bool isPrintable(std::string); // Check for characters which the font can't print
|
||||
std::string hexifyNodeNum(NodeNum num); // Style as !0123abdc
|
||||
SignalStrength getSignalStrength(float snr, float rssi); // Interpret SNR and RSSI, as an easy to understand value
|
||||
std::string getTimeString(uint32_t epochSeconds); // Human readable
|
||||
std::string getTimeString(); // Current time, human readable
|
||||
uint16_t getActiveNodeCount(); // Duration determined by user, in onscreen menu
|
||||
std::string localizeDistance(uint32_t meters); // Human readable distance, imperial or metric
|
||||
std::string parse(std::string text); // Handle text which might contain special chars
|
||||
std::string parseShortName(meshtastic_NodeDetail *node); // Get the shortname, or a substitute if has unprintable chars
|
||||
bool isPrintable(std::string); // Check for characters which the font can't print
|
||||
|
||||
// Convenient references
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "./MapApplet.h"
|
||||
|
||||
#include "mesh/NodeDB.h"
|
||||
|
||||
using namespace NicheGraphics;
|
||||
|
||||
void InkHUD::MapApplet::onRender()
|
||||
@@ -136,9 +138,9 @@ void InkHUD::MapApplet::onRender()
|
||||
printAt(vertBarX + (bottomLabelW / 2) + 1, bottomLabelY + (bottomLabelH / 2), vertBottomLabel, CENTER, MIDDLE);
|
||||
|
||||
// Draw our node LAST with full white fill + outline
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
meshtastic_NodeDetail *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
if (ourNode && nodeDB->hasValidPosition(ourNode)) {
|
||||
Marker self = calculateMarker(ourNode->position.latitude_i * 1e-7, ourNode->position.longitude_i * 1e-7, false, 0);
|
||||
Marker self = calculateMarker(ourNode->latitude_i * 1e-7f, ourNode->longitude_i * 1e-7f, false, 0);
|
||||
|
||||
int16_t centerX = X(0.5) + (self.eastMeters * metersToPx);
|
||||
int16_t centerY = Y(0.5) - (self.northMeters * metersToPx);
|
||||
@@ -166,10 +168,10 @@ void InkHUD::MapApplet::onRender()
|
||||
void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
||||
{
|
||||
// If we have a valid position for our own node, use that as the anchor
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
meshtastic_NodeDetail *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
if (ourNode && nodeDB->hasValidPosition(ourNode)) {
|
||||
*lat = ourNode->position.latitude_i * 1e-7;
|
||||
*lng = ourNode->position.longitude_i * 1e-7;
|
||||
*lat = ourNode->latitude_i * 1e-7f;
|
||||
*lng = ourNode->longitude_i * 1e-7f;
|
||||
} else {
|
||||
// Find mean lat long coords
|
||||
// ============================
|
||||
@@ -189,7 +191,7 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
||||
|
||||
// For each node in db
|
||||
for (uint32_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||
meshtastic_NodeDetail *node = nodeDB->getMeshNodeByIndex(i);
|
||||
|
||||
// Skip if no position
|
||||
if (!nodeDB->hasValidPosition(node))
|
||||
@@ -200,8 +202,8 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
||||
continue;
|
||||
|
||||
// Latitude and Longitude of node, in radians
|
||||
float latRad = node->position.latitude_i * (1e-7) * DEG_TO_RAD;
|
||||
float lngRad = node->position.longitude_i * (1e-7) * DEG_TO_RAD;
|
||||
float latRad = node->latitude_i * (1e-7f) * DEG_TO_RAD;
|
||||
float lngRad = node->longitude_i * (1e-7f) * DEG_TO_RAD;
|
||||
|
||||
// Convert to cartesian points, with center of earth at 0, 0, 0
|
||||
// Exact distance from center is irrelevant, as we're only interested in the vector
|
||||
@@ -287,8 +289,8 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
||||
float easternmost = lngCenter;
|
||||
float westernmost = lngCenter;
|
||||
|
||||
for (uint8_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
meshtastic_NodeDetail *node = nodeDB->getMeshNodeByIndex(i);
|
||||
|
||||
// Skip if no position
|
||||
if (!nodeDB->hasValidPosition(node))
|
||||
@@ -299,12 +301,12 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
||||
continue;
|
||||
|
||||
// Check for a new top or bottom latitude
|
||||
float latNode = node->position.latitude_i * 1e-7;
|
||||
float latNode = node->latitude_i * 1e-7f;
|
||||
northernmost = max(northernmost, latNode);
|
||||
southernmost = min(southernmost, latNode);
|
||||
|
||||
// Longitude is trickier
|
||||
float lngNode = node->position.longitude_i * 1e-7;
|
||||
float lngNode = node->longitude_i * 1e-7f;
|
||||
float degEastward = fmod(((lngNode - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node
|
||||
float degWestward = abs(fmod(((lngNode - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node
|
||||
if (degEastward < degWestward)
|
||||
@@ -366,14 +368,14 @@ InkHUD::MapApplet::Marker InkHUD::MapApplet::calculateMarker(float lat, float ln
|
||||
return m;
|
||||
}
|
||||
// Draw a marker on the map for a node, with a shortname label, and backing box
|
||||
void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
||||
void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeDetail *node)
|
||||
{
|
||||
// Find x and y position based on node's position in nodeDB
|
||||
assert(nodeDB->hasValidPosition(node));
|
||||
Marker m = calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
|
||||
node->position.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style
|
||||
node->has_hops_away, // Is the hopsAway number valid
|
||||
node->hops_away // Hops away
|
||||
Marker m = calculateMarker(node->latitude_i * 1e-7f, // Lat, converted from Meshtastic's internal int32 style
|
||||
node->longitude_i * 1e-7f, // Long, converted from Meshtastic's internal int32 style
|
||||
detailHasFlag(*node, NODEDETAIL_FLAG_HAS_HOPS_AWAY), // Is the hopsAway number valid
|
||||
node->hops_away // Hops away
|
||||
);
|
||||
|
||||
// Convert to pixel coords
|
||||
@@ -396,9 +398,9 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
||||
uint16_t labelH;
|
||||
uint8_t markerSize;
|
||||
|
||||
bool tooManyHops = node->hops_away > config.lora.hop_limit;
|
||||
bool tooManyHops = detailHasFlag(*node, NODEDETAIL_FLAG_HAS_HOPS_AWAY) && node->hops_away > config.lora.hop_limit;
|
||||
bool isOurNode = node->num == nodeDB->getNodeNum();
|
||||
bool unknownHops = !node->has_hops_away && !isOurNode;
|
||||
bool unknownHops = !detailHasFlag(*node, NODEDETAIL_FLAG_HAS_HOPS_AWAY) && !isOurNode;
|
||||
|
||||
// Parse any non-ascii chars in the short name,
|
||||
// and use last 4 instead if unknown / can't render
|
||||
@@ -474,9 +476,9 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
||||
// Need at least two, to draw a sensible map
|
||||
bool InkHUD::MapApplet::enoughMarkers()
|
||||
{
|
||||
uint8_t count = 0;
|
||||
for (uint8_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
meshtastic_NodeDetail *node = nodeDB->getMeshNodeByIndex(i);
|
||||
|
||||
// Count nodes
|
||||
if (nodeDB->hasValidPosition(node) && shouldDrawNode(node))
|
||||
@@ -499,7 +501,7 @@ void InkHUD::MapApplet::calculateAllMarkers()
|
||||
|
||||
// For each node in db
|
||||
for (uint32_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||
meshtastic_NodeDetail *node = nodeDB->getMeshNodeByIndex(i);
|
||||
|
||||
// Skip if no position
|
||||
if (!nodeDB->hasValidPosition(node))
|
||||
@@ -515,12 +517,11 @@ void InkHUD::MapApplet::calculateAllMarkers()
|
||||
continue;
|
||||
|
||||
// Calculate marker and store it
|
||||
markers.push_back(
|
||||
calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
|
||||
node->position.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style
|
||||
node->has_hops_away, // Is the hopsAway number valid
|
||||
node->hops_away // Hops away
|
||||
));
|
||||
markers.push_back(calculateMarker(node->latitude_i * 1e-7f, // Lat, converted from Meshtastic's internal int32 style
|
||||
node->longitude_i * 1e-7f, // Long, converted from Meshtastic's internal int32 style
|
||||
detailHasFlag(*node, NODEDETAIL_FLAG_HAS_HOPS_AWAY), // Is the hopsAway number valid
|
||||
node->hops_away // Hops away
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,12 +30,12 @@ class MapApplet : public Applet
|
||||
void onRender() override;
|
||||
|
||||
protected:
|
||||
virtual bool shouldDrawNode(meshtastic_NodeInfoLite *node) { return true; } // Allow derived applets to filter the nodes
|
||||
virtual bool shouldDrawNode(meshtastic_NodeDetail *node) { return true; } // Allow derived applets to filter the nodes
|
||||
virtual void getMapCenter(float *lat, float *lng);
|
||||
virtual void getMapSize(uint32_t *widthMeters, uint32_t *heightMeters);
|
||||
|
||||
bool enoughMarkers(); // Anything to draw?
|
||||
void drawLabeledMarker(meshtastic_NodeInfoLite *node); // Highlight a specific marker
|
||||
bool enoughMarkers(); // Anything to draw?
|
||||
void drawLabeledMarker(meshtastic_NodeDetail *node); // Highlight a specific marker
|
||||
|
||||
private:
|
||||
// Position and size of a marker to be drawn
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "RTC.h"
|
||||
|
||||
#include "GeoCoord.h"
|
||||
#include "NodeDB.h"
|
||||
#include "mesh/NodeDB.h"
|
||||
|
||||
#include "./NodeListApplet.h"
|
||||
|
||||
@@ -50,19 +50,19 @@ ProcessMessage InkHUD::NodeListApplet::handleReceived(const meshtastic_MeshPacke
|
||||
c.signal = getSignalStrength(mp.rx_snr, mp.rx_rssi);
|
||||
|
||||
// Assemble info: from nodeDB (needed to detect changes)
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(c.nodeNum);
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
meshtastic_NodeDetail *node = nodeDB->getMeshNode(c.nodeNum);
|
||||
meshtastic_NodeDetail *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
if (node) {
|
||||
if (node->has_hops_away)
|
||||
if (detailHasFlag(*node, NODEDETAIL_FLAG_HAS_HOPS_AWAY))
|
||||
c.hopsAway = node->hops_away;
|
||||
|
||||
if (nodeDB->hasValidPosition(node) && nodeDB->hasValidPosition(ourNode)) {
|
||||
// Get lat and long as float
|
||||
// Meshtastic stores these as integers internally
|
||||
float ourLat = ourNode->position.latitude_i * 1e-7;
|
||||
float ourLong = ourNode->position.longitude_i * 1e-7;
|
||||
float theirLat = node->position.latitude_i * 1e-7;
|
||||
float theirLong = node->position.longitude_i * 1e-7;
|
||||
float ourLat = ourNode->latitude_i * 1e-7f;
|
||||
float ourLong = ourNode->longitude_i * 1e-7f;
|
||||
float theirLat = node->latitude_i * 1e-7f;
|
||||
float theirLong = node->longitude_i * 1e-7f;
|
||||
|
||||
c.distanceMeters = (int32_t)GeoCoord::latLongToMeter(theirLat, theirLong, ourLat, ourLong);
|
||||
}
|
||||
@@ -144,7 +144,7 @@ void InkHUD::NodeListApplet::onRender()
|
||||
std::string distance; // handled below;
|
||||
uint8_t &hopsAway = card->hopsAway;
|
||||
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
|
||||
meshtastic_NodeDetail *node = nodeDB->getMeshNode(nodeNum);
|
||||
|
||||
// Skip deleted nodes
|
||||
if (!node) {
|
||||
@@ -162,8 +162,8 @@ void InkHUD::NodeListApplet::onRender()
|
||||
// -- Longname --
|
||||
// Parse special chars in long name
|
||||
// Use node id if unknown
|
||||
if (node && node->has_user)
|
||||
longName = parse(node->user.long_name); // Found in nodeDB
|
||||
if (node && detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER))
|
||||
longName = parse(node->long_name); // Found in nodeDB
|
||||
else {
|
||||
// Not found in nodeDB, show a hex nodeid instead
|
||||
longName = hexifyNodeNum(nodeNum);
|
||||
|
||||
@@ -14,10 +14,10 @@ InkHUD::LogoApplet::LogoApplet() : concurrency::OSThread("LogoApplet")
|
||||
// During onboarding, show the default short name as well as the version string
|
||||
// This behavior assists manufacturers during mass production, and should not be modified without good reason
|
||||
if (!settings->tips.safeShutdownSeen) {
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
meshtastic_NodeDetail *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
fontTitle = fontMedium;
|
||||
textLeft = xstr(APP_VERSION_SHORT);
|
||||
textRight = parseShortName(ourNode);
|
||||
textRight = ourNode ? parseShortName(ourNode) : "";
|
||||
textTitle = "Meshtastic";
|
||||
} else {
|
||||
fontTitle = fontSmall;
|
||||
@@ -146,10 +146,10 @@ void InkHUD::LogoApplet::onShutdown()
|
||||
|
||||
// Prepare for the powered-off screen now
|
||||
// We can change these values because the initial "shutting down" screen has already rendered at this point
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
meshtastic_NodeDetail *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
textLeft = "";
|
||||
textRight = "";
|
||||
textTitle = parseShortName(ourNode);
|
||||
textTitle = ourNode ? parseShortName(ourNode) : "";
|
||||
fontTitle = fontMedium;
|
||||
|
||||
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "Router.h"
|
||||
#include "airtime.h"
|
||||
#include "main.h"
|
||||
#include "mesh/NodeDB.h"
|
||||
#include "power.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_GPS
|
||||
@@ -616,7 +617,8 @@ void InkHUD::MenuApplet::populateRecipientPage()
|
||||
|
||||
// Count favorites
|
||||
for (uint32_t i = 0; i < nodeCount; i++) {
|
||||
if (nodeDB->getMeshNodeByIndex(i)->is_favorite)
|
||||
const meshtastic_NodeDetail *detail = nodeDB->getMeshNodeByIndex(i);
|
||||
if (detail && detailIsFavorite(*detail))
|
||||
favoriteCount++;
|
||||
}
|
||||
|
||||
@@ -624,10 +626,12 @@ void InkHUD::MenuApplet::populateRecipientPage()
|
||||
// Don't want some monstrous list that takes 100 clicks to reach exit
|
||||
if (favoriteCount < 20) {
|
||||
for (uint32_t i = 0; i < nodeCount; i++) {
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||
const meshtastic_NodeDetail *node = nodeDB->getMeshNodeByIndex(i);
|
||||
if (!node)
|
||||
continue;
|
||||
|
||||
// Skip node if not a favorite
|
||||
if (!node->is_favorite)
|
||||
if (!detailIsFavorite(*node))
|
||||
continue;
|
||||
|
||||
CannedMessages::RecipientItem r;
|
||||
@@ -637,8 +641,8 @@ void InkHUD::MenuApplet::populateRecipientPage()
|
||||
|
||||
// Set a label for the menu item
|
||||
r.label = "DM: ";
|
||||
if (node->has_user)
|
||||
r.label += parse(node->user.long_name);
|
||||
if (detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER) && node->long_name[0] != '\0')
|
||||
r.label += parse(node->long_name);
|
||||
else
|
||||
r.label += hexifyNodeNum(node->num); // Unsure if it's possible to favorite a node without NodeInfo?
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "./Notification.h"
|
||||
#include "graphics/niche/InkHUD/Persistence.h"
|
||||
|
||||
#include "mesh/NodeDB.h"
|
||||
#include "meshUtils.h"
|
||||
#include "modules/TextMessageModule.h"
|
||||
|
||||
@@ -206,13 +207,13 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila
|
||||
isBroadcast ? &inkhud->persistence->latestMessage.broadcast : &inkhud->persistence->latestMessage.dm;
|
||||
|
||||
// Find info about the sender
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(message->sender);
|
||||
meshtastic_NodeDetail *node = nodeDB->getMeshNode(message->sender);
|
||||
|
||||
// Leading tag (channel vs. DM)
|
||||
text += isBroadcast ? "From:" : "DM: ";
|
||||
|
||||
// Sender id
|
||||
if (node && node->has_user)
|
||||
if (node && detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER))
|
||||
text += parseShortName(node);
|
||||
else
|
||||
text += hexifyNodeNum(message->sender);
|
||||
@@ -226,7 +227,7 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila
|
||||
text += isBroadcast ? "Msg from " : "DM from ";
|
||||
|
||||
// Sender id
|
||||
if (node && node->has_user)
|
||||
if (node && detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER))
|
||||
text += parseShortName(node);
|
||||
else
|
||||
text += hexifyNodeNum(message->sender);
|
||||
|
||||
@@ -69,11 +69,11 @@ void InkHUD::AllMessageApplet::onRender()
|
||||
// Sender's id
|
||||
// - short name and long name, if available, or
|
||||
// - node id
|
||||
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(message->sender);
|
||||
if (sender && sender->has_user) {
|
||||
meshtastic_NodeDetail *sender = nodeDB->getMeshNode(message->sender);
|
||||
if (sender && detailHasFlag(*sender, NODEDETAIL_FLAG_HAS_USER)) {
|
||||
header += parseShortName(sender); // May be last-four of node if unprintable (emoji, etc)
|
||||
header += " (";
|
||||
header += parse(sender->user.long_name);
|
||||
header += parse(sender->long_name);
|
||||
header += ")";
|
||||
} else
|
||||
header += hexifyNodeNum(message->sender);
|
||||
|
||||
@@ -65,11 +65,11 @@ void InkHUD::DMApplet::onRender()
|
||||
// Sender's id
|
||||
// - shortname and long name, if available, or
|
||||
// - node id
|
||||
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(latestMessage->dm.sender);
|
||||
if (sender && sender->has_user) {
|
||||
meshtastic_NodeDetail *sender = nodeDB->getMeshNode(latestMessage->dm.sender);
|
||||
if (sender && detailHasFlag(*sender, NODEDETAIL_FLAG_HAS_USER)) {
|
||||
header += parseShortName(sender); // May be last-four of node if unprintable (emoji, etc)
|
||||
header += " (";
|
||||
header += parse(sender->user.long_name);
|
||||
header += parse(sender->long_name);
|
||||
header += ")";
|
||||
} else
|
||||
header += hexifyNodeNum(latestMessage->dm.sender);
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include "./HeardApplet.h"
|
||||
|
||||
#include "mesh/NodeDB.h"
|
||||
|
||||
using namespace NicheGraphics;
|
||||
|
||||
void InkHUD::HeardApplet::onActivate()
|
||||
@@ -61,7 +63,7 @@ void InkHUD::HeardApplet::handleParsed(CardInfo c)
|
||||
void InkHUD::HeardApplet::populateFromNodeDB()
|
||||
{
|
||||
// Fill a collection with pointers to each node in db
|
||||
std::vector<meshtastic_NodeInfoLite *> ordered;
|
||||
std::vector<meshtastic_NodeDetail *> ordered;
|
||||
for (auto mn = nodeDB->meshNodes->begin(); mn != nodeDB->meshNodes->end(); ++mn) {
|
||||
// Only copy if valid, and not our own node
|
||||
if (mn->num != 0 && mn->num != nodeDB->getNodeNum())
|
||||
@@ -69,7 +71,7 @@ void InkHUD::HeardApplet::populateFromNodeDB()
|
||||
}
|
||||
|
||||
// Sort the collection by age
|
||||
std::sort(ordered.begin(), ordered.end(), [](meshtastic_NodeInfoLite *top, meshtastic_NodeInfoLite *bottom) -> bool {
|
||||
std::sort(ordered.begin(), ordered.end(), [](const meshtastic_NodeDetail *top, const meshtastic_NodeDetail *bottom) -> bool {
|
||||
return (top->last_heard > bottom->last_heard);
|
||||
});
|
||||
|
||||
@@ -79,21 +81,21 @@ void InkHUD::HeardApplet::populateFromNodeDB()
|
||||
ordered.resize(maxCards());
|
||||
|
||||
// Create card info for these (stale) node observations
|
||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
for (meshtastic_NodeInfoLite *node : ordered) {
|
||||
meshtastic_NodeDetail *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
for (meshtastic_NodeDetail *node : ordered) {
|
||||
CardInfo c;
|
||||
c.nodeNum = node->num;
|
||||
|
||||
if (node->has_hops_away)
|
||||
if (detailHasFlag(*node, NODEDETAIL_FLAG_HAS_HOPS_AWAY))
|
||||
c.hopsAway = node->hops_away;
|
||||
|
||||
if (nodeDB->hasValidPosition(node) && nodeDB->hasValidPosition(ourNode)) {
|
||||
// Get lat and long as float
|
||||
// Meshtastic stores these as integers internally
|
||||
float ourLat = ourNode->position.latitude_i * 1e-7;
|
||||
float ourLong = ourNode->position.longitude_i * 1e-7;
|
||||
float theirLat = node->position.latitude_i * 1e-7;
|
||||
float theirLong = node->position.longitude_i * 1e-7;
|
||||
float ourLat = ourNode->latitude_i * 1e-7f;
|
||||
float ourLong = ourNode->longitude_i * 1e-7f;
|
||||
float theirLat = node->latitude_i * 1e-7f;
|
||||
float theirLong = node->longitude_i * 1e-7f;
|
||||
|
||||
c.distanceMeters = (int32_t)GeoCoord::latLongToMeter(theirLat, theirLong, ourLat, ourLong);
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ void InkHUD::PositionsApplet::onRender()
|
||||
// We might be rendering because we got a position packet from them
|
||||
// We might be rendering because our own position updated
|
||||
// Either way, we still highlight which node most recently sent us a position packet
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(lastFrom);
|
||||
const meshtastic_NodeDetail *node = nodeDB->getMeshNode(lastFrom);
|
||||
if (node && nodeDB->hasValidPosition(node) && enoughMarkers())
|
||||
drawLabeledMarker(node);
|
||||
drawLabeledMarker(const_cast<meshtastic_NodeDetail *>(node));
|
||||
}
|
||||
|
||||
// Determine if we need to redraw the map, when we receive a new position packet
|
||||
|
||||
@@ -108,8 +108,8 @@ void InkHUD::ThreadedMessageApplet::onRender()
|
||||
info += "Me";
|
||||
else {
|
||||
// Check if sender is node db
|
||||
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(m.sender);
|
||||
if (sender)
|
||||
meshtastic_NodeDetail *sender = nodeDB->getMeshNode(m.sender);
|
||||
if (sender && detailHasFlag(*sender, NODEDETAIL_FLAG_HAS_USER))
|
||||
info += parseShortName(sender); // Handle any unprintable chars in short name
|
||||
else
|
||||
info += hexifyNodeNum(m.sender); // No node info at all. Print the node num
|
||||
|
||||
@@ -5,7 +5,6 @@ A pattern / collection of resources for creating custom UIs, to target small gro
|
||||
For an example, see the `heltec-vision-master-e290-inkhud` platformio env.
|
||||
|
||||
- platformio.ini
|
||||
|
||||
- suppress default Meshtastic components (Screen, ButtonThread, etc)
|
||||
- define `MESHTASTIC_INCLUDE_NICHE_GRAPHICS`
|
||||
- (possibly) Edit `build_src_filter` to include our new nicheGraphics.h file
|
||||
|
||||
@@ -52,7 +52,7 @@ int InputBroker::handleInputEvent(const InputEvent *event)
|
||||
powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release
|
||||
|
||||
if (event && event->inputEvent != INPUT_BROKER_NONE && externalNotificationModule &&
|
||||
moduleConfig.external_notification.enabled) {
|
||||
moduleConfig.external_notification.enabled && externalNotificationModule->nagging()) {
|
||||
externalNotificationModule->stopNow();
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ static unsigned char TDeckProTapMap[_TCA8418_NUM_KEYS][5] = {
|
||||
{0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},
|
||||
{0x20, 0x00, 0x00},
|
||||
{0x00, 0x00, 0x00},
|
||||
{0x00, 0x00, '0'},
|
||||
{0x00, 0x00, 0x00} // R_Shift, sym, space, mic, L_Shift
|
||||
};
|
||||
|
||||
|
||||
@@ -477,6 +477,10 @@ void setup()
|
||||
#ifdef RESET_OLED
|
||||
pinMode(RESET_OLED, OUTPUT);
|
||||
digitalWrite(RESET_OLED, 1);
|
||||
delay(2);
|
||||
digitalWrite(RESET_OLED, 0);
|
||||
delay(10);
|
||||
digitalWrite(RESET_OLED, 1);
|
||||
#endif
|
||||
|
||||
#ifdef SENSOR_POWER_CTRL_PIN
|
||||
@@ -959,6 +963,7 @@ void setup()
|
||||
i2cScanner.reset();
|
||||
#endif
|
||||
|
||||
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
||||
// warn the user about a low entropy key
|
||||
if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) {
|
||||
LOG_WARN(LOW_ENTROPY_WARNING);
|
||||
@@ -969,6 +974,7 @@ void setup()
|
||||
service->sendClientNotification(cn);
|
||||
nodeDB->hasWarned = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
// buttons are now inputBroker, so have to come after setupModules
|
||||
#if HAS_BUTTON
|
||||
@@ -1401,7 +1407,7 @@ void setup()
|
||||
#endif
|
||||
|
||||
// check if the radio chip matches the selected region
|
||||
if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (!rIf->wideLora())) {
|
||||
if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && rIf && (!rIf->wideLora())) {
|
||||
LOG_WARN("LoRa chip does not support 2.4GHz. Revert to unset");
|
||||
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
|
||||
nodeDB->saveToDisk(SEGMENT_CONFIG);
|
||||
|
||||
@@ -92,10 +92,10 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtas
|
||||
LOG_DEBUG("Node %d or their public_key not found", toNode);
|
||||
return false;
|
||||
}
|
||||
if (!crypto->setDHPublicKey(remotePublic.bytes)) {
|
||||
if (!setDHPublicKey(remotePublic.bytes)) {
|
||||
return false;
|
||||
}
|
||||
crypto->hash(shared_key, 32);
|
||||
hash(shared_key, 32);
|
||||
initNonce(fromNode, packetNum, extraNonceTmp);
|
||||
|
||||
// Calculate the shared secret with the destination node and encrypt
|
||||
@@ -134,10 +134,10 @@ bool CryptoEngine::decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_publ
|
||||
}
|
||||
|
||||
// Calculate the shared secret with the sending node and decrypt
|
||||
if (!crypto->setDHPublicKey(remotePublic.bytes)) {
|
||||
if (!setDHPublicKey(remotePublic.bytes)) {
|
||||
return false;
|
||||
}
|
||||
crypto->hash(shared_key, 32);
|
||||
hash(shared_key, 32);
|
||||
|
||||
initNonce(fromNode, packetNum, extraNonce);
|
||||
printBytes("Attempt decrypt with nonce: ", nonce, 13);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#else
|
||||
#define default_ringtone_nag_secs 15
|
||||
#endif
|
||||
#define default_network_ipv6_enabled false
|
||||
|
||||
#define default_mqtt_address "mqtt.meshtastic.org"
|
||||
#define default_mqtt_username "meshdev"
|
||||
@@ -46,12 +47,15 @@ class Default
|
||||
static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval);
|
||||
static uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval, uint32_t defaultInterval);
|
||||
static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue);
|
||||
// Note: numOnlineNodes uses uint32_t to match the public API and allow flexibility,
|
||||
// even though internal node counts use uint16_t (max 65535 nodes)
|
||||
static uint32_t getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes);
|
||||
static uint8_t getConfiguredOrDefaultHopLimit(uint8_t configured);
|
||||
static uint32_t getConfiguredOrMinimumValue(uint32_t configured, uint32_t minValue);
|
||||
|
||||
private:
|
||||
static float congestionScalingCoefficient(int numOnlineNodes)
|
||||
// Note: Kept as uint32_t to match the public API parameter type
|
||||
static float congestionScalingCoefficient(uint32_t numOnlineNodes)
|
||||
{
|
||||
// Increase frequency of broadcasts for small networks regardless of preset
|
||||
if (numOnlineNodes <= 10) {
|
||||
|
||||
@@ -244,6 +244,8 @@ template <typename T> void LR11x0Interface<T>::startReceive()
|
||||
// We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly.
|
||||
int err =
|
||||
lora.startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
|
||||
if (err)
|
||||
LOG_ERROR("StartReceive error: %d", err);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
RadioLibInterface::startReceive();
|
||||
@@ -304,4 +306,4 @@ template <typename T> bool LR11x0Interface<T>::sleep()
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -86,12 +86,15 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp)
|
||||
|
||||
nodeDB->updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
|
||||
bool isPreferredRebroadcaster = config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER;
|
||||
meshtastic_NodeDetail *fromNode = nodeDB->getMeshNode(mp->from);
|
||||
|
||||
if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||
mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) {
|
||||
LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo");
|
||||
// ignore our request for its NodeInfo
|
||||
} else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user &&
|
||||
nodeInfoModule && !isPreferredRebroadcaster && !nodeDB->isFull()) {
|
||||
} else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && fromNode &&
|
||||
!detailHasFlag(*fromNode, NODEDETAIL_FLAG_HAS_USER) && nodeInfoModule && !isPreferredRebroadcaster &&
|
||||
!nodeDB->isFull()) {
|
||||
if (airTime->isTxAllowedChannelUtil(true)) {
|
||||
// Hops used by the request. If somebody in between running modified firmware modified it, ignore it
|
||||
auto hopStart = mp->hop_start;
|
||||
@@ -269,7 +272,7 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh
|
||||
|
||||
bool MeshService::trySendPosition(NodeNum dest, bool wantReplies)
|
||||
{
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
meshtastic_NodeDetail *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
|
||||
assert(node);
|
||||
|
||||
@@ -376,24 +379,25 @@ void MeshService::sendClientNotification(meshtastic_ClientNotification *n)
|
||||
fromNum++;
|
||||
}
|
||||
|
||||
meshtastic_NodeInfoLite *MeshService::refreshLocalMeshNode()
|
||||
meshtastic_NodeDetail *MeshService::refreshLocalMeshNode()
|
||||
{
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
meshtastic_NodeDetail *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||
assert(node);
|
||||
|
||||
// We might not have a position yet for our local node, in that case, at least try to send the time
|
||||
if (!node->has_position) {
|
||||
memset(&node->position, 0, sizeof(node->position));
|
||||
node->has_position = true;
|
||||
// Ensure we have a position container so time fields stay fresh even without GPS fixes
|
||||
if (!detailHasFlag(*node, NODEDETAIL_FLAG_HAS_POSITION)) {
|
||||
detailSetFlag(*node, NODEDETAIL_FLAG_HAS_POSITION, true);
|
||||
node->latitude_i = 0;
|
||||
node->longitude_i = 0;
|
||||
node->altitude = 0;
|
||||
node->position_source = _meshtastic_Position_LocSource_MIN;
|
||||
}
|
||||
|
||||
meshtastic_PositionLite &position = node->position;
|
||||
|
||||
// Update our local node info with our time (even if we don't decide to update anyone else)
|
||||
node->last_heard =
|
||||
getValidTime(RTCQualityFromNet); // This nodedb timestamp might be stale, so update it if our clock is kinda valid
|
||||
|
||||
position.time = getValidTime(RTCQualityFromNet);
|
||||
node->position_time = getValidTime(RTCQualityFromNet);
|
||||
|
||||
if (powerStatus->getHasBattery() == 1) {
|
||||
updateBatteryLevel(powerStatus->getBatteryChargePercent());
|
||||
@@ -406,7 +410,7 @@ meshtastic_NodeInfoLite *MeshService::refreshLocalMeshNode()
|
||||
int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus)
|
||||
{
|
||||
// Update our local node info with our position (even if we don't decide to update anyone else)
|
||||
const meshtastic_NodeInfoLite *node = refreshLocalMeshNode();
|
||||
const meshtastic_NodeDetail *node = refreshLocalMeshNode();
|
||||
meshtastic_Position pos = meshtastic_Position_init_default;
|
||||
|
||||
if (newStatus->getHasLock()) {
|
||||
@@ -421,7 +425,7 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus)
|
||||
// Used fixed position if configured regardless of GPS lock
|
||||
if (config.position.fixed_position) {
|
||||
LOG_WARN("Use fixed position");
|
||||
pos = TypeConversions::ConvertToPosition(node->position);
|
||||
pos = TypeConversions::ConvertToPosition(detailToPositionLite(*node));
|
||||
}
|
||||
|
||||
// Add a fresh timestamp
|
||||
|
||||
@@ -79,6 +79,18 @@ class MeshService
|
||||
uint32_t oldFromNum = 0;
|
||||
|
||||
public:
|
||||
enum APIState {
|
||||
STATE_DISCONNECTED, // Initial state, no API is connected
|
||||
STATE_BLE,
|
||||
STATE_WIFI,
|
||||
STATE_SERIAL,
|
||||
STATE_PACKET,
|
||||
STATE_HTTP,
|
||||
STATE_ETH
|
||||
};
|
||||
|
||||
APIState api_state = STATE_DISCONNECTED;
|
||||
|
||||
static bool isTextPayload(const meshtastic_MeshPacket *p)
|
||||
{
|
||||
if (moduleConfig.range_test.enabled && p->decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP) {
|
||||
@@ -158,7 +170,7 @@ class MeshService
|
||||
bool cancelSending(PacketId id);
|
||||
|
||||
/// Pull the latest power and time info into my nodeinfo
|
||||
meshtastic_NodeInfoLite *refreshLocalMeshNode();
|
||||
meshtastic_NodeDetail *refreshLocalMeshNode();
|
||||
|
||||
/// Send a packet to the phone
|
||||
void sendToPhone(meshtastic_MeshPacket *p);
|
||||
|
||||
@@ -94,7 +94,7 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
|
||||
// is not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the
|
||||
// destination
|
||||
if (p->from != 0) {
|
||||
meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from);
|
||||
meshtastic_NodeDetail *origTx = nodeDB->getMeshNode(p->from);
|
||||
if (origTx) {
|
||||
// Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came
|
||||
// directly from the destination
|
||||
@@ -176,7 +176,7 @@ uint8_t NextHopRouter::getNextHop(NodeNum to, uint8_t relay_node)
|
||||
if (isBroadcast(to))
|
||||
return NO_NEXT_HOP_PREFERENCE;
|
||||
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(to);
|
||||
meshtastic_NodeDetail *node = nodeDB->getMeshNode(to);
|
||||
if (node && node->next_hop) {
|
||||
// We are careful not to return the relay node as the next hop
|
||||
if (node->next_hop != relay_node) {
|
||||
@@ -296,7 +296,7 @@ int32_t NextHopRouter::doRetransmissions()
|
||||
// Last retransmission, reset next_hop (fallback to FloodingRouter)
|
||||
p.packet->next_hop = NO_NEXT_HOP_PREFERENCE;
|
||||
// Also reset it in the nodeDB
|
||||
meshtastic_NodeInfoLite *sentTo = nodeDB->getMeshNode(p.packet->to);
|
||||
meshtastic_NodeDetail *sentTo = nodeDB->getMeshNode(p.packet->to);
|
||||
if (sentTo) {
|
||||
LOG_INFO("Resetting next hop for packet with dest 0x%x\n", p.packet->to);
|
||||
sentTo->next_hop = NO_NEXT_HOP_PREFERENCE;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "modules/NeighborInfoModule.h"
|
||||
#include <ErriezCRC32.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
#include <vector>
|
||||
@@ -152,18 +153,19 @@ uint32_t get_st7789_id(uint8_t cs, uint8_t sck, uint8_t mosi, uint8_t dc, uint8_
|
||||
bool meshtastic_NodeDatabase_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field)
|
||||
{
|
||||
if (ostream) {
|
||||
std::vector<meshtastic_NodeInfoLite> const *vec = (std::vector<meshtastic_NodeInfoLite> *)field->pData;
|
||||
for (auto item : *vec) {
|
||||
const auto *vec = reinterpret_cast<const std::vector<meshtastic_NodeDetail> *>(field->pData);
|
||||
for (const auto &item : *vec) {
|
||||
if (!pb_encode_tag_for_field(ostream, field))
|
||||
return false;
|
||||
pb_encode_submessage(ostream, meshtastic_NodeInfoLite_fields, &item);
|
||||
if (!pb_encode_submessage(ostream, meshtastic_NodeDetail_fields, &item))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (istream) {
|
||||
meshtastic_NodeInfoLite node; // this gets good data
|
||||
std::vector<meshtastic_NodeInfoLite> *vec = (std::vector<meshtastic_NodeInfoLite> *)field->pData;
|
||||
meshtastic_NodeDetail node = meshtastic_NodeDetail_init_default; // this gets good data
|
||||
auto *vec = reinterpret_cast<std::vector<meshtastic_NodeDetail> *>(field->pData);
|
||||
|
||||
if (istream->bytes_left && pb_decode(istream, meshtastic_NodeInfoLite_fields, &node))
|
||||
if (istream->bytes_left && pb_decode(istream, meshtastic_NodeDetail_fields, &node))
|
||||
vec->push_back(node);
|
||||
}
|
||||
return true;
|
||||
@@ -295,9 +297,8 @@ NodeDB::NodeDB()
|
||||
}
|
||||
#endif
|
||||
// Include our owner in the node db under our nodenum
|
||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum());
|
||||
info->user = TypeConversions::ConvertToUserLite(owner);
|
||||
info->has_user = true;
|
||||
meshtastic_NodeDetail *info = getOrCreateMeshNode(getNodeNum());
|
||||
applyUserToDetail(*info, owner);
|
||||
|
||||
// If node database has not been saved for the first time, save it now
|
||||
#ifdef FSCom
|
||||
@@ -520,7 +521,7 @@ void NodeDB::installDefaultNodeDatabase()
|
||||
{
|
||||
LOG_DEBUG("Install default NodeDatabase");
|
||||
nodeDatabase.version = DEVICESTATE_CUR_VER;
|
||||
nodeDatabase.nodes = std::vector<meshtastic_NodeInfoLite>(MAX_NUM_NODES);
|
||||
nodeDatabase.nodes = std::vector<meshtastic_NodeDetail>(MAX_NUM_NODES, makeDefaultDetail());
|
||||
numMeshNodes = 0;
|
||||
meshNodes = &nodeDatabase.nodes;
|
||||
}
|
||||
@@ -653,7 +654,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
||||
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) || \
|
||||
defined(ELECROW_PANEL)) && \
|
||||
defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT)) && \
|
||||
HAS_TFT
|
||||
// switch BT off by default; use TFT programming mode or hotkey to enable
|
||||
config.bluetooth.enabled = false;
|
||||
@@ -718,6 +719,12 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
||||
strncpy(config.network.wifi_psk, USERPREFS_NETWORK_WIFI_PSK, sizeof(config.network.wifi_psk));
|
||||
#endif
|
||||
|
||||
#if defined(USERPREFS_NETWORK_IPV6_ENABLED)
|
||||
config.network.ipv6_enabled = USERPREFS_NETWORK_IPV6_ENABLED;
|
||||
#else
|
||||
config.network.ipv6_enabled = default_network_ipv6_enabled;
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_FLIP_SCREEN
|
||||
config.display.flip_screen = true;
|
||||
#endif
|
||||
@@ -978,12 +985,25 @@ void NodeDB::installDefaultChannels()
|
||||
channelFile.version = DEVICESTATE_CUR_VER;
|
||||
}
|
||||
|
||||
void NodeDB::resetNodes()
|
||||
void NodeDB::resetNodes(bool keepFavorites)
|
||||
{
|
||||
if (!config.position.fixed_position)
|
||||
clearLocalPosition();
|
||||
numMeshNodes = 1;
|
||||
std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
|
||||
if (keepFavorites) {
|
||||
LOG_INFO("Clearing node database - preserving favorites");
|
||||
for (size_t i = 0; i < meshNodes->size(); i++) {
|
||||
meshtastic_NodeDetail &detail = meshNodes->at(i);
|
||||
if (i > 0 && !detailIsFavorite(detail)) {
|
||||
detail = makeDefaultDetail();
|
||||
} else {
|
||||
numMeshNodes += 1;
|
||||
}
|
||||
};
|
||||
} else {
|
||||
LOG_INFO("Clearing node database - removing favorites");
|
||||
std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), makeDefaultDetail());
|
||||
}
|
||||
devicestate.has_rx_text_message = false;
|
||||
devicestate.has_rx_waypoint = false;
|
||||
saveNodeDatabaseToDisk();
|
||||
@@ -1002,19 +1022,17 @@ void NodeDB::removeNodeByNum(NodeNum nodeNum)
|
||||
removed++;
|
||||
}
|
||||
numMeshNodes -= removed;
|
||||
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + 1,
|
||||
meshtastic_NodeInfoLite());
|
||||
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + 1, makeDefaultDetail());
|
||||
LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Save changes", removed);
|
||||
saveNodeDatabaseToDisk();
|
||||
}
|
||||
|
||||
void NodeDB::clearLocalPosition()
|
||||
{
|
||||
meshtastic_NodeInfoLite *node = getMeshNode(nodeDB->getNodeNum());
|
||||
node->position.latitude_i = 0;
|
||||
node->position.longitude_i = 0;
|
||||
node->position.altitude = 0;
|
||||
node->position.time = 0;
|
||||
meshtastic_NodeDetail *detail = getMeshNode(nodeDB->getNodeNum());
|
||||
if (detail) {
|
||||
clearPositionFromDetail(*detail);
|
||||
}
|
||||
setLocalPosition(meshtastic_Position_init_default);
|
||||
}
|
||||
|
||||
@@ -1022,10 +1040,10 @@ void NodeDB::cleanupMeshDB()
|
||||
{
|
||||
int newPos = 0, removed = 0;
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
if (meshNodes->at(i).has_user) {
|
||||
if (meshNodes->at(i).user.public_key.size > 0) {
|
||||
if (memfll(meshNodes->at(i).user.public_key.bytes, 0, meshNodes->at(i).user.public_key.size)) {
|
||||
meshNodes->at(i).user.public_key.size = 0;
|
||||
if (detailHasFlag(meshNodes->at(i), NODEDETAIL_FLAG_HAS_USER)) {
|
||||
if (meshNodes->at(i).public_key.size > 0) {
|
||||
if (memfll(meshNodes->at(i).public_key.bytes, 0, meshNodes->at(i).public_key.size)) {
|
||||
meshNodes->at(i).public_key.size = 0;
|
||||
}
|
||||
}
|
||||
if (newPos != i)
|
||||
@@ -1038,7 +1056,7 @@ void NodeDB::cleanupMeshDB()
|
||||
}
|
||||
numMeshNodes -= removed;
|
||||
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + removed,
|
||||
meshtastic_NodeInfoLite());
|
||||
makeDefaultDetail());
|
||||
LOG_DEBUG("cleanupMeshDB purged %d entries", removed);
|
||||
}
|
||||
|
||||
@@ -1090,14 +1108,14 @@ void NodeDB::pickNewNodeNum()
|
||||
nodeNum = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5];
|
||||
}
|
||||
|
||||
meshtastic_NodeInfoLite *found;
|
||||
while (((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0) ||
|
||||
meshtastic_NodeDetail *found;
|
||||
while (((found = getMeshNode(nodeNum)) && memcmp(found->macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0) ||
|
||||
(nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED)) {
|
||||
NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice
|
||||
if (found)
|
||||
LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, by MAC ending in 0x%02x%02x vs our 0x%02x%02x, so "
|
||||
"trying for 0x%x",
|
||||
nodeNum, found->user.macaddr[4], found->user.macaddr[5], ourMacAddr[4], ourMacAddr[5], candidate);
|
||||
nodeNum, found->macaddr[4], found->macaddr[5], ourMacAddr[4], ourMacAddr[5], candidate);
|
||||
nodeNum = candidate;
|
||||
}
|
||||
LOG_DEBUG("Use nodenum 0x%x ", nodeNum);
|
||||
@@ -1396,7 +1414,7 @@ bool NodeDB::saveDeviceStateToDisk()
|
||||
FSCom.mkdir("/prefs");
|
||||
spiLock->unlock();
|
||||
#endif
|
||||
// Note: if MAX_NUM_NODES=100 and meshtastic_NodeInfoLite_size=166, so will be approximately 17KB
|
||||
// Note: if MAX_NUM_NODES=100 and meshtastic_NodeDetail_size=182, node storage alone is roughly 18KB of data
|
||||
// Because so huge we _must_ not use fullAtomic, because the filesystem is probably too small to hold two copies of this
|
||||
return saveProto(deviceStateFileName, meshtastic_DeviceState_size, &meshtastic_DeviceState_msg, &devicestate, true);
|
||||
}
|
||||
@@ -1489,7 +1507,7 @@ bool NodeDB::saveToDisk(int saveWhat)
|
||||
return success;
|
||||
}
|
||||
|
||||
const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex)
|
||||
const meshtastic_NodeDetail *NodeDB::readNextMeshNode(uint32_t &readIndex)
|
||||
{
|
||||
if (readIndex < numMeshNodes)
|
||||
return &meshNodes->at(readIndex++);
|
||||
@@ -1498,7 +1516,7 @@ const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex)
|
||||
}
|
||||
|
||||
/// Given a node, return how many seconds in the past (vs now) that we last heard from it
|
||||
uint32_t sinceLastSeen(const meshtastic_NodeInfoLite *n)
|
||||
uint32_t sinceLastSeen(const meshtastic_NodeDetail *n)
|
||||
{
|
||||
uint32_t now = getTime();
|
||||
|
||||
@@ -1528,9 +1546,10 @@ size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
|
||||
|
||||
// FIXME this implementation is kinda expensive
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
if (localOnly && meshNodes->at(i).via_mqtt)
|
||||
const auto &detail = meshNodes->at(i);
|
||||
if (localOnly && detailViaMqtt(detail))
|
||||
continue;
|
||||
if (sinceLastSeen(&meshNodes->at(i)) < NUM_ONLINE_SECS)
|
||||
if (sinceLastSeen(&detail) < NUM_ONLINE_SECS)
|
||||
numseen++;
|
||||
}
|
||||
|
||||
@@ -1544,8 +1563,8 @@ size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
|
||||
*/
|
||||
void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSource src)
|
||||
{
|
||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
|
||||
if (!info) {
|
||||
meshtastic_NodeDetail *detail = getOrCreateMeshNode(nodeId);
|
||||
if (!detail) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1555,12 +1574,13 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou
|
||||
p.altitude);
|
||||
|
||||
setLocalPosition(p);
|
||||
info->position = TypeConversions::ConvertToPositionLite(p);
|
||||
applyPositionToDetail(*detail, p);
|
||||
} 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
|
||||
// (stop-gap fix for issue #900)
|
||||
LOG_DEBUG("updatePosition SPECIAL time setting time=%u", p.time);
|
||||
info->position.time = p.time;
|
||||
detailSetFlag(*detail, NODEDETAIL_FLAG_HAS_POSITION, true);
|
||||
detail->position_time = p.time;
|
||||
} else {
|
||||
// Be careful to only update fields that have been set by the REMOTE sender
|
||||
// A lot of position reports don't have time populated. In that case, be careful to not blow away the time we
|
||||
@@ -1570,17 +1590,16 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou
|
||||
LOG_INFO("updatePosition REMOTE node=0x%x time=%u lat=%d lon=%d", nodeId, p.time, p.latitude_i, p.longitude_i);
|
||||
|
||||
// First, back up fields that we want to protect from overwrite
|
||||
uint32_t tmp_time = info->position.time;
|
||||
uint32_t tmp_time = detail->position_time;
|
||||
|
||||
// Next, update atomically
|
||||
info->position = TypeConversions::ConvertToPositionLite(p);
|
||||
applyPositionToDetail(*detail, p);
|
||||
|
||||
// Last, restore any fields that may have been overwritten
|
||||
if (!info->position.time)
|
||||
info->position.time = tmp_time;
|
||||
if (!detail->position_time)
|
||||
detail->position_time = tmp_time;
|
||||
}
|
||||
info->has_position = true;
|
||||
updateGUIforNode = info;
|
||||
updateGUIforNode = detail;
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
}
|
||||
|
||||
@@ -1589,9 +1608,9 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou
|
||||
*/
|
||||
void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxSource src)
|
||||
{
|
||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
|
||||
meshtastic_NodeDetail *detail = getOrCreateMeshNode(nodeId);
|
||||
// Environment metrics should never go to NodeDb but we'll safegaurd anyway
|
||||
if (!info || t.which_variant != meshtastic_Telemetry_device_metrics_tag) {
|
||||
if (!detail || t.which_variant != meshtastic_Telemetry_device_metrics_tag) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1601,9 +1620,8 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
|
||||
} else {
|
||||
LOG_DEBUG("updateTelemetry REMOTE node=0x%x ", nodeId);
|
||||
}
|
||||
info->device_metrics = t.variant.device_metrics;
|
||||
info->has_device_metrics = true;
|
||||
updateGUIforNode = info;
|
||||
applyMetricsToDetail(*detail, t.variant.device_metrics);
|
||||
updateGUIforNode = detail;
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
}
|
||||
|
||||
@@ -1612,39 +1630,58 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
|
||||
*/
|
||||
void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
||||
{
|
||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(contact.node_num);
|
||||
if (!info || !contact.has_user) {
|
||||
meshtastic_NodeDetail *detail = getOrCreateMeshNode(contact.node_num);
|
||||
if (!detail || !contact.has_user) {
|
||||
return;
|
||||
}
|
||||
// If the local node has this node marked as manually verified
|
||||
// and the client does not, do not allow the client to update the
|
||||
// saved public key.
|
||||
if ((info->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) && !contact.manually_verified) {
|
||||
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) {
|
||||
if (detailHasFlag(*detail, NODEDETAIL_FLAG_IS_KEY_MANUALLY_VERIFIED) && !contact.manually_verified) {
|
||||
if (contact.user.public_key.size != detail->public_key.size ||
|
||||
memcmp(contact.user.public_key.bytes, detail->public_key.bytes, detail->public_key.size) != 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
info->num = contact.node_num;
|
||||
info->has_user = true;
|
||||
info->user = TypeConversions::ConvertToUserLite(contact.user);
|
||||
detail->num = contact.node_num;
|
||||
applyUserToDetail(*detail, contact.user);
|
||||
if (contact.should_ignore) {
|
||||
// If should_ignore is set,
|
||||
// we need to clear the public key and other cruft, in addition to setting the node as ignored
|
||||
info->is_ignored = true;
|
||||
info->has_device_metrics = false;
|
||||
info->has_position = false;
|
||||
info->user.public_key.size = 0;
|
||||
info->user.public_key.bytes[0] = 0;
|
||||
detailSetFlag(*detail, NODEDETAIL_FLAG_IS_IGNORED, true);
|
||||
detailSetFlag(*detail, NODEDETAIL_FLAG_IS_FAVORITE, false);
|
||||
clearMetricsFromDetail(*detail);
|
||||
clearPositionFromDetail(*detail);
|
||||
detail->public_key.size = 0;
|
||||
memset(detail->public_key.bytes, 0, sizeof(detail->public_key.bytes));
|
||||
} else {
|
||||
info->last_heard = getValidTime(RTCQualityNTP);
|
||||
info->is_favorite = true;
|
||||
detailSetFlag(*detail, NODEDETAIL_FLAG_IS_IGNORED, false);
|
||||
/* Clients are sending add_contact before every text message DM (because clients may hold a larger node database with
|
||||
* public keys than the radio holds). However, we don't want to update last_heard just because we sent someone a DM!
|
||||
*/
|
||||
|
||||
/* "Boring old nodes" are the first to be evicted out of the node database when full. This includes a newly-zeroed
|
||||
* nodeinfo because it has: !is_favorite && last_heard==0. To keep this from happening when we addFromContact, we set the
|
||||
* new node as a favorite, and we leave last_heard alone (even if it's zero).
|
||||
*/
|
||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
|
||||
// Special case for CLIENT_BASE: is_favorite has special meaning, and we don't want to automatically set it
|
||||
// without the user doing so deliberately. We don't normally expect users to use a CLIENT_BASE to send DMs or to add
|
||||
// contacts, but we should make sure it doesn't auto-favorite in case they do. Instead, as a workaround, we'll set
|
||||
// last_heard to now, so that the add_contact node doesn't immediately get evicted.
|
||||
detail->last_heard = getTime();
|
||||
} else {
|
||||
// Normal case: set is_favorite to prevent expiration.
|
||||
// last_heard will remain as-is (or remain 0 if this entry wasn't in the nodeDB).
|
||||
detailSetFlag(*detail, NODEDETAIL_FLAG_IS_FAVORITE, true);
|
||||
}
|
||||
|
||||
// As the clients will begin sending the contact with DMs, we want to strictly check if the node is manually verified
|
||||
if (contact.manually_verified) {
|
||||
info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
|
||||
detailSetFlag(*detail, NODEDETAIL_FLAG_IS_KEY_MANUALLY_VERIFIED, true);
|
||||
}
|
||||
// Mark the node's key as manually verified to indicate trustworthiness.
|
||||
updateGUIforNode = info;
|
||||
updateGUIforNode = detail;
|
||||
sortMeshDB();
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
}
|
||||
@@ -1655,8 +1692,8 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
||||
*/
|
||||
bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex)
|
||||
{
|
||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
|
||||
if (!info) {
|
||||
meshtastic_NodeDetail *detail = getOrCreateMeshNode(nodeId);
|
||||
if (!detail) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1681,9 +1718,9 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (info->user.public_key.size == 32) { // if we have a key for this user already, don't overwrite with a new one
|
||||
if (detail->public_key.size == 32) { // if we have a key for this user already, don't overwrite with a new one
|
||||
// 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, detail->public_key.bytes, 32) != 0)) {
|
||||
LOG_WARN("Public Key mismatch, dropping NodeInfo");
|
||||
return false;
|
||||
}
|
||||
@@ -1696,22 +1733,38 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
||||
// Always ensure user.id is derived from nodeId, regardless of what was received
|
||||
snprintf(p.id, sizeof(p.id), "!%08x", nodeId);
|
||||
|
||||
// Both of info->user and p start as filled with zero so I think this is okay
|
||||
auto lite = TypeConversions::ConvertToUserLite(p);
|
||||
bool changed = memcmp(&info->user, &lite, sizeof(info->user)) || (info->channel != channelIndex);
|
||||
meshtastic_NodeDetail before = *detail;
|
||||
|
||||
info->user = lite;
|
||||
if (info->user.public_key.size == 32) {
|
||||
printBytes("Saved Pubkey: ", info->user.public_key.bytes, 32);
|
||||
applyUserToDetail(*detail, p);
|
||||
|
||||
if (detail->public_key.size == 32) {
|
||||
printBytes("Saved Pubkey: ", detail->public_key.bytes, 32);
|
||||
}
|
||||
if (nodeId != getNodeNum())
|
||||
info->channel = channelIndex; // Set channel we need to use to reach this node (but don't set our own channel)
|
||||
LOG_DEBUG("Update changed=%d user %s/%s, id=0x%08x, channel=%d", changed, info->user.long_name, info->user.short_name, nodeId,
|
||||
info->channel);
|
||||
info->has_user = true;
|
||||
|
||||
if (nodeId != getNodeNum()) {
|
||||
detail->channel = channelIndex; // Set channel we need to use to reach this node (but don't set our own channel)
|
||||
}
|
||||
|
||||
bool userChanged = strncmp(before.long_name, detail->long_name, sizeof(detail->long_name)) != 0 ||
|
||||
strncmp(before.short_name, detail->short_name, sizeof(detail->short_name)) != 0 ||
|
||||
memcmp(before.macaddr, detail->macaddr, sizeof(detail->macaddr)) != 0 ||
|
||||
before.hw_model != detail->hw_model || before.role != detail->role ||
|
||||
before.public_key.size != detail->public_key.size ||
|
||||
memcmp(before.public_key.bytes, detail->public_key.bytes, detail->public_key.size) != 0;
|
||||
|
||||
uint32_t flagDelta = before.flags ^ detail->flags;
|
||||
bool flagChange =
|
||||
(flagDelta & (NODEDETAIL_FLAG_IS_LICENSED | NODEDETAIL_FLAG_HAS_UNMESSAGABLE | NODEDETAIL_FLAG_IS_UNMESSAGABLE)) != 0;
|
||||
|
||||
bool channelChanged = (nodeId != getNodeNum()) && (before.channel != detail->channel);
|
||||
|
||||
bool changed = userChanged || flagChange || channelChanged;
|
||||
|
||||
LOG_DEBUG("Update changed=%d user %s/%s, id=0x%08x, channel=%d", changed, detail->long_name, detail->short_name, nodeId,
|
||||
detail->channel);
|
||||
|
||||
if (changed) {
|
||||
updateGUIforNode = info;
|
||||
updateGUIforNode = detail;
|
||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||
|
||||
// We just changed something about a User,
|
||||
@@ -1739,23 +1792,24 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
|
||||
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);
|
||||
|
||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getFrom(&mp));
|
||||
if (!info) {
|
||||
meshtastic_NodeDetail *detail = getOrCreateMeshNode(getFrom(&mp));
|
||||
if (!detail) {
|
||||
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;
|
||||
detail->last_heard = mp.rx_time;
|
||||
|
||||
if (mp.rx_snr)
|
||||
info->snr = mp.rx_snr; // keep the most recent SNR we received for this node.
|
||||
detail->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
|
||||
detailSetFlag(*detail, NODEDETAIL_FLAG_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;
|
||||
detailSetFlag(*detail, NODEDETAIL_FLAG_HAS_HOPS_AWAY, true);
|
||||
uint32_t hopDelta = mp.hop_start - mp.hop_limit;
|
||||
detail->hops_away = static_cast<uint8_t>(std::min<uint32_t>(hopDelta, UINT8_MAX));
|
||||
}
|
||||
sortMeshDB();
|
||||
}
|
||||
@@ -1763,9 +1817,9 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
|
||||
|
||||
void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
|
||||
{
|
||||
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
||||
if (lite && lite->is_favorite != is_favorite) {
|
||||
lite->is_favorite = is_favorite;
|
||||
meshtastic_NodeDetail *detail = getMeshNode(nodeId);
|
||||
if (detail && detailIsFavorite(*detail) != is_favorite) {
|
||||
detailSetFlag(*detail, NODEDETAIL_FLAG_IS_FAVORITE, is_favorite);
|
||||
sortMeshDB();
|
||||
saveNodeDatabaseToDisk();
|
||||
}
|
||||
@@ -1779,10 +1833,10 @@ bool NodeDB::isFavorite(uint32_t nodeId)
|
||||
if (nodeId == NODENUM_BROADCAST)
|
||||
return false;
|
||||
|
||||
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
||||
meshtastic_NodeDetail *detail = getMeshNode(nodeId);
|
||||
|
||||
if (lite) {
|
||||
return lite->is_favorite;
|
||||
if (detail) {
|
||||
return detailIsFavorite(*detail);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1798,23 +1852,21 @@ bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
|
||||
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;
|
||||
|
||||
bool seenFrom = false;
|
||||
bool seenTo = false;
|
||||
|
||||
for (int i = 0; i < numMeshNodes; i++) {
|
||||
lite = &meshNodes->at(i);
|
||||
auto *detail = &meshNodes->at(i);
|
||||
|
||||
if (lite->num == p.from) {
|
||||
if (lite->is_favorite)
|
||||
if (detail->num == p.from) {
|
||||
if (detailIsFavorite(*detail))
|
||||
return true;
|
||||
|
||||
seenFrom = true;
|
||||
}
|
||||
|
||||
if (lite->num == p.to) {
|
||||
if (lite->is_favorite)
|
||||
if (detail->num == p.to) {
|
||||
if (detailIsFavorite(*detail))
|
||||
return true;
|
||||
|
||||
seenTo = true;
|
||||
@@ -1850,10 +1902,10 @@ void NodeDB::sortMeshDB()
|
||||
// TODO: Look for at(i-1) also matching own node num, and throw the DB in the trash
|
||||
std::swap(meshNodes->at(i), meshNodes->at(i - 1));
|
||||
changed = true;
|
||||
} else if (meshNodes->at(i).is_favorite && !meshNodes->at(i - 1).is_favorite) {
|
||||
} else if (detailIsFavorite(meshNodes->at(i)) && !detailIsFavorite(meshNodes->at(i - 1))) {
|
||||
std::swap(meshNodes->at(i), meshNodes->at(i - 1));
|
||||
changed = true;
|
||||
} else if (!meshNodes->at(i).is_favorite && meshNodes->at(i - 1).is_favorite) {
|
||||
} else if (!detailIsFavorite(meshNodes->at(i)) && detailIsFavorite(meshNodes->at(i - 1))) {
|
||||
// noop
|
||||
} else if (meshNodes->at(i).last_heard > meshNodes->at(i - 1).last_heard) {
|
||||
std::swap(meshNodes->at(i), meshNodes->at(i - 1));
|
||||
@@ -1867,11 +1919,11 @@ void NodeDB::sortMeshDB()
|
||||
|
||||
uint8_t NodeDB::getMeshNodeChannel(NodeNum n)
|
||||
{
|
||||
const meshtastic_NodeInfoLite *info = getMeshNode(n);
|
||||
if (!info) {
|
||||
const meshtastic_NodeDetail *detail = getMeshNode(n);
|
||||
if (!detail) {
|
||||
return 0; // defaults to PRIMARY
|
||||
}
|
||||
return info->channel;
|
||||
return detail->channel;
|
||||
}
|
||||
|
||||
std::string NodeDB::getNodeId() const
|
||||
@@ -1883,7 +1935,7 @@ std::string NodeDB::getNodeId() const
|
||||
|
||||
/// Find a node in our DB, return null for missing
|
||||
/// NOTE: This function might be called from an ISR
|
||||
meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n)
|
||||
meshtastic_NodeDetail *NodeDB::getMeshNode(NodeNum n)
|
||||
{
|
||||
for (int i = 0; i < numMeshNodes; i++)
|
||||
if (meshNodes->at(i).num == n)
|
||||
@@ -1899,11 +1951,11 @@ bool NodeDB::isFull()
|
||||
}
|
||||
|
||||
/// Find a node in our DB, create an empty NodeInfo if missing
|
||||
meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
||||
meshtastic_NodeDetail *NodeDB::getOrCreateMeshNode(NodeNum n)
|
||||
{
|
||||
meshtastic_NodeInfoLite *lite = getMeshNode(n);
|
||||
meshtastic_NodeDetail *detail = getMeshNode(n);
|
||||
|
||||
if (!lite) {
|
||||
if (!detail) {
|
||||
if (isFull()) {
|
||||
LOG_INFO("Node database full with %i nodes and %u bytes free. Erasing oldest entry", numMeshNodes,
|
||||
memGet.getFreeHeap());
|
||||
@@ -1914,14 +1966,14 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
||||
int oldestBoringIndex = -1;
|
||||
for (int i = 1; i < numMeshNodes; i++) {
|
||||
// Simply the oldest non-favorite, non-ignored, non-verified node
|
||||
if (!meshNodes->at(i).is_favorite && !meshNodes->at(i).is_ignored &&
|
||||
!(meshNodes->at(i).bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) &&
|
||||
meshNodes->at(i).last_heard < oldest) {
|
||||
const auto &candidate = meshNodes->at(i);
|
||||
if (!detailIsFavorite(candidate) && !detailIsIgnored(candidate) &&
|
||||
!detailHasFlag(candidate, NODEDETAIL_FLAG_IS_KEY_MANUALLY_VERIFIED) && meshNodes->at(i).last_heard < oldest) {
|
||||
oldest = meshNodes->at(i).last_heard;
|
||||
oldestIndex = i;
|
||||
}
|
||||
// The oldest "boring" node
|
||||
if (!meshNodes->at(i).is_favorite && !meshNodes->at(i).is_ignored && meshNodes->at(i).user.public_key.size == 0 &&
|
||||
if (!detailIsFavorite(candidate) && !detailIsIgnored(candidate) && candidate.public_key.size == 0 &&
|
||||
meshNodes->at(i).last_heard < oldestBoring) {
|
||||
oldestBoring = meshNodes->at(i).last_heard;
|
||||
oldestBoringIndex = i;
|
||||
@@ -1941,35 +1993,36 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
||||
}
|
||||
}
|
||||
// add the node at the end
|
||||
lite = &meshNodes->at((numMeshNodes)++);
|
||||
detail = &meshNodes->at((numMeshNodes)++);
|
||||
|
||||
// everything is missing except the nodenum
|
||||
memset(lite, 0, sizeof(*lite));
|
||||
lite->num = n;
|
||||
*detail = makeDefaultDetail();
|
||||
detail->num = n;
|
||||
LOG_INFO("Adding node to database with %i nodes and %u bytes free!", numMeshNodes, memGet.getFreeHeap());
|
||||
}
|
||||
|
||||
return lite;
|
||||
return detail;
|
||||
}
|
||||
|
||||
/// Sometimes we will have Position objects that only have a time, so check for
|
||||
/// valid lat/lon
|
||||
bool NodeDB::hasValidPosition(const meshtastic_NodeInfoLite *n)
|
||||
bool NodeDB::hasValidPosition(const meshtastic_NodeDetail *n)
|
||||
{
|
||||
return n->has_position && (n->position.latitude_i != 0 || n->position.longitude_i != 0);
|
||||
return detailHasFlag(*n, NODEDETAIL_FLAG_HAS_POSITION) && (n->latitude_i != 0 || n->longitude_i != 0);
|
||||
}
|
||||
|
||||
/// If we have a node / user and they report is_licensed = true
|
||||
/// we consider them licensed
|
||||
UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum)
|
||||
{
|
||||
meshtastic_NodeInfoLite *info = getMeshNode(nodeNum);
|
||||
if (!info || !info->has_user) {
|
||||
meshtastic_NodeDetail *detail = getMeshNode(nodeNum);
|
||||
if (!detail || !detailHasFlag(*detail, NODEDETAIL_FLAG_HAS_USER)) {
|
||||
return UserLicenseStatus::NotKnown;
|
||||
}
|
||||
return info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed;
|
||||
return detailHasFlag(*detail, NODEDETAIL_FLAG_IS_LICENSED) ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed;
|
||||
}
|
||||
|
||||
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
||||
bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest)
|
||||
{
|
||||
if (keyToTest.size == 32) {
|
||||
@@ -1984,6 +2037,7 @@ bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_pub
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <Arduino.h>
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <pb_encode.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
@@ -104,8 +106,13 @@ static constexpr const char *moduleConfigFileName = "/prefs/module.proto";
|
||||
static constexpr const char *channelFileName = "/prefs/channels.proto";
|
||||
static constexpr const char *backupFileName = "/backups/backup.proto";
|
||||
|
||||
template <typename T> inline T clampValue(T value, T low, T high)
|
||||
{
|
||||
return std::max(low, std::min(value, high));
|
||||
}
|
||||
|
||||
/// Given a node, return how many seconds in the past (vs now) that we last heard from it
|
||||
uint32_t sinceLastSeen(const meshtastic_NodeInfoLite *n);
|
||||
uint32_t sinceLastSeen(const meshtastic_NodeDetail *n);
|
||||
|
||||
/// Given a packet, return how many seconds in the past (vs now) it was received
|
||||
uint32_t sinceReceived(const meshtastic_MeshPacket *p);
|
||||
@@ -135,9 +142,9 @@ class NodeDB
|
||||
// Note: these two references just point into our static array we serialize to/from disk
|
||||
|
||||
public:
|
||||
std::vector<meshtastic_NodeInfoLite> *meshNodes;
|
||||
std::vector<meshtastic_NodeDetail> *meshNodes;
|
||||
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_NodeDetail *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
||||
Observable<const meshtastic::NodeStatus *> newStatus;
|
||||
pb_size_t numMeshNodes;
|
||||
|
||||
@@ -229,7 +236,8 @@ class NodeDB
|
||||
*/
|
||||
size_t getNumOnlineMeshNodes(bool localOnly = false);
|
||||
|
||||
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(NodeNum nodeNum);
|
||||
void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(bool keepFavorites = false),
|
||||
removeNodeByNum(NodeNum nodeNum);
|
||||
|
||||
bool factoryReset(bool eraseBleBonds = false);
|
||||
|
||||
@@ -240,15 +248,15 @@ class NodeDB
|
||||
|
||||
void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role);
|
||||
|
||||
const meshtastic_NodeInfoLite *readNextMeshNode(uint32_t &readIndex);
|
||||
const meshtastic_NodeDetail *readNextMeshNode(uint32_t &readIndex);
|
||||
|
||||
meshtastic_NodeInfoLite *getMeshNodeByIndex(size_t x)
|
||||
meshtastic_NodeDetail *getMeshNodeByIndex(size_t x)
|
||||
{
|
||||
assert(x < numMeshNodes);
|
||||
return &meshNodes->at(x);
|
||||
}
|
||||
|
||||
virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n);
|
||||
virtual meshtastic_NodeDetail *getMeshNode(NodeNum n);
|
||||
size_t getNumMeshNodes() { return numMeshNodes; }
|
||||
|
||||
UserLicenseStatus getLicenseStatus(uint32_t nodeNum);
|
||||
@@ -259,7 +267,7 @@ class NodeDB
|
||||
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);
|
||||
return nodeDatabaseSize + (MAX_NUM_NODES * meshtastic_NodeDetail_size);
|
||||
}
|
||||
|
||||
// returns true if the maximum number of nodes is reached or we are running low on memory
|
||||
@@ -280,9 +288,11 @@ class NodeDB
|
||||
localPosition = position;
|
||||
}
|
||||
|
||||
bool hasValidPosition(const meshtastic_NodeInfoLite *n);
|
||||
bool hasValidPosition(const meshtastic_NodeDetail *n);
|
||||
|
||||
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
||||
bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest);
|
||||
#endif
|
||||
|
||||
bool backupPreferences(meshtastic_AdminMessage_BackupLocation location);
|
||||
bool restorePreferences(meshtastic_AdminMessage_BackupLocation location,
|
||||
@@ -301,8 +311,8 @@ class NodeDB
|
||||
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 lastSort = 0; // When last sorted the nodeDB
|
||||
/// Find a node in our DB, create an empty NodeInfoLite if missing
|
||||
meshtastic_NodeInfoLite *getOrCreateMeshNode(NodeNum n);
|
||||
/// Find a node in our DB, create an empty NodeDetail if missing
|
||||
meshtastic_NodeDetail *getOrCreateMeshNode(NodeNum n);
|
||||
|
||||
/*
|
||||
* Internal boolean to track sorting paused
|
||||
@@ -367,9 +377,265 @@ extern uint32_t error_address;
|
||||
#define NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_SHIFT 0
|
||||
#define NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK (1 << NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_SHIFT)
|
||||
|
||||
enum NodeDetailFlag : uint32_t {
|
||||
NODEDETAIL_FLAG_IS_KEY_MANUALLY_VERIFIED = 1u << 0,
|
||||
NODEDETAIL_FLAG_HAS_USER = 1u << 1,
|
||||
NODEDETAIL_FLAG_HAS_POSITION = 1u << 2,
|
||||
NODEDETAIL_FLAG_HAS_DEVICE_METRICS = 1u << 3,
|
||||
NODEDETAIL_FLAG_VIA_MQTT = 1u << 4,
|
||||
NODEDETAIL_FLAG_IS_FAVORITE = 1u << 5,
|
||||
NODEDETAIL_FLAG_IS_IGNORED = 1u << 6,
|
||||
NODEDETAIL_FLAG_HAS_HOPS_AWAY = 1u << 7,
|
||||
NODEDETAIL_FLAG_IS_LICENSED = 1u << 8,
|
||||
NODEDETAIL_FLAG_HAS_UNMESSAGABLE = 1u << 9,
|
||||
NODEDETAIL_FLAG_IS_UNMESSAGABLE = 1u << 10,
|
||||
NODEDETAIL_FLAG_HAS_BATTERY_LEVEL = 1u << 11,
|
||||
NODEDETAIL_FLAG_HAS_VOLTAGE = 1u << 12,
|
||||
NODEDETAIL_FLAG_HAS_CHANNEL_UTIL = 1u << 13,
|
||||
NODEDETAIL_FLAG_HAS_AIR_UTIL_TX = 1u << 14,
|
||||
NODEDETAIL_FLAG_HAS_UPTIME = 1u << 15
|
||||
};
|
||||
|
||||
inline bool detailHasFlag(const meshtastic_NodeDetail &detail, NodeDetailFlag flag)
|
||||
{
|
||||
return (detail.flags & static_cast<uint32_t>(flag)) != 0;
|
||||
}
|
||||
|
||||
inline void detailSetFlag(meshtastic_NodeDetail &detail, NodeDetailFlag flag, bool value = true)
|
||||
{
|
||||
if (value) {
|
||||
detail.flags |= static_cast<uint32_t>(flag);
|
||||
} else {
|
||||
detail.flags &= ~static_cast<uint32_t>(flag);
|
||||
}
|
||||
}
|
||||
|
||||
inline meshtastic_NodeDetail makeDefaultDetail()
|
||||
{
|
||||
meshtastic_NodeDetail detail = meshtastic_NodeDetail_init_default;
|
||||
return detail;
|
||||
}
|
||||
|
||||
inline void clearUserFromDetail(meshtastic_NodeDetail &detail)
|
||||
{
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_USER, false);
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_IS_LICENSED, false);
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_UNMESSAGABLE, false);
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_IS_UNMESSAGABLE, false);
|
||||
detail.long_name[0] = '\0';
|
||||
detail.short_name[0] = '\0';
|
||||
memset(detail.macaddr, 0, sizeof(detail.macaddr));
|
||||
detail.hw_model = _meshtastic_HardwareModel_MIN;
|
||||
detail.role = _meshtastic_Config_DeviceConfig_Role_MIN;
|
||||
detail.public_key.size = 0;
|
||||
memset(detail.public_key.bytes, 0, sizeof(detail.public_key.bytes));
|
||||
}
|
||||
|
||||
inline void applyUserLiteToDetail(meshtastic_NodeDetail &detail, const meshtastic_UserLite &userLite)
|
||||
{
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_USER, true);
|
||||
strncpy(detail.long_name, userLite.long_name, sizeof(detail.long_name));
|
||||
detail.long_name[sizeof(detail.long_name) - 1] = '\0';
|
||||
strncpy(detail.short_name, userLite.short_name, sizeof(detail.short_name));
|
||||
detail.short_name[sizeof(detail.short_name) - 1] = '\0';
|
||||
memcpy(detail.macaddr, userLite.macaddr, sizeof(detail.macaddr));
|
||||
detail.hw_model = userLite.hw_model;
|
||||
detail.role = userLite.role;
|
||||
|
||||
const pb_size_t keySize = std::min(userLite.public_key.size, static_cast<pb_size_t>(sizeof(detail.public_key.bytes)));
|
||||
memcpy(detail.public_key.bytes, userLite.public_key.bytes, keySize);
|
||||
detail.public_key.size = keySize;
|
||||
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_IS_LICENSED, userLite.is_licensed);
|
||||
|
||||
if (userLite.has_is_unmessagable) {
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_UNMESSAGABLE, true);
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_IS_UNMESSAGABLE, userLite.is_unmessagable);
|
||||
} else {
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_UNMESSAGABLE, false);
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_IS_UNMESSAGABLE, false);
|
||||
}
|
||||
}
|
||||
|
||||
inline void applyUserToDetail(meshtastic_NodeDetail &detail, const meshtastic_User &user)
|
||||
{
|
||||
meshtastic_UserLite lite = meshtastic_UserLite_init_default;
|
||||
strncpy(lite.long_name, user.long_name, sizeof(lite.long_name));
|
||||
lite.long_name[sizeof(lite.long_name) - 1] = '\0';
|
||||
strncpy(lite.short_name, user.short_name, sizeof(lite.short_name));
|
||||
lite.short_name[sizeof(lite.short_name) - 1] = '\0';
|
||||
lite.hw_model = user.hw_model;
|
||||
lite.role = user.role;
|
||||
lite.is_licensed = user.is_licensed;
|
||||
memcpy(lite.macaddr, user.macaddr, sizeof(lite.macaddr));
|
||||
const pb_size_t keySize = std::min(user.public_key.size, static_cast<pb_size_t>(sizeof(lite.public_key.bytes)));
|
||||
memcpy(lite.public_key.bytes, user.public_key.bytes, keySize);
|
||||
lite.public_key.size = keySize;
|
||||
lite.has_is_unmessagable = user.has_is_unmessagable;
|
||||
lite.is_unmessagable = user.is_unmessagable;
|
||||
applyUserLiteToDetail(detail, lite);
|
||||
}
|
||||
|
||||
inline void clearPositionFromDetail(meshtastic_NodeDetail &detail)
|
||||
{
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_POSITION, false);
|
||||
detail.latitude_i = 0;
|
||||
detail.longitude_i = 0;
|
||||
detail.altitude = 0;
|
||||
detail.position_time = 0;
|
||||
detail.position_source = _meshtastic_Position_LocSource_MIN;
|
||||
}
|
||||
|
||||
inline void applyPositionLiteToDetail(meshtastic_NodeDetail &detail, const meshtastic_PositionLite &positionLite)
|
||||
{
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_POSITION, true);
|
||||
detail.latitude_i = positionLite.latitude_i;
|
||||
detail.longitude_i = positionLite.longitude_i;
|
||||
detail.altitude = positionLite.altitude;
|
||||
detail.position_time = positionLite.time;
|
||||
detail.position_source = positionLite.location_source;
|
||||
}
|
||||
|
||||
inline void applyPositionToDetail(meshtastic_NodeDetail &detail, const meshtastic_Position &position)
|
||||
{
|
||||
meshtastic_PositionLite lite = meshtastic_PositionLite_init_default;
|
||||
lite.latitude_i = position.latitude_i;
|
||||
lite.longitude_i = position.longitude_i;
|
||||
lite.altitude = position.altitude;
|
||||
lite.location_source = position.location_source;
|
||||
lite.time = position.time;
|
||||
applyPositionLiteToDetail(detail, lite);
|
||||
}
|
||||
|
||||
inline void clearMetricsFromDetail(meshtastic_NodeDetail &detail)
|
||||
{
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_DEVICE_METRICS, false);
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_BATTERY_LEVEL, false);
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_VOLTAGE, false);
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_CHANNEL_UTIL, false);
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_AIR_UTIL_TX, false);
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_UPTIME, false);
|
||||
detail.battery_level = 0;
|
||||
detail.voltage_millivolts = 0;
|
||||
detail.channel_utilization_permille = 0;
|
||||
detail.air_util_tx_permille = 0;
|
||||
detail.uptime_seconds = 0;
|
||||
}
|
||||
|
||||
inline void applyMetricsToDetail(meshtastic_NodeDetail &detail, const meshtastic_DeviceMetrics &metrics)
|
||||
{
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_DEVICE_METRICS, true);
|
||||
|
||||
if (metrics.has_battery_level) {
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_BATTERY_LEVEL, true);
|
||||
uint32_t battery = metrics.battery_level;
|
||||
if (battery > 255u) {
|
||||
battery = 255u;
|
||||
}
|
||||
detail.battery_level = static_cast<uint8_t>(battery);
|
||||
} else {
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_BATTERY_LEVEL, false);
|
||||
}
|
||||
|
||||
if (metrics.has_voltage) {
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_VOLTAGE, true);
|
||||
double limitedVoltage = clampValue(static_cast<double>(metrics.voltage), 0.0, 65.535);
|
||||
int millivolts = static_cast<int>(std::lround(limitedVoltage * 1000.0));
|
||||
millivolts = clampValue<int>(millivolts, 0, 0xFFFF);
|
||||
detail.voltage_millivolts = static_cast<uint16_t>(millivolts);
|
||||
} else {
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_VOLTAGE, false);
|
||||
detail.voltage_millivolts = 0;
|
||||
}
|
||||
|
||||
if (metrics.has_channel_utilization) {
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_CHANNEL_UTIL, true);
|
||||
double limitedUtil = clampValue(static_cast<double>(metrics.channel_utilization), 0.0, 100.0);
|
||||
int permille = static_cast<int>(std::lround(limitedUtil * 10.0));
|
||||
permille = clampValue<int>(permille, 0, 1000);
|
||||
detail.channel_utilization_permille = static_cast<uint16_t>(permille);
|
||||
} else {
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_CHANNEL_UTIL, false);
|
||||
detail.channel_utilization_permille = 0;
|
||||
}
|
||||
|
||||
if (metrics.has_air_util_tx) {
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_AIR_UTIL_TX, true);
|
||||
double limitedAirUtil = clampValue(static_cast<double>(metrics.air_util_tx), 0.0, 100.0);
|
||||
int permille = static_cast<int>(std::lround(limitedAirUtil * 10.0));
|
||||
permille = clampValue<int>(permille, 0, 1000);
|
||||
detail.air_util_tx_permille = static_cast<uint16_t>(permille);
|
||||
} else {
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_AIR_UTIL_TX, false);
|
||||
detail.air_util_tx_permille = 0;
|
||||
}
|
||||
|
||||
if (metrics.has_uptime_seconds) {
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_UPTIME, true);
|
||||
detail.uptime_seconds = metrics.uptime_seconds;
|
||||
} else {
|
||||
detailSetFlag(detail, NODEDETAIL_FLAG_HAS_UPTIME, false);
|
||||
detail.uptime_seconds = 0;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool detailIsFavorite(const meshtastic_NodeDetail &detail)
|
||||
{
|
||||
return detailHasFlag(detail, NODEDETAIL_FLAG_IS_FAVORITE);
|
||||
}
|
||||
|
||||
inline bool detailIsIgnored(const meshtastic_NodeDetail &detail)
|
||||
{
|
||||
return detailHasFlag(detail, NODEDETAIL_FLAG_IS_IGNORED);
|
||||
}
|
||||
|
||||
inline bool detailViaMqtt(const meshtastic_NodeDetail &detail)
|
||||
{
|
||||
return detailHasFlag(detail, NODEDETAIL_FLAG_VIA_MQTT);
|
||||
}
|
||||
|
||||
inline meshtastic_PositionLite detailToPositionLite(const meshtastic_NodeDetail &detail)
|
||||
{
|
||||
meshtastic_PositionLite lite = meshtastic_PositionLite_init_default;
|
||||
if (!detailHasFlag(detail, NODEDETAIL_FLAG_HAS_POSITION)) {
|
||||
return lite;
|
||||
}
|
||||
|
||||
lite.latitude_i = detail.latitude_i;
|
||||
lite.longitude_i = detail.longitude_i;
|
||||
lite.altitude = detail.altitude;
|
||||
lite.time = detail.position_time;
|
||||
lite.location_source = detail.position_source;
|
||||
return lite;
|
||||
}
|
||||
|
||||
inline meshtastic_UserLite detailToUserLite(const meshtastic_NodeDetail &detail)
|
||||
{
|
||||
meshtastic_UserLite lite = meshtastic_UserLite_init_default;
|
||||
if (!detailHasFlag(detail, NODEDETAIL_FLAG_HAS_USER)) {
|
||||
return lite;
|
||||
}
|
||||
|
||||
strncpy(lite.long_name, detail.long_name, sizeof(lite.long_name));
|
||||
lite.long_name[sizeof(lite.long_name) - 1] = '\0';
|
||||
strncpy(lite.short_name, detail.short_name, sizeof(lite.short_name));
|
||||
lite.short_name[sizeof(lite.short_name) - 1] = '\0';
|
||||
lite.hw_model = detail.hw_model;
|
||||
lite.role = detail.role;
|
||||
lite.is_licensed = detailHasFlag(detail, NODEDETAIL_FLAG_IS_LICENSED);
|
||||
memcpy(lite.macaddr, detail.macaddr, sizeof(lite.macaddr));
|
||||
lite.public_key.size = std::min(static_cast<pb_size_t>(sizeof(lite.public_key.bytes)), detail.public_key.size);
|
||||
memcpy(lite.public_key.bytes, detail.public_key.bytes, lite.public_key.size);
|
||||
if (detailHasFlag(detail, NODEDETAIL_FLAG_HAS_UNMESSAGABLE)) {
|
||||
lite.has_is_unmessagable = true;
|
||||
lite.is_unmessagable = detailHasFlag(detail, NODEDETAIL_FLAG_IS_UNMESSAGABLE);
|
||||
}
|
||||
return lite;
|
||||
}
|
||||
|
||||
#define Module_Config_size \
|
||||
(ModuleConfig_CannedMessageConfig_size + ModuleConfig_ExternalNotificationConfig_size + ModuleConfig_MQTTConfig_size + \
|
||||
ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \
|
||||
ModuleConfig_TelemetryConfig_size + ModuleConfig_size)
|
||||
|
||||
// Please do not remove this comment, it makes trunk and compiler happy at the same time.
|
||||
// Please do not remove this comment, it makes trunk and compiler happy at the same time.
|
||||
|
||||
@@ -87,6 +87,18 @@ void PhoneAPI::handleStartConfig()
|
||||
void PhoneAPI::close()
|
||||
{
|
||||
LOG_DEBUG("PhoneAPI::close()");
|
||||
if (service->api_state == service->STATE_BLE && api_type == TYPE_BLE)
|
||||
service->api_state = service->STATE_DISCONNECTED;
|
||||
else if (service->api_state == service->STATE_WIFI && api_type == TYPE_WIFI)
|
||||
service->api_state = service->STATE_DISCONNECTED;
|
||||
else if (service->api_state == service->STATE_SERIAL && api_type == TYPE_SERIAL)
|
||||
service->api_state = service->STATE_DISCONNECTED;
|
||||
else if (service->api_state == service->STATE_PACKET && api_type == TYPE_PACKET)
|
||||
service->api_state = service->STATE_DISCONNECTED;
|
||||
else if (service->api_state == service->STATE_HTTP && api_type == TYPE_HTTP)
|
||||
service->api_state = service->STATE_DISCONNECTED;
|
||||
else if (service->api_state == service->STATE_ETH && api_type == TYPE_ETH)
|
||||
service->api_state = service->STATE_DISCONNECTED;
|
||||
|
||||
if (state != STATE_SEND_NOTHING) {
|
||||
state = STATE_SEND_NOTHING;
|
||||
@@ -255,9 +267,9 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
|
||||
case STATE_SEND_OWN_NODEINFO: {
|
||||
LOG_DEBUG("Send My NodeInfo");
|
||||
auto us = nodeDB->readNextMeshNode(readIndex);
|
||||
const meshtastic_NodeDetail *us = nodeDB->readNextMeshNode(readIndex);
|
||||
if (us) {
|
||||
auto info = TypeConversions::ConvertToNodeInfo(us);
|
||||
auto info = TypeConversions::ConvertToNodeInfo(*us);
|
||||
info.has_hops_away = false;
|
||||
info.is_favorite = true;
|
||||
{
|
||||
@@ -578,6 +590,19 @@ void PhoneAPI::sendConfigComplete()
|
||||
fromRadioScratch.config_complete_id = config_nonce;
|
||||
config_nonce = 0;
|
||||
state = STATE_SEND_PACKETS;
|
||||
if (api_type == TYPE_BLE) {
|
||||
service->api_state = service->STATE_BLE;
|
||||
} else if (api_type == TYPE_WIFI) {
|
||||
service->api_state = service->STATE_WIFI;
|
||||
} else if (api_type == TYPE_SERIAL) {
|
||||
service->api_state = service->STATE_SERIAL;
|
||||
} else if (api_type == TYPE_PACKET) {
|
||||
service->api_state = service->STATE_PACKET;
|
||||
} else if (api_type == TYPE_HTTP) {
|
||||
service->api_state = service->STATE_HTTP;
|
||||
} else if (api_type == TYPE_ETH) {
|
||||
service->api_state = service->STATE_ETH;
|
||||
}
|
||||
|
||||
// Allow subclasses to know we've entered steady-state so they can lower power consumption
|
||||
onConfigComplete();
|
||||
@@ -608,11 +633,11 @@ void PhoneAPI::prefetchNodeInfos()
|
||||
{
|
||||
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||
while (nodeInfoQueue.size() < kNodePrefetchDepth) {
|
||||
auto nextNode = nodeDB->readNextMeshNode(readIndex);
|
||||
const meshtastic_NodeDetail *nextNode = nodeDB->readNextMeshNode(readIndex);
|
||||
if (!nextNode)
|
||||
break;
|
||||
|
||||
auto info = TypeConversions::ConvertToNodeInfo(nextNode);
|
||||
auto info = TypeConversions::ConvertToNodeInfo(*nextNode);
|
||||
bool isUs = info.num == nodeDB->getNodeNum();
|
||||
info.hops_away = isUs ? 0 : info.hops_away;
|
||||
info.last_heard = isUs ? getValidTime(RTCQualityFromNet) : info.last_heard;
|
||||
|
||||
@@ -167,6 +167,18 @@ class PhoneAPI
|
||||
/// begin a new connection
|
||||
void handleStartConfig();
|
||||
|
||||
enum APIType {
|
||||
TYPE_NONE, // Initial state, don't send anything until the client starts asking for config
|
||||
TYPE_BLE,
|
||||
TYPE_WIFI,
|
||||
TYPE_SERIAL,
|
||||
TYPE_PACKET,
|
||||
TYPE_HTTP,
|
||||
TYPE_ETH
|
||||
};
|
||||
|
||||
APIType api_type = TYPE_NONE;
|
||||
|
||||
private:
|
||||
void releasePhonePacket();
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ template <class T> class ProtobufModule : protected SinglePortModule
|
||||
const pb_msgdesc_t *fields;
|
||||
|
||||
public:
|
||||
uint8_t numOnlineNodes = 0;
|
||||
uint16_t numOnlineNodes = 0;
|
||||
/** Constructor
|
||||
* name is for debugging output
|
||||
*/
|
||||
@@ -58,7 +58,7 @@ template <class T> class ProtobufModule : protected SinglePortModule
|
||||
const char *getSenderShortName(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
auto node = nodeDB->getMeshNode(getFrom(&mp));
|
||||
const char *sender = (node) ? node->user.short_name : "???";
|
||||
const char *sender = (node) ? node->short_name : "???";
|
||||
return sender;
|
||||
}
|
||||
|
||||
|
||||
@@ -119,11 +119,16 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
|
||||
// stop the immediate relayer's retransmissions.
|
||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
|
||||
}
|
||||
} else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 &&
|
||||
(nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) {
|
||||
LOG_INFO("PKI packet from unknown node, send PKI_UNKNOWN_PUBKEY");
|
||||
sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
|
||||
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
|
||||
} else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0) {
|
||||
const meshtastic_NodeDetail *fromDetail = nodeDB->getMeshNode(p->from);
|
||||
if (!fromDetail || fromDetail->public_key.size == 0) {
|
||||
LOG_INFO("PKI packet from unknown node, send PKI_UNKNOWN_PUBKEY");
|
||||
sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
|
||||
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
|
||||
} else {
|
||||
sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(),
|
||||
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
|
||||
}
|
||||
} else {
|
||||
// Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded
|
||||
sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(),
|
||||
|
||||
@@ -100,21 +100,20 @@ bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
|
||||
// Optimized search for favorite routers with matching last byte
|
||||
// Check ordering optimized for IoT devices (cheapest checks first)
|
||||
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||
const meshtastic_NodeDetail *node = nodeDB->getMeshNodeByIndex(i);
|
||||
if (!node)
|
||||
continue;
|
||||
|
||||
// Check 1: is_favorite (cheapest - single bool)
|
||||
if (!node->is_favorite)
|
||||
if (!detailIsFavorite(*node))
|
||||
continue;
|
||||
|
||||
// Check 2: has_user (cheap - single bool)
|
||||
if (!node->has_user)
|
||||
if (!detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER))
|
||||
continue;
|
||||
|
||||
// Check 3: role check (moderate cost - multiple comparisons)
|
||||
if (!IS_ONE_OF(node->user.role, meshtastic_Config_DeviceConfig_Role_ROUTER,
|
||||
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
|
||||
if (!IS_ONE_OF(node->role, meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -261,7 +260,7 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src)
|
||||
|
||||
// don't override if a channel was requested and no need to set it when PKI is enforced
|
||||
if (!p->channel && !p->pki_encrypted && !isBroadcast(p->to)) {
|
||||
meshtastic_NodeInfoLite const *node = nodeDB->getMeshNode(p->to);
|
||||
const meshtastic_NodeDetail *node = nodeDB->getMeshNode(p->to);
|
||||
if (node) {
|
||||
p->channel = node->channel;
|
||||
LOG_DEBUG("localSend to channel %d", p->channel);
|
||||
@@ -409,8 +408,11 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
||||
{
|
||||
concurrency::LockGuard g(cryptLock);
|
||||
|
||||
const meshtastic_NodeDetail *fromDetail = nodeDB->getMeshNode(p->from);
|
||||
const meshtastic_NodeDetail *toDetail = nodeDB->getMeshNode(p->to);
|
||||
|
||||
if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY &&
|
||||
(nodeDB->getMeshNode(p->from) == NULL || !nodeDB->getMeshNode(p->from)->has_user)) {
|
||||
(!fromDetail || !detailHasFlag(*fromDetail, NODEDETAIL_FLAG_HAS_USER))) {
|
||||
LOG_DEBUG("Node 0x%x not in nodeDB-> Rebroadcast mode KNOWN_ONLY will ignore packet", p->from);
|
||||
return DecodeState::DECODE_FAILURE;
|
||||
}
|
||||
@@ -427,33 +429,38 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
||||
ChannelIndex chIndex = 0;
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
||||
// Attempt PKI decryption first
|
||||
if (p->channel == 0 && isToUs(p) && p->to > 0 && !isBroadcast(p->to) && nodeDB->getMeshNode(p->from) != nullptr &&
|
||||
nodeDB->getMeshNode(p->from)->user.public_key.size > 0 && nodeDB->getMeshNode(p->to)->user.public_key.size > 0 &&
|
||||
if (p->channel == 0 && isToUs(p) && p->to > 0 && !isBroadcast(p->to) && fromDetail != nullptr && toDetail != nullptr &&
|
||||
rawSize > MESHTASTIC_PKC_OVERHEAD) {
|
||||
LOG_DEBUG("Attempt PKI decryption");
|
||||
meshtastic_UserLite fromLite = meshtastic_UserLite_init_default;
|
||||
meshtastic_UserLite toLite = meshtastic_UserLite_init_default;
|
||||
fromLite = detailToUserLite(*fromDetail);
|
||||
toLite = detailToUserLite(*toDetail);
|
||||
|
||||
if (crypto->decryptCurve25519(p->from, nodeDB->getMeshNode(p->from)->user.public_key, p->id, rawSize, p->encrypted.bytes,
|
||||
bytes)) {
|
||||
LOG_INFO("PKI Decryption worked!");
|
||||
if (fromLite.public_key.size == 32 && toLite.public_key.size == 32) {
|
||||
LOG_DEBUG("Attempt PKI decryption");
|
||||
|
||||
meshtastic_Data decodedtmp;
|
||||
memset(&decodedtmp, 0, sizeof(decodedtmp));
|
||||
rawSize -= MESHTASTIC_PKC_OVERHEAD;
|
||||
if (pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &decodedtmp) &&
|
||||
decodedtmp.portnum != meshtastic_PortNum_UNKNOWN_APP) {
|
||||
decrypted = true;
|
||||
LOG_INFO("Packet decrypted using PKI!");
|
||||
p->pki_encrypted = true;
|
||||
memcpy(&p->public_key.bytes, nodeDB->getMeshNode(p->from)->user.public_key.bytes, 32);
|
||||
p->public_key.size = 32;
|
||||
p->decoded = decodedtmp;
|
||||
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded
|
||||
if (crypto->decryptCurve25519(p->from, fromLite.public_key, p->id, rawSize, p->encrypted.bytes, bytes)) {
|
||||
LOG_INFO("PKI Decryption worked!");
|
||||
|
||||
meshtastic_Data decodedtmp;
|
||||
memset(&decodedtmp, 0, sizeof(decodedtmp));
|
||||
rawSize -= MESHTASTIC_PKC_OVERHEAD;
|
||||
if (pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &decodedtmp) &&
|
||||
decodedtmp.portnum != meshtastic_PortNum_UNKNOWN_APP) {
|
||||
decrypted = true;
|
||||
LOG_INFO("Packet decrypted using PKI!");
|
||||
p->pki_encrypted = true;
|
||||
memcpy(&p->public_key.bytes, fromLite.public_key.bytes, 32);
|
||||
p->public_key.size = 32;
|
||||
p->decoded = decodedtmp;
|
||||
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded
|
||||
} else {
|
||||
LOG_ERROR("PKC Decrypted, but pb_decode failed!");
|
||||
return DecodeState::DECODE_FAILURE;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR("PKC Decrypted, but pb_decode failed!");
|
||||
return DecodeState::DECODE_FAILURE;
|
||||
LOG_WARN("PKC decrypt attempted but failed!");
|
||||
}
|
||||
} else {
|
||||
LOG_WARN("PKC decrypt attempted but failed!");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -479,6 +486,11 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
||||
LOG_ERROR("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)!", p->id);
|
||||
} else if (decodedtmp.portnum == meshtastic_PortNum_UNKNOWN_APP) {
|
||||
LOG_ERROR("Invalid portnum (bad psk?)!");
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
||||
} else if (!owner.is_licensed && isToUs(p) && decodedtmp.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP) {
|
||||
LOG_WARN("Rejecting legacy DM");
|
||||
return DecodeState::DECODE_FAILURE;
|
||||
#endif
|
||||
} else {
|
||||
p->decoded = decodedtmp;
|
||||
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded
|
||||
@@ -591,7 +603,11 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
||||
ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it
|
||||
|
||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->to);
|
||||
const meshtastic_NodeDetail *node = nodeDB->getMeshNode(p->to);
|
||||
meshtastic_UserLite destLite = meshtastic_UserLite_init_default;
|
||||
if (node) {
|
||||
destLite = detailToUserLite(*node);
|
||||
}
|
||||
// We may want to retool things so we can send a PKC packet when the client specifies a key and nodenum, even if the node
|
||||
// is not in the local nodedb
|
||||
// First, only PKC encrypt packets we are originating
|
||||
@@ -608,7 +624,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
||||
// Check for valid keys and single node destination
|
||||
config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr &&
|
||||
// Check for a known public key for the destination
|
||||
(node->user.public_key.size == 32) &&
|
||||
(destLite.public_key.size == 32) &&
|
||||
// Some portnums either make no sense to send with PKC
|
||||
p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP &&
|
||||
p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP) {
|
||||
@@ -616,12 +632,12 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
||||
if (numbytes + MESHTASTIC_HEADER_LENGTH + MESHTASTIC_PKC_OVERHEAD > MAX_LORA_PAYLOAD_LEN)
|
||||
return meshtastic_Routing_Error_TOO_LARGE;
|
||||
if (p->pki_encrypted && !memfll(p->public_key.bytes, 0, 32) &&
|
||||
memcmp(p->public_key.bytes, node->user.public_key.bytes, 32) != 0) {
|
||||
memcmp(p->public_key.bytes, destLite.public_key.bytes, 32) != 0) {
|
||||
LOG_WARN("Client public key differs from requested: 0x%02x, stored key begins 0x%02x", *p->public_key.bytes,
|
||||
*node->user.public_key.bytes);
|
||||
*destLite.public_key.bytes);
|
||||
return meshtastic_Routing_Error_PKI_FAILED;
|
||||
}
|
||||
crypto->encryptCurve25519(p->to, getFrom(p), node->user.public_key, p->id, numbytes, bytes, p->encrypted.bytes);
|
||||
crypto->encryptCurve25519(p->to, getFrom(p), destLite.public_key, p->id, numbytes, bytes, p->encrypted.bytes);
|
||||
numbytes += MESHTASTIC_PKC_OVERHEAD;
|
||||
p->channel = 0;
|
||||
p->pki_encrypted = true;
|
||||
@@ -771,8 +787,8 @@ void Router::perhapsHandleReceived(meshtastic_MeshPacket *p)
|
||||
return;
|
||||
}
|
||||
|
||||
meshtastic_NodeInfoLite const *node = nodeDB->getMeshNode(p->from);
|
||||
if (node != NULL && node->is_ignored) {
|
||||
const meshtastic_NodeDetail *node = nodeDB->getMeshNode(p->from);
|
||||
if (node != NULL && detailIsIgnored(*node)) {
|
||||
LOG_DEBUG("Ignore msg, 0x%x is ignored", p->from);
|
||||
packetPool.release(p);
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#include "TypeConversions.h"
|
||||
#include "mesh/generated/meshtastic/deviceonly.pb.h"
|
||||
#include "mesh/generated/meshtastic/mesh.pb.h"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfoLite *lite)
|
||||
{
|
||||
@@ -45,6 +48,196 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo
|
||||
return info;
|
||||
}
|
||||
|
||||
meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeDetail &detail)
|
||||
{
|
||||
meshtastic_NodeInfo info = meshtastic_NodeInfo_init_default;
|
||||
|
||||
info.num = detail.num;
|
||||
info.snr = detail.snr;
|
||||
info.last_heard = detail.last_heard;
|
||||
info.channel = detail.channel;
|
||||
info.via_mqtt = detail.flags & NODEDETAIL_FLAG_VIA_MQTT;
|
||||
info.is_favorite = detail.flags & NODEDETAIL_FLAG_IS_FAVORITE;
|
||||
info.is_ignored = detail.flags & NODEDETAIL_FLAG_IS_IGNORED;
|
||||
info.is_key_manually_verified = detail.flags & NODEDETAIL_FLAG_IS_KEY_MANUALLY_VERIFIED;
|
||||
|
||||
if (detail.flags & NODEDETAIL_FLAG_HAS_HOPS_AWAY) {
|
||||
info.has_hops_away = true;
|
||||
info.hops_away = detail.hops_away;
|
||||
}
|
||||
|
||||
if (detail.flags & NODEDETAIL_FLAG_HAS_POSITION) {
|
||||
info.has_position = true;
|
||||
info.position = meshtastic_Position_init_default;
|
||||
if (detail.latitude_i != 0) {
|
||||
info.position.has_latitude_i = true;
|
||||
}
|
||||
info.position.latitude_i = detail.latitude_i;
|
||||
if (detail.longitude_i != 0) {
|
||||
info.position.has_longitude_i = true;
|
||||
}
|
||||
info.position.longitude_i = detail.longitude_i;
|
||||
if (detail.altitude != 0) {
|
||||
info.position.has_altitude = true;
|
||||
}
|
||||
info.position.altitude = detail.altitude;
|
||||
info.position.location_source = detail.position_source;
|
||||
info.position.time = detail.position_time;
|
||||
}
|
||||
|
||||
if (detail.flags & NODEDETAIL_FLAG_HAS_USER) {
|
||||
info.has_user = true;
|
||||
meshtastic_User user = meshtastic_User_init_default;
|
||||
snprintf(user.id, sizeof(user.id), "!%08x", detail.num);
|
||||
strncpy(user.long_name, detail.long_name, sizeof(user.long_name));
|
||||
user.long_name[sizeof(user.long_name) - 1] = '\0';
|
||||
strncpy(user.short_name, detail.short_name, sizeof(user.short_name));
|
||||
user.short_name[sizeof(user.short_name) - 1] = '\0';
|
||||
user.hw_model = detail.hw_model;
|
||||
user.role = detail.role;
|
||||
user.is_licensed = detail.flags & NODEDETAIL_FLAG_IS_LICENSED;
|
||||
memcpy(user.macaddr, detail.macaddr, sizeof(user.macaddr));
|
||||
const pb_size_t keySize = std::min(detail.public_key.size, static_cast<pb_size_t>(sizeof(user.public_key.bytes)));
|
||||
memcpy(user.public_key.bytes, detail.public_key.bytes, keySize);
|
||||
user.public_key.size = keySize;
|
||||
if (detail.flags & NODEDETAIL_FLAG_HAS_UNMESSAGABLE) {
|
||||
user.has_is_unmessagable = true;
|
||||
user.is_unmessagable = detail.flags & NODEDETAIL_FLAG_IS_UNMESSAGABLE;
|
||||
}
|
||||
info.user = user;
|
||||
}
|
||||
|
||||
if (detail.flags & NODEDETAIL_FLAG_HAS_DEVICE_METRICS) {
|
||||
info.has_device_metrics = true;
|
||||
meshtastic_DeviceMetrics metrics = meshtastic_DeviceMetrics_init_default;
|
||||
|
||||
if (detail.flags & NODEDETAIL_FLAG_HAS_BATTERY_LEVEL) {
|
||||
metrics.has_battery_level = true;
|
||||
metrics.battery_level = detail.battery_level;
|
||||
}
|
||||
if (detail.flags & NODEDETAIL_FLAG_HAS_VOLTAGE) {
|
||||
metrics.has_voltage = true;
|
||||
metrics.voltage = static_cast<float>(detail.voltage_millivolts) / 1000.0f;
|
||||
}
|
||||
if (detail.flags & NODEDETAIL_FLAG_HAS_CHANNEL_UTIL) {
|
||||
metrics.has_channel_utilization = true;
|
||||
metrics.channel_utilization = static_cast<float>(detail.channel_utilization_permille) / 10.0f;
|
||||
}
|
||||
if (detail.flags & NODEDETAIL_FLAG_HAS_AIR_UTIL_TX) {
|
||||
metrics.has_air_util_tx = true;
|
||||
metrics.air_util_tx = static_cast<float>(detail.air_util_tx_permille) / 10.0f;
|
||||
}
|
||||
if (detail.flags & NODEDETAIL_FLAG_HAS_UPTIME) {
|
||||
metrics.has_uptime_seconds = true;
|
||||
metrics.uptime_seconds = detail.uptime_seconds;
|
||||
}
|
||||
|
||||
info.device_metrics = metrics;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
meshtastic_NodeDetail TypeConversions::ConvertToNodeDetail(const meshtastic_NodeInfoLite &lite)
|
||||
{
|
||||
meshtastic_NodeDetail detail = meshtastic_NodeDetail_init_default;
|
||||
|
||||
detail.num = lite.num;
|
||||
detail.snr = lite.snr;
|
||||
detail.last_heard = lite.last_heard;
|
||||
detail.channel = lite.channel;
|
||||
detail.next_hop = lite.next_hop;
|
||||
if (lite.has_hops_away) {
|
||||
detail.flags |= NODEDETAIL_FLAG_HAS_HOPS_AWAY;
|
||||
detail.hops_away = lite.hops_away;
|
||||
}
|
||||
if (lite.via_mqtt) {
|
||||
detail.flags |= NODEDETAIL_FLAG_VIA_MQTT;
|
||||
}
|
||||
if (lite.is_favorite) {
|
||||
detail.flags |= NODEDETAIL_FLAG_IS_FAVORITE;
|
||||
}
|
||||
if (lite.is_ignored) {
|
||||
detail.flags |= NODEDETAIL_FLAG_IS_IGNORED;
|
||||
}
|
||||
if (lite.bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) {
|
||||
detail.flags |= NODEDETAIL_FLAG_IS_KEY_MANUALLY_VERIFIED;
|
||||
}
|
||||
|
||||
if (lite.has_user) {
|
||||
detail.flags |= NODEDETAIL_FLAG_HAS_USER;
|
||||
strncpy(detail.long_name, lite.user.long_name, sizeof(detail.long_name));
|
||||
detail.long_name[sizeof(detail.long_name) - 1] = '\0';
|
||||
strncpy(detail.short_name, lite.user.short_name, sizeof(detail.short_name));
|
||||
detail.short_name[sizeof(detail.short_name) - 1] = '\0';
|
||||
detail.hw_model = lite.user.hw_model;
|
||||
detail.role = lite.user.role;
|
||||
memcpy(detail.macaddr, lite.user.macaddr, sizeof(detail.macaddr));
|
||||
const pb_size_t keySize = std::min(lite.user.public_key.size, static_cast<pb_size_t>(sizeof(detail.public_key.bytes)));
|
||||
memcpy(detail.public_key.bytes, lite.user.public_key.bytes, keySize);
|
||||
detail.public_key.size = keySize;
|
||||
if (lite.user.is_licensed) {
|
||||
detail.flags |= NODEDETAIL_FLAG_IS_LICENSED;
|
||||
}
|
||||
if (lite.user.has_is_unmessagable) {
|
||||
detail.flags |= NODEDETAIL_FLAG_HAS_UNMESSAGABLE;
|
||||
if (lite.user.is_unmessagable) {
|
||||
detail.flags |= NODEDETAIL_FLAG_IS_UNMESSAGABLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lite.has_position) {
|
||||
detail.flags |= NODEDETAIL_FLAG_HAS_POSITION;
|
||||
detail.latitude_i = lite.position.latitude_i;
|
||||
detail.longitude_i = lite.position.longitude_i;
|
||||
detail.altitude = lite.position.altitude;
|
||||
detail.position_time = lite.position.time;
|
||||
detail.position_source = lite.position.location_source;
|
||||
}
|
||||
|
||||
if (lite.has_device_metrics) {
|
||||
detail.flags |= NODEDETAIL_FLAG_HAS_DEVICE_METRICS;
|
||||
const meshtastic_DeviceMetrics &metrics = lite.device_metrics;
|
||||
|
||||
if (metrics.has_battery_level) {
|
||||
detail.flags |= NODEDETAIL_FLAG_HAS_BATTERY_LEVEL;
|
||||
uint32_t battery = metrics.battery_level;
|
||||
if (battery > 255u) {
|
||||
battery = 255u;
|
||||
}
|
||||
detail.battery_level = static_cast<uint8_t>(battery);
|
||||
}
|
||||
if (metrics.has_voltage) {
|
||||
detail.flags |= NODEDETAIL_FLAG_HAS_VOLTAGE;
|
||||
double limitedVoltage = clampValue(static_cast<double>(metrics.voltage), 0.0, 65.535);
|
||||
int millivolts = static_cast<int>(std::lround(limitedVoltage * 1000.0));
|
||||
millivolts = clampValue<int>(millivolts, 0, 0xFFFF);
|
||||
detail.voltage_millivolts = static_cast<uint16_t>(millivolts);
|
||||
}
|
||||
if (metrics.has_channel_utilization) {
|
||||
detail.flags |= NODEDETAIL_FLAG_HAS_CHANNEL_UTIL;
|
||||
double limitedUtil = clampValue(static_cast<double>(metrics.channel_utilization), 0.0, 100.0);
|
||||
int permille = static_cast<int>(std::lround(limitedUtil * 10.0));
|
||||
permille = clampValue<int>(permille, 0, 1000);
|
||||
detail.channel_utilization_permille = static_cast<uint16_t>(permille);
|
||||
}
|
||||
if (metrics.has_air_util_tx) {
|
||||
detail.flags |= NODEDETAIL_FLAG_HAS_AIR_UTIL_TX;
|
||||
double limitedAirUtil = clampValue(static_cast<double>(metrics.air_util_tx), 0.0, 100.0);
|
||||
int permille = static_cast<int>(std::lround(limitedAirUtil * 10.0));
|
||||
permille = clampValue<int>(permille, 0, 1000);
|
||||
detail.air_util_tx_permille = static_cast<uint16_t>(permille);
|
||||
}
|
||||
if (metrics.has_uptime_seconds) {
|
||||
detail.flags |= NODEDETAIL_FLAG_HAS_UPTIME;
|
||||
detail.uptime_seconds = metrics.uptime_seconds;
|
||||
}
|
||||
}
|
||||
|
||||
return detail;
|
||||
}
|
||||
|
||||
meshtastic_PositionLite TypeConversions::ConvertToPositionLite(meshtastic_Position position)
|
||||
{
|
||||
meshtastic_PositionLite lite = meshtastic_PositionLite_init_default;
|
||||
|
||||
@@ -8,6 +8,8 @@ class TypeConversions
|
||||
{
|
||||
public:
|
||||
static meshtastic_NodeInfo ConvertToNodeInfo(const meshtastic_NodeInfoLite *lite);
|
||||
static meshtastic_NodeInfo ConvertToNodeInfo(const meshtastic_NodeDetail &detail);
|
||||
static meshtastic_NodeDetail ConvertToNodeDetail(const meshtastic_NodeInfoLite &lite);
|
||||
static meshtastic_PositionLite ConvertToPositionLite(meshtastic_Position position);
|
||||
static meshtastic_Position ConvertToPosition(meshtastic_PositionLite lite);
|
||||
static meshtastic_UserLite ConvertToUserLite(meshtastic_User user);
|
||||
|
||||
@@ -19,6 +19,7 @@ PacketAPI *PacketAPI::create(PacketServer *_server)
|
||||
PacketAPI::PacketAPI(PacketServer *_server)
|
||||
: concurrency::OSThread("PacketAPI"), isConnected(false), programmingMode(false), server(_server)
|
||||
{
|
||||
api_type = TYPE_PACKET;
|
||||
}
|
||||
|
||||
int32_t PacketAPI::runOnce()
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user