mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-22 03:02:08 +10:00
Compare commits
108
Commits
| 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 | ||
|
|
a579a9d011 | ||
|
|
6b55ec6603 | ||
|
|
f2400c9dc6 | ||
|
|
0a13bcaabf | ||
|
|
03f69b3b77 | ||
|
|
3ed831b8a3 | ||
|
|
83954293d8 | ||
|
|
91621427f1 | ||
|
|
cf716fe5ef | ||
|
|
8d1b9c9dce | ||
|
|
538c05ad6c | ||
|
|
f6370bea8f | ||
|
|
468247fb94 | ||
|
|
3ae7e54681 | ||
|
|
597fa0b382 | ||
|
|
b5b9dc310f | ||
|
|
a7796fc7b4 |
@@ -1,7 +1,7 @@
|
|||||||
# trunk-ignore-all(terrascan/AC_DOCKER_0002): Known terrascan issue
|
# 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/DL3008): Do not pin apt package versions
|
||||||
# trunk-ignore-all(hadolint/DL3013): Do not pin pip 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
|
USER root
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/python:1": {
|
"ghcr.io/devcontainers/features/python:1": {
|
||||||
"installTools": true,
|
"installTools": true,
|
||||||
"version": "3.13"
|
"version": "3.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"customizations": {
|
"customizations": {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ runs:
|
|||||||
using: composite
|
using: composite
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
path: meshtasticd
|
path: meshtasticd
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
artifact-id: ${{ steps.upload.outputs.artifact-id }}
|
artifact-id: ${{ steps.upload.outputs.artifact-id }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
setup:
|
setup:
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
version:
|
version:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Get release version string
|
- name: Get release version string
|
||||||
run: |
|
run: |
|
||||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||||
@@ -108,7 +108,7 @@ jobs:
|
|||||||
needs: [version, build]
|
needs: [version, build]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ jobs:
|
|||||||
|
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
if: ${{ inputs.target != '' }}
|
if: ${{ inputs.target != '' }}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Get release version string
|
- name: Get release version string
|
||||||
run: |
|
run: |
|
||||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||||
@@ -114,7 +114,7 @@ jobs:
|
|||||||
needs: [version, build]
|
needs: [version, build]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ jobs:
|
|||||||
runs-on: ${{ inputs.runs-on }}
|
runs-on: ${{ inputs.runs-on }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{ github.ref }}
|
ref: ${{ github.ref }}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ jobs:
|
|||||||
- check
|
- check
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
version:
|
version:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Get release version string
|
- name: Get release version string
|
||||||
run: |
|
run: |
|
||||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||||
@@ -81,7 +81,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
|
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Build base
|
- name: Build base
|
||||||
id: base
|
id: base
|
||||||
uses: ./.github/actions/setup-base
|
uses: ./.github/actions/setup-base
|
||||||
@@ -163,7 +163,7 @@ jobs:
|
|||||||
needs: [version, build]
|
needs: [version, build]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
@@ -242,7 +242,7 @@ jobs:
|
|||||||
- package-pio-deps-native-tft
|
- package-pio-deps-native-tft
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
@@ -311,7 +311,7 @@ jobs:
|
|||||||
needs: [release-artifacts, version]
|
needs: [release-artifacts, version]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
@@ -366,7 +366,7 @@ jobs:
|
|||||||
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
|
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
- check
|
- check
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: 3.x
|
python-version: 3.x
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
version:
|
version:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Get release version string
|
- name: Get release version string
|
||||||
run: |
|
run: |
|
||||||
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
- name: Build base
|
- name: Build base
|
||||||
id: base
|
id: base
|
||||||
uses: ./.github/actions/setup-base
|
uses: ./.github/actions/setup-base
|
||||||
@@ -142,7 +142,7 @@ jobs:
|
|||||||
needs: [version, build]
|
needs: [version, build]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
@@ -221,7 +221,7 @@ jobs:
|
|||||||
- package-pio-deps-native-tft
|
- package-pio-deps-native-tft
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
@@ -290,7 +290,7 @@ jobs:
|
|||||||
needs: [release-artifacts, version]
|
needs: [release-artifacts, version]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
@@ -345,7 +345,7 @@ jobs:
|
|||||||
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
|
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Trunk Check
|
- name: Trunk Check
|
||||||
uses: trunk-io/trunk-action@v1
|
uses: trunk-io/trunk-action@v1
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
pull-requests: write # For trunk to create PRs
|
pull-requests: write # For trunk to create PRs
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Trunk Upgrade
|
- name: Trunk Upgrade
|
||||||
uses: trunk-io/trunk-action/upgrade@v1
|
uses: trunk-io/trunk-action/upgrade@v1
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
needs: build-debian-src
|
needs: build-debian-src
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
path: meshtasticd
|
path: meshtasticd
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
needs: build-debian-src
|
needs: build-debian-src
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
path: meshtasticd
|
path: meshtasticd
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
checks: write
|
checks: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,10 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
# Always use master branch for version bumps
|
||||||
|
ref: master
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v6
|
uses: actions/setup-python@v6
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# step 1
|
# step 1
|
||||||
- name: clone application source code
|
- name: clone application source code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
# step 2
|
# step 2
|
||||||
- name: full scan
|
- name: full scan
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# step 1
|
# step 1
|
||||||
- name: clone application source code
|
- name: clone application source code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ jobs:
|
|||||||
name: Native Simulator Tests
|
name: Native Simulator Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
@@ -70,7 +70,7 @@ jobs:
|
|||||||
name: Native PlatformIO Tests
|
name: Native PlatformIO Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
@@ -127,7 +127,7 @@ jobs:
|
|||||||
- platformio-tests
|
- platformio-tests
|
||||||
if: always()
|
if: always()
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
@@ -143,7 +143,7 @@ jobs:
|
|||||||
merge-multiple: true
|
merge-multiple: true
|
||||||
|
|
||||||
- name: Test Report
|
- name: Test Report
|
||||||
uses: dorny/test-reporter@v2.1.1
|
uses: dorny/test-reporter@v2.2.0
|
||||||
with:
|
with:
|
||||||
name: PlatformIO Tests
|
name: PlatformIO Tests
|
||||||
path: testreport.xml
|
path: testreport.xml
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ jobs:
|
|||||||
runs-on: test-runner
|
runs-on: test-runner
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
# - uses: actions/setup-python@v5
|
# - uses: actions/setup-python@v5
|
||||||
# with:
|
# with:
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Trunk Check
|
- name: Trunk Check
|
||||||
uses: trunk-io/trunk-action@v1
|
uses: trunk-io/trunk-action@v1
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Trunk Check
|
- name: Trunk Check
|
||||||
uses: trunk-io/trunk-action@v1
|
uses: trunk-io/trunk-action@v1
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{github.event.pull_request.head.ref}}
|
ref: ${{github.event.pull_request.head.ref}}
|
||||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v5
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -8,15 +8,15 @@ plugins:
|
|||||||
uri: https://github.com/trunk-io/plugins
|
uri: https://github.com/trunk-io/plugins
|
||||||
lint:
|
lint:
|
||||||
enabled:
|
enabled:
|
||||||
- checkov@3.2.486
|
- checkov@3.2.492
|
||||||
- renovate@41.157.0
|
- renovate@42.5.4
|
||||||
- prettier@3.6.2
|
- prettier@3.6.2
|
||||||
- trufflehog@3.90.11
|
- trufflehog@3.90.13
|
||||||
- yamllint@1.37.1
|
- yamllint@1.37.1
|
||||||
- bandit@1.8.6
|
- bandit@1.8.6
|
||||||
- trivy@0.67.2
|
- trivy@0.67.2
|
||||||
- taplo@0.10.0
|
- taplo@0.10.0
|
||||||
- ruff@0.14.1
|
- ruff@0.14.4
|
||||||
- isort@7.0.0
|
- isort@7.0.0
|
||||||
- markdownlint@0.45.0
|
- markdownlint@0.45.0
|
||||||
- oxipng@9.1.5
|
- oxipng@9.1.5
|
||||||
@@ -26,9 +26,9 @@ lint:
|
|||||||
- hadolint@2.14.0
|
- hadolint@2.14.0
|
||||||
- shfmt@3.6.0
|
- shfmt@3.6.0
|
||||||
- shellcheck@0.11.0
|
- shellcheck@0.11.0
|
||||||
- black@25.9.0
|
- black@25.11.0
|
||||||
- git-diff-check
|
- git-diff-check
|
||||||
- gitleaks@8.28.0
|
- gitleaks@8.29.0
|
||||||
- clang-format@16.0.3
|
- clang-format@16.0.3
|
||||||
ignore:
|
ignore:
|
||||||
- linters: [ALL]
|
- linters: [ALL]
|
||||||
|
|||||||
@@ -37,4 +37,3 @@ Join our community and help improve Meshtastic! 🚀
|
|||||||
## Stats
|
## Stats
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -8,7 +8,8 @@ ARG PIO_ENV=native
|
|||||||
ENV PIP_ROOT_USER_ACTION=ignore
|
ENV PIP_ROOT_USER_ACTION=ignore
|
||||||
|
|
||||||
RUN apk --no-cache add \
|
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 \
|
libusb-dev i2c-tools-dev libuv-dev openssl-dev pkgconf argp-standalone \
|
||||||
libx11-dev libinput-dev libxkbcommon-dev \
|
libx11-dev libinput-dev libxkbcommon-dev \
|
||||||
&& rm -rf /var/cache/apk/* \
|
&& rm -rf /var/cache/apk/* \
|
||||||
@@ -40,8 +41,8 @@ LABEL org.opencontainers.image.title="Meshtastic" \
|
|||||||
USER root
|
USER root
|
||||||
|
|
||||||
RUN apk --no-cache add \
|
RUN apk --no-cache add \
|
||||||
shadow libstdc++ libgpiod yaml-cpp libusb i2c-tools libuv \
|
shadow libstdc++ libbsd libgpiod yaml-cpp libusb \
|
||||||
libx11 libinput libxkbcommon \
|
i2c-tools libuv libx11 libinput libxkbcommon \
|
||||||
&& rm -rf /var/cache/apk/* \
|
&& rm -rf /var/cache/apk/* \
|
||||||
&& mkdir -p /var/lib/meshtasticd \
|
&& mkdir -p /var/lib/meshtasticd \
|
||||||
&& mkdir -p /etc/meshtasticd/config.d \
|
&& mkdir -p /etc/meshtasticd/config.d \
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ extends = arduino_base
|
|||||||
platform_packages =
|
platform_packages =
|
||||||
; our custom Git version until they merge our PR
|
; our custom Git version until they merge our PR
|
||||||
# TODO renovate
|
# 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
|
; Don't renovate toolchain-gccarmnoneeabi
|
||||||
platformio/toolchain-gccarmnoneeabi@~1.90301.0
|
platformio/toolchain-gccarmnoneeabi@~1.90301.0
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ lib_deps =
|
|||||||
${environmental_base.lib_deps}
|
${environmental_base.lib_deps}
|
||||||
${environmental_extra.lib_deps}
|
${environmental_extra.lib_deps}
|
||||||
# renovate: datasource=git-refs depName=Kongduino-Adafruit_nRFCrypto packageName=https://github.com/Kongduino/Adafruit_nRFCrypto gitBranch=master
|
# 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.
|
; Common NRF52 debugging settings follow. See the Meshtastic developer docs for how to connect SWD debugging probes to your board.
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
[portduino_base]
|
[portduino_base]
|
||||||
platform =
|
platform =
|
||||||
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
|
# renovate: datasource=git-refs depName=platform-native packageName=https://github.com/meshtastic/platform-native gitBranch=develop
|
||||||
https://github.com/meshtastic/platform-native/archive/d3f6e339534233c7217818867368767590ce549e.zip
|
https://github.com/meshtastic/platform-native/archive/f566d364204416cdbf298e349213f7d551f793d9.zip
|
||||||
framework = arduino
|
framework = arduino
|
||||||
|
|
||||||
build_src_filter =
|
build_src_filter =
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ build_flags =
|
|||||||
-DRADIOLIB_EXCLUDE_LR11X0=1
|
-DRADIOLIB_EXCLUDE_LR11X0=1
|
||||||
-DHAL_DAC_MODULE_ONLY
|
-DHAL_DAC_MODULE_ONLY
|
||||||
-DHAL_RNG_MODULE_ENABLED
|
-DHAL_RNG_MODULE_ENABLED
|
||||||
|
-Wl,--wrap=__assert_func
|
||||||
|
-Wl,--wrap=strerror
|
||||||
|
-Wl,--wrap=_tzset_unlocked_r
|
||||||
|
|
||||||
build_src_filter =
|
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>
|
${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>
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ SET "LOGCOUNTER=0"
|
|||||||
SET "BPS_RESET=0"
|
SET "BPS_RESET=0"
|
||||||
|
|
||||||
@REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable.
|
@REM FIXME: Determine mcu from PlatformIO variant, this is unmaintainable.
|
||||||
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone t-eth-elite tlora-pager mesh-tab dreamcatcher ESP32-S3-Pico seeed-sensecap-indicator heltec_capsule_sensor_v3 vision-master icarus tracksenger elecrow-adv"
|
SET "S3=s3 v3 t-deck wireless-paper wireless-tracker station-g2 unphone t-eth-elite tlora-pager mesh-tab dreamcatcher ESP32-S3-Pico seeed-sensecap-indicator heltec_capsule_sensor_v3 vision-master icarus tracksenger elecrow-adv heltec-v4"
|
||||||
SET "C3=esp32c3"
|
SET "C3=esp32c3"
|
||||||
@REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable.
|
@REM FIXME: Determine flash size from PlatformIO variant, this is unmaintainable.
|
||||||
SET "BIGDB_8MB=crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger"
|
SET "BIGDB_8MB=crowpanel-esp32s3 heltec_capsule_sensor_v3 heltec-v3 heltec-vision-master-e213 heltec-vision-master-e290 heltec-vision-master-t190 heltec-wireless-paper heltec-wireless-tracker heltec-wsl-v3 icarus seeed-xiao-s3 tbeam-s3-core tracksenger"
|
||||||
SET "MUIDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator"
|
SET "MUIDB_8MB=picomputer-s3 unphone seeed-sensecap-indicator"
|
||||||
SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite tlora-pager t-watch-s3 elecrow-adv"
|
SET "BIGDB_16MB=t-deck mesh-tab t-energy-s3 dreamcatcher ESP32-S3-Pico m5stack-cores3 station-g2 t-eth-elite tlora-pager t-watch-s3 elecrow-adv heltec-v4"
|
||||||
|
|
||||||
GOTO getopts
|
GOTO getopts
|
||||||
:help
|
:help
|
||||||
|
|||||||
@@ -87,6 +87,15 @@
|
|||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
<releases>
|
<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">
|
<release version="2.7.13" date="2025-10-11">
|
||||||
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.13</url>
|
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.13</url>
|
||||||
</release>
|
</release>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
}
|
||||||
Vendored
+19
@@ -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
|
meshtasticd (2.7.13.0) unstable; urgency=medium
|
||||||
|
|
||||||
* Version 2.7.13
|
* Version 2.7.13
|
||||||
|
|||||||
Vendored
+1
@@ -3,6 +3,7 @@ Section: misc
|
|||||||
Priority: optional
|
Priority: optional
|
||||||
Maintainer: Austin Lane <vidplace7@gmail.com>
|
Maintainer: Austin Lane <vidplace7@gmail.com>
|
||||||
Build-Depends: debhelper-compat (= 13),
|
Build-Depends: debhelper-compat (= 13),
|
||||||
|
libc6-dev (>= 2.38) | libbsd-dev,
|
||||||
lsb-release,
|
lsb-release,
|
||||||
tar,
|
tar,
|
||||||
gzip,
|
gzip,
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ BuildRequires: python3dist(grpcio[protobuf])
|
|||||||
BuildRequires: python3dist(grpcio-tools)
|
BuildRequires: python3dist(grpcio-tools)
|
||||||
BuildRequires: git-core
|
BuildRequires: git-core
|
||||||
BuildRequires: gcc-c++
|
BuildRequires: gcc-c++
|
||||||
|
BuildRequires: (glibc-devel >= 2.38) or pkgconfig(libbsd-overlay)
|
||||||
BuildRequires: pkgconfig(yaml-cpp)
|
BuildRequires: pkgconfig(yaml-cpp)
|
||||||
BuildRequires: pkgconfig(libgpiod)
|
BuildRequires: pkgconfig(libgpiod)
|
||||||
BuildRequires: pkgconfig(bluez)
|
BuildRequires: pkgconfig(bluez)
|
||||||
@@ -49,6 +50,13 @@ BuildRequires: pkgconfig(x11)
|
|||||||
BuildRequires: pkgconfig(libinput)
|
BuildRequires: pkgconfig(libinput)
|
||||||
BuildRequires: pkgconfig(xkbcommon-x11)
|
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
|
Requires: systemd-udev
|
||||||
|
|
||||||
%description
|
%description
|
||||||
|
|||||||
+5
-4
@@ -62,7 +62,7 @@ monitor_speed = 115200
|
|||||||
monitor_filters = direct
|
monitor_filters = direct
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
|
||||||
https://github.com/meshtastic/esp8266-oled-ssd1306/archive/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
|
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
|
||||||
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
|
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
|
||||||
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
|
||||||
@@ -115,12 +115,13 @@ lib_deps =
|
|||||||
[radiolib_base]
|
[radiolib_base]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
# renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib
|
||||||
jgromes/RadioLib@7.3.0
|
# jgromes/RadioLib@7.4.0
|
||||||
|
https://github.com/jgromes/RadioLib/archive/536c7267362e2c1345be7054ba45e503252975ff.zip
|
||||||
|
|
||||||
[device-ui_base]
|
[device-ui_base]
|
||||||
lib_deps =
|
lib_deps =
|
||||||
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
||||||
https://github.com/meshtastic/device-ui/archive/19b7855e9a1d9deff37391659ca7194e4ef57c43.zip
|
https://github.com/meshtastic/device-ui/archive/28167c67dfd13015a0b5eef1828f95fe8e3ab7c3.zip
|
||||||
|
|
||||||
; Common libs for environmental measurements in telemetry module
|
; Common libs for environmental measurements in telemetry module
|
||||||
[environmental_base]
|
[environmental_base]
|
||||||
@@ -176,7 +177,7 @@ lib_deps =
|
|||||||
# renovate: datasource=custom.pio depName=Adafruit LTR390 Library packageName=adafruit/library/Adafruit LTR390 Library
|
# renovate: datasource=custom.pio depName=Adafruit LTR390 Library packageName=adafruit/library/Adafruit LTR390 Library
|
||||||
adafruit/Adafruit LTR390 Library@1.1.2
|
adafruit/Adafruit LTR390 Library@1.1.2
|
||||||
# renovate: datasource=custom.pio depName=Adafruit PCT2075 packageName=adafruit/library/Adafruit PCT2075
|
# renovate: datasource=custom.pio depName=Adafruit PCT2075 packageName=adafruit/library/Adafruit PCT2075
|
||||||
adafruit/Adafruit PCT2075@1.0.5
|
adafruit/Adafruit PCT2075@1.0.6
|
||||||
# renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150
|
# renovate: datasource=custom.pio depName=DFRobot_BMM150 packageName=dfrobot/library/DFRobot_BMM150
|
||||||
dfrobot/DFRobot_BMM150@1.0.0
|
dfrobot/DFRobot_BMM150@1.0.0
|
||||||
# renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561
|
# renovate: datasource=custom.pio depName=Adafruit_TSL2561 packageName=adafruit/library/Adafruit TSL2561
|
||||||
|
|||||||
+1
-1
Submodule protobufs updated: bf149bbdcc...52fa252f1e
+6
-6
@@ -53,8 +53,8 @@ class GPSStatus : public Status
|
|||||||
int32_t getLatitude() const
|
int32_t getLatitude() const
|
||||||
{
|
{
|
||||||
if (config.position.fixed_position) {
|
if (config.position.fixed_position) {
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeDetail *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
return node->position.latitude_i;
|
return node ? node->latitude_i : 0;
|
||||||
} else {
|
} else {
|
||||||
return p.latitude_i;
|
return p.latitude_i;
|
||||||
}
|
}
|
||||||
@@ -63,8 +63,8 @@ class GPSStatus : public Status
|
|||||||
int32_t getLongitude() const
|
int32_t getLongitude() const
|
||||||
{
|
{
|
||||||
if (config.position.fixed_position) {
|
if (config.position.fixed_position) {
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeDetail *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
return node->position.longitude_i;
|
return node ? node->longitude_i : 0;
|
||||||
} else {
|
} else {
|
||||||
return p.longitude_i;
|
return p.longitude_i;
|
||||||
}
|
}
|
||||||
@@ -73,8 +73,8 @@ class GPSStatus : public Status
|
|||||||
int32_t getAltitude() const
|
int32_t getAltitude() const
|
||||||
{
|
{
|
||||||
if (config.position.fixed_position) {
|
if (config.position.fixed_position) {
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeDetail *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
return node->position.altitude;
|
return node ? node->altitude : 0;
|
||||||
} else {
|
} else {
|
||||||
return p.altitude;
|
return p.altitude;
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-9
@@ -194,7 +194,7 @@ static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level se
|
|||||||
|
|
||||||
#ifdef BATTERY_PIN
|
#ifdef BATTERY_PIN
|
||||||
|
|
||||||
static void adcEnable()
|
void battery_adcEnable()
|
||||||
{
|
{
|
||||||
#ifdef ADC_CTRL // enable adc voltage divider when we need to read
|
#ifdef ADC_CTRL // enable adc voltage divider when we need to read
|
||||||
#ifdef ADC_USE_PULLUP
|
#ifdef ADC_USE_PULLUP
|
||||||
@@ -214,7 +214,7 @@ static void adcEnable()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static void adcDisable()
|
static void battery_adcDisable()
|
||||||
{
|
{
|
||||||
#ifdef ADC_CTRL // disable adc voltage divider when we need to read
|
#ifdef ADC_CTRL // disable adc voltage divider when we need to read
|
||||||
#ifdef ADC_USE_PULLUP
|
#ifdef ADC_USE_PULLUP
|
||||||
@@ -320,7 +320,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
uint32_t raw = 0;
|
uint32_t raw = 0;
|
||||||
float scaled = 0;
|
float scaled = 0;
|
||||||
|
|
||||||
adcEnable();
|
battery_adcEnable();
|
||||||
#ifdef ARCH_ESP32 // ADC block for espressif platforms
|
#ifdef ARCH_ESP32 // ADC block for espressif platforms
|
||||||
raw = espAdcRead();
|
raw = espAdcRead();
|
||||||
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
|
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
|
||||||
@@ -332,7 +332,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
raw = raw / BATTERY_SENSE_SAMPLES;
|
raw = raw / BATTERY_SENSE_SAMPLES;
|
||||||
scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw;
|
scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw;
|
||||||
#endif
|
#endif
|
||||||
adcDisable();
|
battery_adcDisable();
|
||||||
|
|
||||||
if (!initial_read_done) {
|
if (!initial_read_done) {
|
||||||
// Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
|
// Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
|
||||||
@@ -906,13 +906,8 @@ void Power::readPowerStatus()
|
|||||||
low_voltage_counter++;
|
low_voltage_counter++;
|
||||||
LOG_DEBUG("Low voltage counter: %d/10", low_voltage_counter);
|
LOG_DEBUG("Low voltage counter: %d/10", low_voltage_counter);
|
||||||
if (low_voltage_counter > 10) {
|
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");
|
LOG_INFO("Low voltage detected, trigger deep sleep");
|
||||||
powerFSM.trigger(EVENT_LOW_BATTERY);
|
powerFSM.trigger(EVENT_LOW_BATTERY);
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
low_voltage_counter = 0;
|
low_voltage_counter = 0;
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ void consolePrintf(const char *format, ...)
|
|||||||
|
|
||||||
SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), concurrency::OSThread("SerialConsole")
|
SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), concurrency::OSThread("SerialConsole")
|
||||||
{
|
{
|
||||||
|
api_type = TYPE_SERIAL;
|
||||||
assert(!console);
|
assert(!console);
|
||||||
console = this;
|
console = this;
|
||||||
canWrite = false; // We don't send packets to our port until it has talked to us first
|
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 ICM20948_ADDR_ALT 0x68
|
||||||
#define BHI260AP_ADDR 0x28
|
#define BHI260AP_ADDR 0x28
|
||||||
#define BMM150_ADDR 0x13
|
#define BMM150_ADDR 0x13
|
||||||
|
#define DA217_ADDR 0x26
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// LED
|
// LED
|
||||||
@@ -250,6 +251,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
#define FT6336U_ADDR 0x48
|
#define FT6336U_ADDR 0x48
|
||||||
#define CST328_ADDR 0x1A
|
#define CST328_ADDR 0x1A
|
||||||
|
#define CHSC6X_ADDR 0x2E
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected)
|
// RAK12035VB Soil Monitor (using RAK12023 up to 3 RAK12035 monitors can be connected)
|
||||||
|
|||||||
@@ -83,7 +83,9 @@ class ScanI2C
|
|||||||
BMM150,
|
BMM150,
|
||||||
TSL2561,
|
TSL2561,
|
||||||
DRV2605,
|
DRV2605,
|
||||||
BH1750
|
BH1750,
|
||||||
|
DA217,
|
||||||
|
CHSC6X
|
||||||
} DeviceType;
|
} DeviceType;
|
||||||
|
|
||||||
// typedef uint8_t DeviceAddress;
|
// typedef uint8_t DeviceAddress;
|
||||||
|
|||||||
@@ -465,8 +465,23 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3", (uint8_t)addr.address);
|
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);
|
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:
|
case TSL25911_ADDR:
|
||||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x12), 1);
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x12), 1);
|
||||||
if (registerValue == 0x50) {
|
if (registerValue == 0x50) {
|
||||||
@@ -485,6 +500,7 @@ 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(LTR390UV_ADDR, LTR390UV, "LTR390UV", (uint8_t)addr.address);
|
||||||
SCAN_SIMPLE_CASE(PCT2075_ADDR, PCT2075, "PCT2075", (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(CST328_ADDR, CST328, "CST328", (uint8_t)addr.address);
|
||||||
|
SCAN_SIMPLE_CASE(CHSC6X_ADDR, CHSC6X, "CHSC6X", (uint8_t)addr.address);
|
||||||
case LTR553ALS_ADDR:
|
case LTR553ALS_ADDR:
|
||||||
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x86), 1); // Part ID register
|
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x86), 1); // Part ID register
|
||||||
if (registerValue == 0x92) { // LTR553ALS Part ID
|
if (registerValue == 0x92) { // LTR553ALS Part ID
|
||||||
|
|||||||
+27
-8
@@ -240,6 +240,9 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
|
|||||||
buffer[bytesRead] = b;
|
buffer[bytesRead] = b;
|
||||||
bytesRead++;
|
bytesRead++;
|
||||||
if ((bytesRead == 767) || (b == '\r')) {
|
if ((bytesRead == 767) || (b == '\r')) {
|
||||||
|
#ifdef GPS_DEBUG
|
||||||
|
LOG_DEBUG(debugmsg.c_str());
|
||||||
|
#endif
|
||||||
if (strnstr((char *)buffer, message, bytesRead) != nullptr) {
|
if (strnstr((char *)buffer, message, bytesRead) != nullptr) {
|
||||||
#ifdef GPS_DEBUG
|
#ifdef GPS_DEBUG
|
||||||
LOG_DEBUG("Found: %s", message); // Log the found message
|
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;
|
return GNSS_RESPONSE_OK;
|
||||||
} else {
|
} else {
|
||||||
bytesRead = 0;
|
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));
|
memset(&ublox_info, 0, sizeof(ublox_info));
|
||||||
delay(100);
|
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)
|
// 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");
|
_serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
|
||||||
delay(20);
|
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')) {
|
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
|
// check if we can see our chips
|
||||||
for (const auto &chipInfo : responseMap) {
|
for (const auto &chipInfo : responseMap) {
|
||||||
if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
|
if (strstr(response, chipInfo.detectionString.c_str()) != nullptr) {
|
||||||
|
#ifdef GPS_DEBUG
|
||||||
|
LOG_DEBUG(response);
|
||||||
|
#endif
|
||||||
LOG_INFO("%s detected", chipInfo.chipName.c_str());
|
LOG_INFO("%s detected", chipInfo.chipName.c_str());
|
||||||
delete[] response; // Cleanup before return
|
delete[] response; // Cleanup before return
|
||||||
return chipInfo.driver;
|
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') {
|
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
|
// Reset the response buffer for the next potential message
|
||||||
responseLen = 0;
|
responseLen = 0;
|
||||||
response[0] = '\0';
|
response[0] = '\0';
|
||||||
@@ -1572,8 +1593,6 @@ GPS *GPS::createGps()
|
|||||||
|
|
||||||
#ifdef PIN_GPS_RESET
|
#ifdef PIN_GPS_RESET
|
||||||
pinMode(PIN_GPS_RESET, OUTPUT);
|
pinMode(PIN_GPS_RESET, OUTPUT);
|
||||||
digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms
|
|
||||||
delay(10);
|
|
||||||
digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
|
digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -310,7 +310,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
|
|||||||
#ifdef BUILD_EPOCH
|
#ifdef BUILD_EPOCH
|
||||||
if (tv.tv_sec < BUILD_EPOCH) {
|
if (tv.tv_sec < BUILD_EPOCH) {
|
||||||
if (Throttle::isWithinTimespanMs(lastTimeValidationWarning, TIME_VALIDATION_WARNING_INTERVAL_MS) == false) {
|
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();
|
lastTimeValidationWarning = millis();
|
||||||
}
|
}
|
||||||
return RTCSetResultInvalidTime;
|
return RTCSetResultInvalidTime;
|
||||||
@@ -319,7 +319,7 @@ RTCSetResult perhapsSetRTC(RTCQuality q, struct tm &t)
|
|||||||
// Calculate max allowed time safely to avoid overflow in logging
|
// Calculate max allowed time safely to avoid overflow in logging
|
||||||
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
|
uint64_t maxAllowedTime = (uint64_t)BUILD_EPOCH + FORTY_YEARS;
|
||||||
uint32_t maxAllowedPrintable = (maxAllowedTime > UINT32_MAX) ? UINT32_MAX : (uint32_t)maxAllowedTime;
|
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);
|
(uint32_t)BUILD_EPOCH, maxAllowedPrintable);
|
||||||
lastTimeValidationWarning = millis();
|
lastTimeValidationWarning = millis();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1106,8 +1106,8 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
std::vector<FrameCallback> favoriteFrames;
|
std::vector<FrameCallback> favoriteFrames;
|
||||||
|
|
||||||
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||||
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
|
const meshtastic_NodeDetail *nodeDetail = nodeDB->getMeshNodeByIndex(i);
|
||||||
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
|
if (nodeDetail && nodeDetail->num != nodeDB->getNodeNum() && detailIsFavorite(*nodeDetail)) {
|
||||||
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
|
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1460,10 +1460,10 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
|||||||
forceDisplay(); // Forces screen redraw
|
forceDisplay(); // Forces screen redraw
|
||||||
}
|
}
|
||||||
// === Prepare banner content ===
|
// === Prepare banner content ===
|
||||||
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
|
const meshtastic_NodeDetail *node = nodeDB->getMeshNode(packet->from);
|
||||||
const meshtastic_Channel channel =
|
const meshtastic_Channel channel =
|
||||||
channels.getByIndex(packet->channel ? packet->channel : channels.getPrimaryIndex());
|
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);
|
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#if HAS_SCREEN
|
#if HAS_SCREEN
|
||||||
|
#include "MeshService.h"
|
||||||
#include "RTC.h"
|
#include "RTC.h"
|
||||||
|
#include "draw/NodeListRenderer.h"
|
||||||
#include "graphics/ScreenFonts.h"
|
#include "graphics/ScreenFonts.h"
|
||||||
#include "graphics/SharedUIDisplay.h"
|
#include "graphics/SharedUIDisplay.h"
|
||||||
#include "graphics/draw/UIRenderer.h"
|
#include "graphics/draw/UIRenderer.h"
|
||||||
@@ -398,6 +400,43 @@ const int *getTextPositions(OLEDDisplay *display)
|
|||||||
return textPositions;
|
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)
|
bool isAllowedPunctuation(char c)
|
||||||
{
|
{
|
||||||
const std::string allowed = ".,!?;:-_()[]{}'\"@#$/\\&+=%~^ ";
|
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,
|
void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr = "", bool force_no_invert = false,
|
||||||
bool show_date = 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);
|
const int *getTextPositions(OLEDDisplay *display);
|
||||||
|
|
||||||
bool isAllowedPunctuation(char c);
|
bool isAllowedPunctuation(char c);
|
||||||
|
|||||||
@@ -422,7 +422,57 @@ static LGFX *tft = nullptr;
|
|||||||
|
|
||||||
#elif defined(ST7789_CS)
|
#elif defined(ST7789_CS)
|
||||||
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
|
#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
|
class LGFX : public lgfx::LGFX_Device
|
||||||
{
|
{
|
||||||
lgfx::Panel_ST7789 _panel_instance;
|
lgfx::Panel_ST7789 _panel_instance;
|
||||||
@@ -431,6 +481,8 @@ class LGFX : public lgfx::LGFX_Device
|
|||||||
#if HAS_TOUCHSCREEN
|
#if HAS_TOUCHSCREEN
|
||||||
#if defined(T_WATCH_S3) || defined(ELECROW)
|
#if defined(T_WATCH_S3) || defined(ELECROW)
|
||||||
lgfx::Touch_FT5x06 _touch_instance;
|
lgfx::Touch_FT5x06 _touch_instance;
|
||||||
|
#elif defined(HELTEC_V4_TFT)
|
||||||
|
lgfx::TOUCH_CHSC6X _touch_instance;
|
||||||
#else
|
#else
|
||||||
lgfx::Touch_GT911 _touch_instance;
|
lgfx::Touch_GT911 _touch_instance;
|
||||||
#endif
|
#endif
|
||||||
@@ -465,8 +517,8 @@ class LGFX : public lgfx::LGFX_Device
|
|||||||
auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
|
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_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_rst = ST7789_RESET; // Pin number where RST is connected (-1 = disable)
|
||||||
cfg.pin_busy = -1; // Pin number where BUSY 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
|
// The following setting values are general initial values for each panel, so please comment out any
|
||||||
// unknown items and try them.
|
// unknown items and try them.
|
||||||
|
|||||||
@@ -101,3 +101,23 @@ void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength)
|
|||||||
else
|
else
|
||||||
snprintf(timeStr, maxLength, "unknown age");
|
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
|
* @param maxLength Maximum length of the resulting string buffer
|
||||||
*/
|
*/
|
||||||
void getTimeAgoStr(uint32_t agoSecs, char *timeStr, uint8_t maxLength);
|
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);
|
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
|
||||||
int line = 0;
|
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
|
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
|
||||||
char timeString[16];
|
char timeString[16];
|
||||||
int hour = 0;
|
int hour = 0;
|
||||||
int minute = 0;
|
int minute = 0;
|
||||||
int second = 0;
|
int second = 0;
|
||||||
|
|
||||||
if (rtc_sec > 0) {
|
if (rtc_sec > 0) {
|
||||||
long hms = rtc_sec % SEC_PER_DAY;
|
long hms = rtc_sec % SEC_PER_DAY;
|
||||||
hms = (hms + SEC_PER_DAY) % 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;
|
bool isPM = hour >= 12;
|
||||||
// hour = hour > 12 ? hour - 12 : hour;
|
|
||||||
if (config.display.use_12h_clock) {
|
if (config.display.use_12h_clock) {
|
||||||
hour %= 12;
|
hour %= 12;
|
||||||
if (hour == 0)
|
if (hour == 0) {
|
||||||
hour = 12;
|
hour = 12;
|
||||||
|
}
|
||||||
snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute);
|
snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute);
|
||||||
} else {
|
} else {
|
||||||
snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute);
|
snprintf(timeString, sizeof(timeString), "%02d:%02d", hour, minute);
|
||||||
@@ -229,24 +224,56 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
char secondString[8];
|
char secondString[8];
|
||||||
snprintf(secondString, sizeof(secondString), "%02d", second);
|
snprintf(secondString, sizeof(secondString), "%02d", second);
|
||||||
|
|
||||||
#ifdef T_WATCH_S3
|
static bool scaleInitialized = false;
|
||||||
float scale = 1.5;
|
static float scale = 0.75f;
|
||||||
#elif defined(CHATTER_2)
|
static float segmentWidth = SEGMENT_WIDTH * 0.75f;
|
||||||
float scale = 1.1;
|
static float segmentHeight = SEGMENT_HEIGHT * 0.75f;
|
||||||
#else
|
|
||||||
float scale = 0.75;
|
|
||||||
if (isHighResolution) {
|
|
||||||
scale = 1.5;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
|
if (!scaleInitialized) {
|
||||||
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
|
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
|
// 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];
|
char character = timeString[i];
|
||||||
|
|
||||||
if (character == ':') {
|
if (character == ':') {
|
||||||
@@ -257,19 +284,21 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint16_t hourMinuteTextX = (display->getWidth() / 2) - (timeStringWidth / 2);
|
uint16_t hourMinuteTextX = (display->getWidth() / 2) - (timeStringWidth / 2);
|
||||||
|
|
||||||
uint16_t startingHourMinuteTextX = hourMinuteTextX;
|
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
|
// 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];
|
char character = timeString[i];
|
||||||
|
|
||||||
if (character == ':') {
|
if (character == ':') {
|
||||||
drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale);
|
drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale);
|
||||||
|
|
||||||
hourMinuteTextX += segmentHeight + 6;
|
hourMinuteTextX += segmentHeight + 6;
|
||||||
|
if (scale >= 2.0f) {
|
||||||
|
hourMinuteTextX += (uint16_t)(4.5f * scale);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character - '0', scale);
|
drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character - '0', scale);
|
||||||
|
|
||||||
@@ -279,34 +308,27 @@ void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int1
|
|||||||
hourMinuteTextX += 5;
|
hourMinuteTextX += 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw seconds string
|
// draw seconds string + AM/PM
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
int xOffset = (isHighResolution) ? 0 : -1;
|
int xOffset = (isHighResolution) ? 0 : -1;
|
||||||
if (hour >= 10) {
|
if (hour >= 10) {
|
||||||
xOffset += (isHighResolution) ? 32 : 18;
|
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) {
|
if (config.display.use_12h_clock) {
|
||||||
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - yOffset - 2,
|
display->drawString(startingHourMinuteTextX + xOffset, (display->getHeight() - hourMinuteTextY) - 1, isPM ? "pm" : "am");
|
||||||
isPM ? "pm" : "am");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef USE_EINK
|
#ifndef USE_EINK
|
||||||
xOffset = (isHighResolution) ? 18 : 10;
|
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);
|
secondString);
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
|
|
||||||
void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
|
graphics::drawCommonFooter(display, x, y);
|
||||||
{
|
|
||||||
display->drawFastImage(x, y, 18, 14, bluetoothConnectedIcon);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw an analog clock
|
// Draw an analog clock
|
||||||
@@ -319,11 +341,6 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
|
graphics::drawCommonHeader(display, x, y, titleStr, true, true);
|
||||||
int line = 0;
|
int line = 0;
|
||||||
|
|
||||||
#ifdef T_WATCH_S3
|
|
||||||
if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
|
|
||||||
drawBluetoothConnectedIcon(display, display->getWidth() - 18, display->getHeight() - 14);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// clock face center coordinates
|
// clock face center coordinates
|
||||||
int16_t centerX = display->getWidth() / 2;
|
int16_t centerX = display->getWidth() / 2;
|
||||||
int16_t centerY = display->getHeight() / 2;
|
int16_t centerY = display->getHeight() / 2;
|
||||||
@@ -516,6 +533,7 @@ void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
display->drawLine(centerX, centerY, secondX, secondY);
|
display->drawLine(centerX, centerY, secondX, secondY);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
graphics::drawCommonFooter(display, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ClockRenderer
|
} // namespace ClockRenderer
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int heig
|
|||||||
|
|
||||||
// UI elements for clock displays
|
// UI elements for clock displays
|
||||||
// void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode = true, float scale = 1);
|
// 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
|
} // namespace ClockRenderer
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "../Screen.h"
|
#include "../Screen.h"
|
||||||
#include "DebugRenderer.h"
|
#include "DebugRenderer.h"
|
||||||
#include "FSCommon.h"
|
#include "FSCommon.h"
|
||||||
|
#include "MeshService.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "Throttle.h"
|
#include "Throttle.h"
|
||||||
#include "UIRenderer.h"
|
#include "UIRenderer.h"
|
||||||
@@ -10,6 +11,7 @@
|
|||||||
#include "gps/RTC.h"
|
#include "gps/RTC.h"
|
||||||
#include "graphics/ScreenFonts.h"
|
#include "graphics/ScreenFonts.h"
|
||||||
#include "graphics/SharedUIDisplay.h"
|
#include "graphics/SharedUIDisplay.h"
|
||||||
|
#include "graphics/TimeFormatters.h"
|
||||||
#include "graphics/images.h"
|
#include "graphics/images.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "mesh/Channels.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");
|
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 */
|
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||||
#ifdef SHOW_REDRAWS
|
#ifdef SHOW_REDRAWS
|
||||||
if (heartbeat)
|
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++],
|
display->drawString(starting_position + chUtil_x + chutil_bar_width + extraoffset, getTextPositions(display)[line++],
|
||||||
chUtilPercentage);
|
chUtilPercentage);
|
||||||
#endif
|
#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 textWidth = display->getStringWidth(appversionstr);
|
||||||
int nameX = (SCREEN_WIDTH - textWidth) / 2;
|
int nameX = (SCREEN_WIDTH - textWidth) / 2;
|
||||||
|
|
||||||
display->drawString(nameX, getTextPositions(display)[line], appversionstr);
|
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
|
if (SCREEN_HEIGHT > 64 || (SCREEN_HEIGHT <= 64 && line <= 5)) { // Only show uptime if the screen can show it
|
||||||
line += 1;
|
|
||||||
char uptimeStr[32] = "";
|
char uptimeStr[32] = "";
|
||||||
uint32_t uptime = millis() / 1000;
|
getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr));
|
||||||
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);
|
|
||||||
textWidth = display->getStringWidth(uptimeStr);
|
textWidth = display->getStringWidth(uptimeStr);
|
||||||
nameX = (SCREEN_WIDTH - textWidth) / 2;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ****************************
|
// ****************************
|
||||||
|
|||||||
@@ -119,6 +119,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
|
|||||||
auto changes = SEGMENT_CONFIG;
|
auto changes = SEGMENT_CONFIG;
|
||||||
|
|
||||||
// This is needed as we wait til picking the LoRa region to generate keys for the first time.
|
// 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) {
|
if (!owner.is_licensed) {
|
||||||
bool keygenSuccess = false;
|
bool keygenSuccess = false;
|
||||||
if (config.security.private_key.size == 32) {
|
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);
|
memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
config.lora.tx_enabled = true;
|
config.lora.tx_enabled = true;
|
||||||
initRegion();
|
initRegion();
|
||||||
if (myRegion->dutyCycle < 100) {
|
if (myRegion->dutyCycle < 100) {
|
||||||
@@ -581,11 +583,8 @@ void menuHandler::systemBaseMenu()
|
|||||||
|
|
||||||
optionsArray[options] = "Notifications";
|
optionsArray[options] = "Notifications";
|
||||||
optionsEnumArray[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] = "Display Options";
|
optionsArray[options] = "Display Options";
|
||||||
optionsEnumArray[options++] = ScreenOptions;
|
optionsEnumArray[options++] = ScreenOptions;
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(M5STACK_UNITC6L)
|
#if defined(M5STACK_UNITC6L)
|
||||||
optionsArray[options] = "Bluetooth";
|
optionsArray[options] = "Bluetooth";
|
||||||
@@ -785,17 +784,24 @@ void menuHandler::nodeNameLengthMenu()
|
|||||||
|
|
||||||
void menuHandler::resetNodeDBMenu()
|
void menuHandler::resetNodeDBMenu()
|
||||||
{
|
{
|
||||||
static const char *optionsArray[] = {"Back", "Confirm"};
|
static const char *optionsArray[] = {"Back", "Reset All", "Preserve Favorites"};
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
bannerOptions.message = "Confirm Reset NodeDB";
|
bannerOptions.message = "Confirm Reset NodeDB";
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsCount = 2;
|
bannerOptions.optionsCount = 3;
|
||||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||||
if (selected == 1) {
|
if (selected == 1 || selected == 2) {
|
||||||
disableBluetooth();
|
disableBluetooth();
|
||||||
|
screen->setFrames(Screen::FOCUS_DEFAULT);
|
||||||
|
}
|
||||||
|
if (selected == 1) {
|
||||||
LOG_INFO("Initiate node-db reset");
|
LOG_INFO("Initiate node-db reset");
|
||||||
nodeDB->resetNodes();
|
nodeDB->resetNodes();
|
||||||
rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000);
|
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);
|
screen->showOverlayBanner(bannerOptions);
|
||||||
@@ -931,7 +937,9 @@ void menuHandler::BluetoothToggleMenu()
|
|||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
bannerOptions.optionsCount = 3;
|
bannerOptions.optionsCount = 3;
|
||||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
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};
|
InputEvent event = {.inputEvent = (input_broker_event)170, .kbchar = 170, .touchX = 0, .touchY = 0};
|
||||||
inputBroker->injectInputEvent(&event);
|
inputBroker->injectInputEvent(&event);
|
||||||
}
|
}
|
||||||
@@ -1183,8 +1191,8 @@ void menuHandler::removeFavoriteMenu()
|
|||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
std::string message = "Unfavorite This Node?\n";
|
std::string message = "Unfavorite This Node?\n";
|
||||||
auto node = nodeDB->getMeshNode(graphics::UIRenderer::currentFavoriteNodeNum);
|
auto node = nodeDB->getMeshNode(graphics::UIRenderer::currentFavoriteNodeNum);
|
||||||
if (node && node->has_user) {
|
if (node && detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER)) {
|
||||||
message += sanitizeString(node->user.long_name).substr(0, 15);
|
message += sanitizeString(node->long_name).substr(0, 15);
|
||||||
}
|
}
|
||||||
bannerOptions.message = message.c_str();
|
bannerOptions.message = message.c_str();
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
@@ -1347,7 +1355,7 @@ void menuHandler::screenOptionsMenu()
|
|||||||
optionsEnumArray[options++] = ScreenColor;
|
optionsEnumArray[options++] = ScreenColor;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
optionsArray[options] = "Frame Visiblity Toggle";
|
optionsArray[options] = "Frame Visibility Toggle";
|
||||||
optionsEnumArray[options++] = FrameToggles;
|
optionsEnumArray[options++] = FrameToggles;
|
||||||
|
|
||||||
optionsArray[options] = "Display Units";
|
optionsArray[options] = "Display Units";
|
||||||
@@ -1451,8 +1459,10 @@ void menuHandler::keyVerificationFinalPrompt()
|
|||||||
options.notificationType = graphics::notificationTypeEnum::selection_picker;
|
options.notificationType = graphics::notificationTypeEnum::selection_picker;
|
||||||
options.bannerCallback = [=](int selected) {
|
options.bannerCallback = [=](int selected) {
|
||||||
if (selected == 1) {
|
if (selected == 1) {
|
||||||
auto remoteNodePtr = nodeDB->getMeshNode(keyVerificationModule->getCurrentRemoteNode());
|
meshtastic_NodeDetail *remoteNodePtr = nodeDB->getMeshNode(keyVerificationModule->getCurrentRemoteNode());
|
||||||
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
|
if (remoteNodePtr) {
|
||||||
|
detailSetFlag(*remoteNodePtr, NODEDETAIL_FLAG_IS_KEY_MANUALLY_VERIFIED, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
screen->showOverlayBanner(options);
|
screen->showOverlayBanner(options);
|
||||||
|
|||||||
@@ -213,22 +213,23 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
#else
|
#else
|
||||||
display->drawString(center_text, getTextPositions(display)[2], messageString);
|
display->drawString(center_text, getTextPositions(display)[2], messageString);
|
||||||
#endif
|
#endif
|
||||||
|
graphics::drawCommonFooter(display, x, y);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Header Construction ===
|
// === Header Construction ===
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
|
const meshtastic_NodeDetail *node = nodeDB->getMeshNode(getFrom(&mp));
|
||||||
char headerStr[80];
|
char headerStr[80];
|
||||||
const char *sender = "???";
|
const char *sender = "???";
|
||||||
#if defined(M5STACK_UNITC6L)
|
#if defined(M5STACK_UNITC6L)
|
||||||
if (node && node->has_user)
|
if (node && detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER))
|
||||||
sender = node->user.short_name;
|
sender = node->short_name;
|
||||||
#else
|
#else
|
||||||
if (node && node->has_user) {
|
if (node && detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER)) {
|
||||||
if (SCREEN_WIDTH >= 200 && strlen(node->user.long_name) > 0) {
|
if (SCREEN_WIDTH >= 200 && node->long_name[0] != '\0') {
|
||||||
sender = node->user.long_name;
|
sender = node->long_name;
|
||||||
} else {
|
} else {
|
||||||
sender = node->user.short_name;
|
sender = node->short_name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -423,6 +424,7 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
// Draw header at the end to sort out overlapping elements
|
// Draw header at the end to sort out overlapping elements
|
||||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||||
#endif
|
#endif
|
||||||
|
graphics::drawCommonFooter(display, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth)
|
std::vector<std::string> generateLines(OLEDDisplay *display, const char *headerStr, const char *messageBuf, int textWidth)
|
||||||
|
|||||||
@@ -53,23 +53,29 @@ static int scrollIndex = 0;
|
|||||||
// Utility Functions
|
// 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] = "?";
|
static char nodeName[16] = "?";
|
||||||
if (config.display.use_long_node_name == true) {
|
if (!node) {
|
||||||
if (node->has_user && strlen(node->user.long_name) > 0) {
|
strncpy(nodeName, "?", sizeof(nodeName));
|
||||||
name = node->user.long_name;
|
return nodeName;
|
||||||
} else {
|
}
|
||||||
snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
|
|
||||||
|
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 {
|
} else {
|
||||||
if (node->has_user && strlen(node->user.short_name) > 0) {
|
if (hasUser && node->short_name[0] != '\0') {
|
||||||
name = node->user.short_name;
|
name = node->short_name;
|
||||||
} else {
|
|
||||||
snprintf(nodeName, sizeof(nodeName), "(%04X)", (uint16_t)(node->num & 0xFFFF));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!name) {
|
||||||
|
snprintf(nodeName, sizeof(nodeName), "(%04X)", static_cast<uint16_t>(node->num & 0xFFFF));
|
||||||
|
name = nodeName;
|
||||||
|
}
|
||||||
|
|
||||||
// Use sanitizeString() function and copy directly into nodeName
|
// Use sanitizeString() function and copy directly into nodeName
|
||||||
std::string sanitized_name = sanitizeString(name ? name : "");
|
std::string sanitized_name = sanitizeString(name ? name : "");
|
||||||
@@ -164,7 +170,7 @@ void drawScrollbar(OLEDDisplay *display, int visibleNodeRows, int totalEntries,
|
|||||||
// Entry Renderers
|
// 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);
|
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||||
int timeOffset = (isHighResolution) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7);
|
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->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
display->drawString(x + ((isHighResolution) ? 6 : 3), y, nodeName);
|
display->drawString(x + ((isHighResolution) ? 6 : 3), y, nodeName);
|
||||||
if (node->is_favorite) {
|
if (node && detailIsFavorite(*node)) {
|
||||||
if (isHighResolution) {
|
if (isHighResolution) {
|
||||||
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
||||||
} else {
|
} else {
|
||||||
@@ -204,7 +210,7 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
|||||||
display->drawString(rightEdge - textWidth, y, timeStr);
|
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);
|
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||||
|
|
||||||
@@ -220,7 +226,7 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
|||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
display->drawStringMaxWidth(x + ((isHighResolution) ? 6 : 3), y, nameMaxWidth, nodeName);
|
display->drawStringMaxWidth(x + ((isHighResolution) ? 6 : 3), y, nameMaxWidth, nodeName);
|
||||||
if (node->is_favorite) {
|
if (node && detailIsFavorite(*node)) {
|
||||||
if (isHighResolution) {
|
if (isHighResolution) {
|
||||||
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
||||||
} else {
|
} else {
|
||||||
@@ -243,7 +249,7 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
|||||||
|
|
||||||
// Draw hop count
|
// Draw hop count
|
||||||
char hopStr[6] = "";
|
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);
|
snprintf(hopStr, sizeof(hopStr), "[%d]", node->hops_away);
|
||||||
|
|
||||||
if (hopStr[0] != '\0') {
|
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);
|
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||||
int nameMaxWidth = columnWidth - (isHighResolution ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
|
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);
|
const char *nodeName = getSafeNodeName(display, node);
|
||||||
char distStr[10] = "";
|
char distStr[10] = "";
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeDetail *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
if (nodeDB->hasValidPosition(ourNode) && nodeDB->hasValidPosition(node)) {
|
if (nodeDB->hasValidPosition(ourNode) && nodeDB->hasValidPosition(node)) {
|
||||||
double lat1 = ourNode->position.latitude_i * 1e-7;
|
double lat1 = ourNode->latitude_i * 1e-7;
|
||||||
double lon1 = ourNode->position.longitude_i * 1e-7;
|
double lon1 = ourNode->longitude_i * 1e-7;
|
||||||
double lat2 = node->position.latitude_i * 1e-7;
|
double lat2 = node->latitude_i * 1e-7;
|
||||||
double lon2 = node->position.longitude_i * 1e-7;
|
double lon2 = node->longitude_i * 1e-7;
|
||||||
|
|
||||||
double earthRadiusKm = 6371.0;
|
double earthRadiusKm = 6371.0;
|
||||||
double dLat = (lat2 - lat1) * DEG_TO_RAD;
|
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->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
display->drawStringMaxWidth(x + ((isHighResolution) ? 6 : 3), y, nameMaxWidth, nodeName);
|
display->drawStringMaxWidth(x + ((isHighResolution) ? 6 : 3), y, nameMaxWidth, nodeName);
|
||||||
if (node->is_favorite) {
|
if (node && detailIsFavorite(*node)) {
|
||||||
if (isHighResolution) {
|
if (isHighResolution) {
|
||||||
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
||||||
} else {
|
} 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) {
|
switch (currentMode) {
|
||||||
case MODE_LAST_HEARD:
|
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);
|
bool isLeftCol = (x < SCREEN_WIDTH / 2);
|
||||||
|
|
||||||
@@ -358,7 +364,7 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
|||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
display->drawStringMaxWidth(x + ((isHighResolution) ? 6 : 3), y, nameMaxWidth, nodeName);
|
display->drawStringMaxWidth(x + ((isHighResolution) ? 6 : 3), y, nameMaxWidth, nodeName);
|
||||||
if (node->is_favorite) {
|
if (node && detailIsFavorite(*node)) {
|
||||||
if (isHighResolution) {
|
if (isHighResolution) {
|
||||||
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
||||||
} else {
|
} 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)
|
double userLat, double userLon)
|
||||||
{
|
{
|
||||||
if (!nodeDB->hasValidPosition(node))
|
if (!nodeDB->hasValidPosition(node))
|
||||||
@@ -379,8 +385,8 @@ void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
|||||||
int centerX = x + columnWidth - arrowXOffset;
|
int centerX = x + columnWidth - arrowXOffset;
|
||||||
int centerY = y + FONT_HEIGHT_SMALL / 2;
|
int centerY = y + FONT_HEIGHT_SMALL / 2;
|
||||||
|
|
||||||
double nodeLat = node->position.latitude_i * 1e-7;
|
double nodeLat = node->latitude_i * 1e-7;
|
||||||
double nodeLon = node->position.longitude_i * 1e-7;
|
double nodeLon = node->longitude_i * 1e-7;
|
||||||
float bearing = GeoCoord::bearing(userLat, userLon, nodeLat, nodeLon);
|
float bearing = GeoCoord::bearing(userLat, userLon, nodeLat, nodeLon);
|
||||||
float bearingToNode = RAD_TO_DEG * bearing;
|
float bearingToNode = RAD_TO_DEG * bearing;
|
||||||
float relativeBearing = fmod((bearingToNode - myHeading + 360), 360);
|
float relativeBearing = fmod((bearingToNode - myHeading + 360), 360);
|
||||||
@@ -466,16 +472,17 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
int rowCount = 0;
|
int rowCount = 0;
|
||||||
|
|
||||||
for (int i = startIndex; i < endIndex; ++i) {
|
for (int i = startIndex; i < endIndex; ++i) {
|
||||||
if (locationScreen && !nodeDB->getMeshNodeByIndex(i)->has_position) {
|
meshtastic_NodeDetail *candidate = nodeDB->getMeshNodeByIndex(i);
|
||||||
|
if (locationScreen && candidate && !detailHasFlag(*candidate, NODEDETAIL_FLAG_HAS_POSITION)) {
|
||||||
numskipped++;
|
numskipped++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int xPos = x + (col * columnWidth);
|
int xPos = x + (col * columnWidth);
|
||||||
int yPos = y + yOffset;
|
int yPos = y + yOffset;
|
||||||
renderer(display, nodeDB->getMeshNodeByIndex(i), xPos, yPos, columnWidth);
|
renderer(display, candidate, xPos, yPos, columnWidth);
|
||||||
|
|
||||||
if (extras) {
|
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);
|
lastNodeY = std::max(lastNodeY, yPos + FONT_HEIGHT_SMALL);
|
||||||
@@ -505,6 +512,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
#endif
|
#endif
|
||||||
const int scrollStartY = y + 3;
|
const int scrollStartY = y + 3;
|
||||||
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
|
drawScrollbar(display, visibleNodeRows, totalEntries, scrollIndex, 2, scrollStartY);
|
||||||
|
graphics::drawCommonFooter(display, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================
|
// =============================
|
||||||
@@ -577,9 +585,12 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state,
|
|||||||
{
|
{
|
||||||
float heading = 0;
|
float heading = 0;
|
||||||
bool validHeading = false;
|
bool validHeading = false;
|
||||||
auto ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeDetail *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
double lat = DegD(ourNode->position.latitude_i);
|
if (!ourNode || !nodeDB->hasValidPosition(ourNode)) {
|
||||||
double lon = DegD(ourNode->position.longitude_i);
|
return;
|
||||||
|
}
|
||||||
|
double lat = DegD(ourNode->latitude_i);
|
||||||
|
double lon = DegD(ourNode->longitude_i);
|
||||||
|
|
||||||
#if defined(M5STACK_UNITC6L)
|
#if defined(M5STACK_UNITC6L)
|
||||||
display->clear();
|
display->clear();
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ class Screen;
|
|||||||
namespace NodeListRenderer
|
namespace NodeListRenderer
|
||||||
{
|
{
|
||||||
// Entry renderer function types
|
// Entry renderer function types
|
||||||
typedef void (*EntryRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, int16_t, int);
|
typedef void (*EntryRenderer)(OLEDDisplay *, meshtastic_NodeDetail *, int16_t, int16_t, int);
|
||||||
typedef void (*NodeExtrasRenderer)(OLEDDisplay *, meshtastic_NodeInfoLite *, int16_t, int16_t, int, float, double, double);
|
typedef void (*NodeExtrasRenderer)(OLEDDisplay *, meshtastic_NodeDetail *, int16_t, int16_t, int, float, double, double);
|
||||||
|
|
||||||
// Node list mode enumeration
|
// Node list mode enumeration
|
||||||
enum NodeListMode { MODE_LAST_HEARD = 0, MODE_HOP_SIGNAL = 1, MODE_DISTANCE = 2, MODE_COUNT = 3 };
|
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);
|
double lon = 0);
|
||||||
|
|
||||||
// Entry renderers
|
// 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);
|
||||||
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);
|
||||||
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);
|
||||||
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);
|
||||||
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);
|
||||||
|
|
||||||
// Extras renderers
|
// 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);
|
double userLat, double userLon);
|
||||||
|
|
||||||
// Screen frame functions
|
// Screen frame functions
|
||||||
@@ -51,7 +51,7 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state,
|
|||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
const char *getCurrentModeTitle(int screenWidth);
|
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);
|
void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields);
|
||||||
|
|
||||||
// Bitmap drawing function
|
// Bitmap drawing function
|
||||||
|
|||||||
@@ -300,14 +300,17 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
|
|||||||
int scratchLineNum = 0;
|
int scratchLineNum = 0;
|
||||||
for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) {
|
for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) {
|
||||||
char temp_name[16] = {0};
|
char temp_name[16] = {0};
|
||||||
if (nodeDB->getMeshNodeByIndex(i + 1)->has_user) {
|
meshtastic_NodeDetail *candidate = nodeDB->getMeshNodeByIndex(i + 1);
|
||||||
std::string sanitized = sanitizeString(nodeDB->getMeshNodeByIndex(i + 1)->user.long_name);
|
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);
|
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 {
|
} 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) {
|
if (candidate && i == curSelected) {
|
||||||
selectedNodenum = nodeDB->getMeshNodeByIndex(i + 1)->num;
|
selectedNodenum = candidate->num;
|
||||||
if (isHighResolution) {
|
if (isHighResolution) {
|
||||||
strncpy(scratchLineBuffer[scratchLineNum], "> ", 3);
|
strncpy(scratchLineBuffer[scratchLineNum], "> ", 3);
|
||||||
strncpy(scratchLineBuffer[scratchLineNum] + 2, temp_name, 36);
|
strncpy(scratchLineBuffer[scratchLineNum] + 2, temp_name, 36);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "graphics/Screen.h"
|
#include "graphics/Screen.h"
|
||||||
#include "graphics/ScreenFonts.h"
|
#include "graphics/ScreenFonts.h"
|
||||||
#include "graphics/SharedUIDisplay.h"
|
#include "graphics/SharedUIDisplay.h"
|
||||||
|
#include "graphics/TimeFormatters.h"
|
||||||
#include "graphics/images.h"
|
#include "graphics/images.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "target_specific.h"
|
#include "target_specific.h"
|
||||||
@@ -26,22 +27,22 @@ static uint32_t lastSwitchTime = 0;
|
|||||||
namespace graphics
|
namespace graphics
|
||||||
{
|
{
|
||||||
NodeNum UIRenderer::currentFavoriteNodeNum = 0;
|
NodeNum UIRenderer::currentFavoriteNodeNum = 0;
|
||||||
std::vector<meshtastic_NodeInfoLite *> graphics::UIRenderer::favoritedNodes;
|
std::vector<meshtastic_NodeDetail *> graphics::UIRenderer::favoritedNodes;
|
||||||
|
|
||||||
void graphics::UIRenderer::rebuildFavoritedNodes()
|
void graphics::UIRenderer::rebuildFavoritedNodes()
|
||||||
{
|
{
|
||||||
favoritedNodes.clear();
|
favoritedNodes.clear();
|
||||||
size_t total = nodeDB->getNumMeshNodes();
|
size_t total = nodeDB->getNumMeshNodes();
|
||||||
for (size_t i = 0; i < total; i++) {
|
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())
|
if (!n || n->num == nodeDB->getNodeNum())
|
||||||
continue;
|
continue;
|
||||||
if (n->is_favorite)
|
if (detailIsFavorite(*n))
|
||||||
favoritedNodes.push_back(n);
|
favoritedNodes.push_back(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::sort(favoritedNodes.begin(), favoritedNodes.end(),
|
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
|
#if !MESHTASTIC_EXCLUDE_GPS
|
||||||
@@ -288,8 +289,8 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
|||||||
if (nodeIndex < 0 || nodeIndex >= (int)favoritedNodes.size())
|
if (nodeIndex < 0 || nodeIndex >= (int)favoritedNodes.size())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex];
|
meshtastic_NodeDetail *node = favoritedNodes[nodeIndex];
|
||||||
if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite)
|
if (!node || node->num == nodeDB->getNodeNum() || !detailIsFavorite(*node))
|
||||||
return;
|
return;
|
||||||
display->clear();
|
display->clear();
|
||||||
#if defined(M5STACK_UNITC6L)
|
#if defined(M5STACK_UNITC6L)
|
||||||
@@ -302,7 +303,9 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
|||||||
#endif
|
#endif
|
||||||
currentFavoriteNodeNum = node->num;
|
currentFavoriteNodeNum = node->num;
|
||||||
// === Create the shortName and title string ===
|
// === 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};
|
char titlestr[32] = {0};
|
||||||
snprintf(titlestr, sizeof(titlestr), "Fav: %s", shortName);
|
snprintf(titlestr, sizeof(titlestr), "Fav: %s", shortName);
|
||||||
|
|
||||||
@@ -320,9 +323,9 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
|||||||
std::string usernameStr;
|
std::string usernameStr;
|
||||||
// === 1. Long Name (always try to show first) ===
|
// === 1. Long Name (always try to show first) ===
|
||||||
#if defined(M5STACK_UNITC6L)
|
#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
|
#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
|
#endif
|
||||||
|
|
||||||
if (username) {
|
if (username) {
|
||||||
@@ -348,7 +351,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
|||||||
haveSignal = true;
|
haveSignal = true;
|
||||||
}
|
}
|
||||||
// If hops is valid (>0), show right after signal
|
// 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);
|
size_t len = strlen(signalHopsStr);
|
||||||
// Decide between "1 Hop" and "N Hops"
|
// Decide between "1 Hop" and "N Hops"
|
||||||
if (haveSignal) {
|
if (haveSignal) {
|
||||||
@@ -382,33 +385,23 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
|||||||
#if !defined(M5STACK_UNITC6L)
|
#if !defined(M5STACK_UNITC6L)
|
||||||
// === 4. Uptime (only show if metric is present) ===
|
// === 4. Uptime (only show if metric is present) ===
|
||||||
char uptimeStr[32] = "";
|
char uptimeStr[32] = "";
|
||||||
if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) {
|
if (detailHasFlag(*node, NODEDETAIL_FLAG_HAS_UPTIME)) {
|
||||||
uint32_t uptime = node->device_metrics.uptime_seconds;
|
getUptimeStr(node->uptime_seconds * 1000, " Up", uptimeStr, sizeof(uptimeStr));
|
||||||
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 (uptimeStr[0] && line < 5) {
|
if (uptimeStr[0] && line < 5) {
|
||||||
display->drawString(x, getTextPositions(display)[line++], uptimeStr);
|
display->drawString(x, getTextPositions(display)[line++], uptimeStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// === 5. Distance (only if both nodes have GPS position) ===
|
// === 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
|
char distStr[24] = ""; // Make buffer big enough for any string
|
||||||
bool haveDistance = false;
|
bool haveDistance = false;
|
||||||
|
|
||||||
if (nodeDB->hasValidPosition(ourNode) && nodeDB->hasValidPosition(node)) {
|
if (nodeDB->hasValidPosition(ourNode) && nodeDB->hasValidPosition(node)) {
|
||||||
double lat1 = ourNode->position.latitude_i * 1e-7;
|
double lat1 = ourNode->latitude_i * 1e-7;
|
||||||
double lon1 = ourNode->position.longitude_i * 1e-7;
|
double lon1 = ourNode->longitude_i * 1e-7;
|
||||||
double lat2 = node->position.latitude_i * 1e-7;
|
double lat2 = node->latitude_i * 1e-7;
|
||||||
double lon2 = node->position.longitude_i * 1e-7;
|
double lon2 = node->longitude_i * 1e-7;
|
||||||
double earthRadiusKm = 6371.0;
|
double earthRadiusKm = 6371.0;
|
||||||
double dLat = (lat2 - lat1) * DEG_TO_RAD;
|
double dLat = (lat2 - lat1) * DEG_TO_RAD;
|
||||||
double dLon = (lon2 - lon1) * DEG_TO_RAD;
|
double dLon = (lon2 - lon1) * DEG_TO_RAD;
|
||||||
@@ -466,6 +459,10 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
|||||||
showCompass = true;
|
showCompass = true;
|
||||||
}
|
}
|
||||||
if (showCompass) {
|
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 topY = getTextPositions(display)[1];
|
||||||
const int16_t bottomY = SCREEN_HEIGHT - (FONT_HEIGHT_SMALL - 1);
|
const int16_t bottomY = SCREEN_HEIGHT - (FONT_HEIGHT_SMALL - 1);
|
||||||
const int16_t usableHeight = bottomY - topY - 5;
|
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 compassX = x + SCREEN_WIDTH - compassRadius - 8;
|
||||||
const int16_t compassY = topY + (usableHeight / 2) + ((FONT_HEIGHT_SMALL - 1) / 2) + 2;
|
const int16_t compassY = topY + (usableHeight / 2) + ((FONT_HEIGHT_SMALL - 1) / 2) + 2;
|
||||||
|
|
||||||
const auto &op = ourNode->position;
|
float myHeading =
|
||||||
float myHeading = screen->hasHeading() ? screen->getHeading() * PI / 180
|
screen->hasHeading() ? screen->getHeading() * PI / 180 : screen->estimatedHeading(ourLatDeg, ourLonDeg);
|
||||||
: screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
|
|
||||||
|
|
||||||
const auto &p = node->position;
|
float bearing = GeoCoord::bearing(ourLatDeg, ourLonDeg, nodeLatDeg, nodeLonDeg);
|
||||||
/* 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));
|
|
||||||
if (uiconfig.compass_mode == meshtastic_CompassMode_FREEZE_HEADING) {
|
if (uiconfig.compass_mode == meshtastic_CompassMode_FREEZE_HEADING) {
|
||||||
myHeading = 0;
|
myHeading = 0;
|
||||||
} else {
|
} else {
|
||||||
@@ -529,20 +520,21 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
|||||||
int compassX = x + SCREEN_WIDTH / 2;
|
int compassX = x + SCREEN_WIDTH / 2;
|
||||||
int compassY = yBelowContent + availableHeight / 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;
|
float myHeading = 0;
|
||||||
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
|
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING) {
|
||||||
myHeading = screen->hasHeading() ? screen->getHeading() * PI / 180
|
myHeading =
|
||||||
: screen->estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
|
screen->hasHeading() ? screen->getHeading() * PI / 180 : screen->estimatedHeading(ourLatDeg, ourLonDeg);
|
||||||
}
|
}
|
||||||
graphics::CompassRenderer::drawCompassNorth(display, compassX, compassY, myHeading, compassRadius);
|
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
|
/* unused
|
||||||
float d =
|
float d = GeoCoord::latLongToMeter(nodeLatDeg, nodeLonDeg, ourLatDeg, ourLonDeg);
|
||||||
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)
|
if (uiconfig.compass_mode != meshtastic_CompassMode_FREEZE_HEADING)
|
||||||
bearing -= myHeading;
|
bearing -= myHeading;
|
||||||
graphics::CompassRenderer::drawNodeHeading(display, compassX, compassY, compassRadius * 2, bearing);
|
graphics::CompassRenderer::drawNodeHeading(display, compassX, compassY, compassRadius * 2, bearing);
|
||||||
@@ -552,6 +544,7 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
|||||||
// else show nothing
|
// else show nothing
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
graphics::drawCommonFooter(display, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ****************************
|
// ****************************
|
||||||
@@ -563,7 +556,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
int line = 1;
|
int line = 1;
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeDetail *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
|
|
||||||
// === Header ===
|
// === Header ===
|
||||||
#if defined(M5STACK_UNITC6L)
|
#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");
|
drawNodes(display, x + 1, getTextPositions(display)[line] + 2, nodeStatus, -1, false, "online");
|
||||||
#endif
|
#endif
|
||||||
char uptimeStr[32] = "";
|
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 !defined(M5STACK_UNITC6L)
|
||||||
if (days)
|
getUptimeStr(millis(), "Up", uptimeStr, sizeof(uptimeStr));
|
||||||
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);
|
|
||||||
#endif
|
#endif
|
||||||
display->drawString(SCREEN_WIDTH - display->getStringWidth(uptimeStr), getTextPositions(display)[line++], uptimeStr);
|
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;
|
int yOffset = (isHighResolution) ? 0 : 5;
|
||||||
std::string longNameStr;
|
std::string longNameStr;
|
||||||
|
|
||||||
if (ourNode && ourNode->has_user && strlen(ourNode->user.long_name) > 0) {
|
if (ourNode && detailHasFlag(*ourNode, NODEDETAIL_FLAG_HAS_USER) && ourNode->long_name[0] != '\0') {
|
||||||
longNameStr = sanitizeString(ourNode->user.long_name);
|
longNameStr = sanitizeString(ourNode->long_name);
|
||||||
}
|
}
|
||||||
char shortnameble[35];
|
char shortnameble[35];
|
||||||
snprintf(shortnameble, sizeof(shortnameble), "%s",
|
snprintf(shortnameble, sizeof(shortnameble), "%s",
|
||||||
@@ -771,6 +754,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
|
display->drawString(nameX, getTextPositions(display)[line++], shortnameble);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
graphics::drawCommonFooter(display, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start Functions to write date/time to the screen
|
// Start Functions to write date/time to the screen
|
||||||
@@ -998,8 +982,6 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
|||||||
config.display.heading_bold = false;
|
config.display.heading_bold = false;
|
||||||
|
|
||||||
const char *displayLine = ""; // Initialize to empty string by default
|
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.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
|
||||||
if (config.position.fixed_position) {
|
if (config.position.fixed_position) {
|
||||||
displayLine = "Fixed GPS";
|
displayLine = "Fixed GPS";
|
||||||
@@ -1046,36 +1028,17 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
|||||||
if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) {
|
if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) {
|
||||||
// === Second Row: Last GPS Fix ===
|
// === Second Row: Last GPS Fix ===
|
||||||
if (gpsStatus->getLastFixMillis() > 0) {
|
if (gpsStatus->getLastFixMillis() > 0) {
|
||||||
uint32_t delta = (millis() - gpsStatus->getLastFixMillis()) / 1000; // seconds since last fix
|
uint32_t delta = millis() - gpsStatus->getLastFixMillis();
|
||||||
uint32_t days = delta / 86400;
|
char uptimeStr[32];
|
||||||
uint32_t hours = (delta % 86400) / 3600;
|
|
||||||
uint32_t mins = (delta % 3600) / 60;
|
|
||||||
uint32_t secs = delta % 60;
|
|
||||||
|
|
||||||
char buf[32];
|
|
||||||
#if defined(USE_EINK)
|
#if defined(USE_EINK)
|
||||||
// E-Ink: skip seconds, show only days/hours/mins
|
// E-Ink: skip seconds, show only days/hours/mins
|
||||||
if (days > 0) {
|
getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), false);
|
||||||
snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours);
|
|
||||||
} else if (hours > 0) {
|
|
||||||
snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins);
|
|
||||||
} else {
|
|
||||||
snprintf(buf, sizeof(buf), "Last: %um", mins);
|
|
||||||
}
|
|
||||||
#else
|
#else
|
||||||
// Non E-Ink: include seconds where useful
|
// Non E-Ink: include seconds where useful
|
||||||
if (days > 0) {
|
getUptimeStr(delta, "Last", uptimeStr, sizeof(uptimeStr), true);
|
||||||
snprintf(buf, sizeof(buf), "Last: %ud %uh", days, hours);
|
|
||||||
} else if (hours > 0) {
|
|
||||||
snprintf(buf, sizeof(buf), "Last: %uh %um", hours, mins);
|
|
||||||
} else if (mins > 0) {
|
|
||||||
snprintf(buf, sizeof(buf), "Last: %um %us", mins, secs);
|
|
||||||
} else {
|
|
||||||
snprintf(buf, sizeof(buf), "Last: %us", secs);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
display->drawString(0, getTextPositions(display)[line++], buf);
|
display->drawString(0, getTextPositions(display)[line++], uptimeStr);
|
||||||
} else {
|
} else {
|
||||||
display->drawString(0, getTextPositions(display)[line++], "Last: ?");
|
display->drawString(0, getTextPositions(display)[line++], "Last: ?");
|
||||||
}
|
}
|
||||||
@@ -1183,6 +1146,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#endif // HAS_GPS
|
#endif // HAS_GPS
|
||||||
|
graphics::drawCommonFooter(display, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USERPREFS_OEM_TEXT
|
#ifdef USERPREFS_OEM_TEXT
|
||||||
@@ -1267,7 +1231,13 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
if (totalIcons == 0)
|
if (totalIcons == 0)
|
||||||
return;
|
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 currentPage = currentFrame / iconsPerPage;
|
||||||
const size_t pageStart = currentPage * iconsPerPage;
|
const size_t pageStart = currentPage * iconsPerPage;
|
||||||
const size_t pageEnd = min(pageStart + iconsPerPage, totalIcons);
|
const size_t pageEnd = min(pageStart + iconsPerPage, totalIcons);
|
||||||
@@ -1338,6 +1308,47 @@ void UIRenderer::drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
display->setColor(WHITE);
|
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
|
// Knock the corners off the square
|
||||||
display->setColor(BLACK);
|
display->setColor(BLACK);
|
||||||
display->drawRect(rectX, y - 2, 1, 1);
|
display->drawRect(rectX, y - 2, 1, 1);
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class UIRenderer
|
|||||||
static void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
static void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
|
||||||
static NodeNum currentFavoriteNodeNum;
|
static NodeNum currentFavoriteNodeNum;
|
||||||
static std::vector<meshtastic_NodeInfoLite *> favoritedNodes;
|
static std::vector<meshtastic_NodeDetail *> favoritedNodes;
|
||||||
static void rebuildFavoritedNodes();
|
static void rebuildFavoritedNodes();
|
||||||
|
|
||||||
// OEM screens
|
// OEM screens
|
||||||
|
|||||||
@@ -360,6 +360,10 @@ const uint8_t chirpy_hirez[] = {
|
|||||||
#define chirpy_small_image_height 8
|
#define chirpy_small_image_height 8
|
||||||
const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
|
const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
|
||||||
|
|
||||||
|
#define connection_icon_width 7
|
||||||
|
#define connection_icon_height 5
|
||||||
|
const uint8_t connection_icon[] = {0x36, 0x41, 0x5D, 0x41, 0x36};
|
||||||
|
|
||||||
#ifdef M5STACK_UNITC6L
|
#ifdef M5STACK_UNITC6L
|
||||||
#include "img/icon_small.xbm"
|
#include "img/icon_small.xbm"
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
|
||||||
#include "RTC.h"
|
#include "RTC.h"
|
||||||
|
#include "mesh/NodeDB.h"
|
||||||
|
|
||||||
using namespace NicheGraphics;
|
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
|
// Get the best version of a node's short name available to us
|
||||||
// Parses any non-ascii chars
|
// 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)
|
// 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);
|
assert(node);
|
||||||
|
|
||||||
// Use the true shortname if known, and doesn't contain any unprintable characters (emoji, etc.)
|
// Use the true shortname if known, and doesn't contain any unprintable characters (emoji, etc.)
|
||||||
if (node->has_user) {
|
if (detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER) && node->short_name[0] != '\0') {
|
||||||
std::string parsed = parse(node->user.short_name);
|
std::string parsed = parse(node->short_name);
|
||||||
if (isPrintable(parsed))
|
if (isPrintable(parsed))
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
@@ -640,7 +641,9 @@ uint16_t InkHUD::Applet::getActiveNodeCount()
|
|||||||
|
|
||||||
// For each node in db
|
// For each node in db
|
||||||
for (uint16_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
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
|
// Check if heard recently, and not our own node
|
||||||
if (sinceLastSeen(node) < settings->recentlyActiveSeconds && node->num != nodeDB->getNodeNum())
|
if (sinceLastSeen(node) < settings->recentlyActiveSeconds && node->num != nodeDB->getNodeNum())
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
#include <GFX.h> // GFXRoot drawing lib
|
#include <GFX.h> // GFXRoot drawing lib
|
||||||
|
|
||||||
#include "mesh/MeshTypes.h"
|
#include "mesh/MeshTypes.h"
|
||||||
|
#include "mesh/generated/meshtastic/deviceonly.pb.h"
|
||||||
|
|
||||||
#include "./AppletFont.h"
|
#include "./AppletFont.h"
|
||||||
#include "./Applets/System/Notification/Notification.h" // The notification object, not the applet
|
#include "./Applets/System/Notification/Notification.h" // The notification object, not the applet
|
||||||
@@ -140,7 +141,7 @@ class Applet : public GFX
|
|||||||
uint16_t getActiveNodeCount(); // Duration determined by user, in onscreen menu
|
uint16_t getActiveNodeCount(); // Duration determined by user, in onscreen menu
|
||||||
std::string localizeDistance(uint32_t meters); // Human readable distance, imperial or metric
|
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 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
|
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
|
bool isPrintable(std::string); // Check for characters which the font can't print
|
||||||
|
|
||||||
// Convenient references
|
// Convenient references
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include "./MapApplet.h"
|
#include "./MapApplet.h"
|
||||||
|
|
||||||
|
#include "mesh/NodeDB.h"
|
||||||
|
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
void InkHUD::MapApplet::onRender()
|
void InkHUD::MapApplet::onRender()
|
||||||
@@ -136,9 +138,9 @@ void InkHUD::MapApplet::onRender()
|
|||||||
printAt(vertBarX + (bottomLabelW / 2) + 1, bottomLabelY + (bottomLabelH / 2), vertBottomLabel, CENTER, MIDDLE);
|
printAt(vertBarX + (bottomLabelW / 2) + 1, bottomLabelY + (bottomLabelH / 2), vertBottomLabel, CENTER, MIDDLE);
|
||||||
|
|
||||||
// Draw our node LAST with full white fill + outline
|
// 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)) {
|
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 centerX = X(0.5) + (self.eastMeters * metersToPx);
|
||||||
int16_t centerY = Y(0.5) - (self.northMeters * 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)
|
void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
||||||
{
|
{
|
||||||
// If we have a valid position for our own node, use that as the anchor
|
// 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)) {
|
if (ourNode && nodeDB->hasValidPosition(ourNode)) {
|
||||||
*lat = ourNode->position.latitude_i * 1e-7;
|
*lat = ourNode->latitude_i * 1e-7f;
|
||||||
*lng = ourNode->position.longitude_i * 1e-7;
|
*lng = ourNode->longitude_i * 1e-7f;
|
||||||
} else {
|
} else {
|
||||||
// Find mean lat long coords
|
// Find mean lat long coords
|
||||||
// ============================
|
// ============================
|
||||||
@@ -189,7 +191,7 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
|||||||
|
|
||||||
// For each node in db
|
// For each node in db
|
||||||
for (uint32_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
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
|
// Skip if no position
|
||||||
if (!nodeDB->hasValidPosition(node))
|
if (!nodeDB->hasValidPosition(node))
|
||||||
@@ -200,8 +202,8 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Latitude and Longitude of node, in radians
|
// Latitude and Longitude of node, in radians
|
||||||
float latRad = node->position.latitude_i * (1e-7) * DEG_TO_RAD;
|
float latRad = node->latitude_i * (1e-7f) * DEG_TO_RAD;
|
||||||
float lngRad = node->position.longitude_i * (1e-7) * 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
|
// 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
|
// Exact distance from center is irrelevant, as we're only interested in the vector
|
||||||
@@ -288,7 +290,7 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
|||||||
float westernmost = lngCenter;
|
float westernmost = lngCenter;
|
||||||
|
|
||||||
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
meshtastic_NodeDetail *node = nodeDB->getMeshNodeByIndex(i);
|
||||||
|
|
||||||
// Skip if no position
|
// Skip if no position
|
||||||
if (!nodeDB->hasValidPosition(node))
|
if (!nodeDB->hasValidPosition(node))
|
||||||
@@ -299,12 +301,12 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check for a new top or bottom latitude
|
// 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);
|
northernmost = max(northernmost, latNode);
|
||||||
southernmost = min(southernmost, latNode);
|
southernmost = min(southernmost, latNode);
|
||||||
|
|
||||||
// Longitude is trickier
|
// 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 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
|
float degWestward = abs(fmod(((lngNode - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node
|
||||||
if (degEastward < degWestward)
|
if (degEastward < degWestward)
|
||||||
@@ -366,13 +368,13 @@ InkHUD::MapApplet::Marker InkHUD::MapApplet::calculateMarker(float lat, float ln
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
// Draw a marker on the map for a node, with a shortname label, and backing box
|
// 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
|
// Find x and y position based on node's position in nodeDB
|
||||||
assert(nodeDB->hasValidPosition(node));
|
assert(nodeDB->hasValidPosition(node));
|
||||||
Marker m = calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
|
Marker m = calculateMarker(node->latitude_i * 1e-7f, // Lat, converted from Meshtastic's internal int32 style
|
||||||
node->position.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style
|
node->longitude_i * 1e-7f, // Long, converted from Meshtastic's internal int32 style
|
||||||
node->has_hops_away, // Is the hopsAway number valid
|
detailHasFlag(*node, NODEDETAIL_FLAG_HAS_HOPS_AWAY), // Is the hopsAway number valid
|
||||||
node->hops_away // Hops away
|
node->hops_away // Hops away
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -396,9 +398,9 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
|||||||
uint16_t labelH;
|
uint16_t labelH;
|
||||||
uint8_t markerSize;
|
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 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,
|
// Parse any non-ascii chars in the short name,
|
||||||
// and use last 4 instead if unknown / can't render
|
// and use last 4 instead if unknown / can't render
|
||||||
@@ -476,7 +478,7 @@ bool InkHUD::MapApplet::enoughMarkers()
|
|||||||
{
|
{
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
meshtastic_NodeDetail *node = nodeDB->getMeshNodeByIndex(i);
|
||||||
|
|
||||||
// Count nodes
|
// Count nodes
|
||||||
if (nodeDB->hasValidPosition(node) && shouldDrawNode(node))
|
if (nodeDB->hasValidPosition(node) && shouldDrawNode(node))
|
||||||
@@ -499,7 +501,7 @@ void InkHUD::MapApplet::calculateAllMarkers()
|
|||||||
|
|
||||||
// For each node in db
|
// For each node in db
|
||||||
for (uint32_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
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
|
// Skip if no position
|
||||||
if (!nodeDB->hasValidPosition(node))
|
if (!nodeDB->hasValidPosition(node))
|
||||||
@@ -515,10 +517,9 @@ void InkHUD::MapApplet::calculateAllMarkers()
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Calculate marker and store it
|
// Calculate marker and store it
|
||||||
markers.push_back(
|
markers.push_back(calculateMarker(node->latitude_i * 1e-7f, // Lat, converted from Meshtastic's internal int32 style
|
||||||
calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
|
node->longitude_i * 1e-7f, // Long, converted from Meshtastic's internal int32 style
|
||||||
node->position.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style
|
detailHasFlag(*node, NODEDETAIL_FLAG_HAS_HOPS_AWAY), // Is the hopsAway number valid
|
||||||
node->has_hops_away, // Is the hopsAway number valid
|
|
||||||
node->hops_away // Hops away
|
node->hops_away // Hops away
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ class MapApplet : public Applet
|
|||||||
void onRender() override;
|
void onRender() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual bool shouldDrawNode(meshtastic_NodeInfoLite *node) { return true; } // Allow derived applets to filter the nodes
|
virtual bool shouldDrawNode(meshtastic_NodeDetail *node) { return true; } // Allow derived applets to filter the nodes
|
||||||
virtual void getMapCenter(float *lat, float *lng);
|
virtual void getMapCenter(float *lat, float *lng);
|
||||||
virtual void getMapSize(uint32_t *widthMeters, uint32_t *heightMeters);
|
virtual void getMapSize(uint32_t *widthMeters, uint32_t *heightMeters);
|
||||||
|
|
||||||
bool enoughMarkers(); // Anything to draw?
|
bool enoughMarkers(); // Anything to draw?
|
||||||
void drawLabeledMarker(meshtastic_NodeInfoLite *node); // Highlight a specific marker
|
void drawLabeledMarker(meshtastic_NodeDetail *node); // Highlight a specific marker
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Position and size of a marker to be drawn
|
// Position and size of a marker to be drawn
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#include "RTC.h"
|
#include "RTC.h"
|
||||||
|
|
||||||
#include "GeoCoord.h"
|
#include "GeoCoord.h"
|
||||||
#include "NodeDB.h"
|
#include "mesh/NodeDB.h"
|
||||||
|
|
||||||
#include "./NodeListApplet.h"
|
#include "./NodeListApplet.h"
|
||||||
|
|
||||||
@@ -50,19 +50,19 @@ ProcessMessage InkHUD::NodeListApplet::handleReceived(const meshtastic_MeshPacke
|
|||||||
c.signal = getSignalStrength(mp.rx_snr, mp.rx_rssi);
|
c.signal = getSignalStrength(mp.rx_snr, mp.rx_rssi);
|
||||||
|
|
||||||
// Assemble info: from nodeDB (needed to detect changes)
|
// Assemble info: from nodeDB (needed to detect changes)
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(c.nodeNum);
|
meshtastic_NodeDetail *node = nodeDB->getMeshNode(c.nodeNum);
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeDetail *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
if (node) {
|
if (node) {
|
||||||
if (node->has_hops_away)
|
if (detailHasFlag(*node, NODEDETAIL_FLAG_HAS_HOPS_AWAY))
|
||||||
c.hopsAway = node->hops_away;
|
c.hopsAway = node->hops_away;
|
||||||
|
|
||||||
if (nodeDB->hasValidPosition(node) && nodeDB->hasValidPosition(ourNode)) {
|
if (nodeDB->hasValidPosition(node) && nodeDB->hasValidPosition(ourNode)) {
|
||||||
// Get lat and long as float
|
// Get lat and long as float
|
||||||
// Meshtastic stores these as integers internally
|
// Meshtastic stores these as integers internally
|
||||||
float ourLat = ourNode->position.latitude_i * 1e-7;
|
float ourLat = ourNode->latitude_i * 1e-7f;
|
||||||
float ourLong = ourNode->position.longitude_i * 1e-7;
|
float ourLong = ourNode->longitude_i * 1e-7f;
|
||||||
float theirLat = node->position.latitude_i * 1e-7;
|
float theirLat = node->latitude_i * 1e-7f;
|
||||||
float theirLong = node->position.longitude_i * 1e-7;
|
float theirLong = node->longitude_i * 1e-7f;
|
||||||
|
|
||||||
c.distanceMeters = (int32_t)GeoCoord::latLongToMeter(theirLat, theirLong, ourLat, ourLong);
|
c.distanceMeters = (int32_t)GeoCoord::latLongToMeter(theirLat, theirLong, ourLat, ourLong);
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,7 @@ void InkHUD::NodeListApplet::onRender()
|
|||||||
std::string distance; // handled below;
|
std::string distance; // handled below;
|
||||||
uint8_t &hopsAway = card->hopsAway;
|
uint8_t &hopsAway = card->hopsAway;
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
|
meshtastic_NodeDetail *node = nodeDB->getMeshNode(nodeNum);
|
||||||
|
|
||||||
// Skip deleted nodes
|
// Skip deleted nodes
|
||||||
if (!node) {
|
if (!node) {
|
||||||
@@ -162,8 +162,8 @@ void InkHUD::NodeListApplet::onRender()
|
|||||||
// -- Longname --
|
// -- Longname --
|
||||||
// Parse special chars in long name
|
// Parse special chars in long name
|
||||||
// Use node id if unknown
|
// Use node id if unknown
|
||||||
if (node && node->has_user)
|
if (node && detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER))
|
||||||
longName = parse(node->user.long_name); // Found in nodeDB
|
longName = parse(node->long_name); // Found in nodeDB
|
||||||
else {
|
else {
|
||||||
// Not found in nodeDB, show a hex nodeid instead
|
// Not found in nodeDB, show a hex nodeid instead
|
||||||
longName = hexifyNodeNum(nodeNum);
|
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
|
// 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
|
// This behavior assists manufacturers during mass production, and should not be modified without good reason
|
||||||
if (!settings->tips.safeShutdownSeen) {
|
if (!settings->tips.safeShutdownSeen) {
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeDetail *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
fontTitle = fontMedium;
|
fontTitle = fontMedium;
|
||||||
textLeft = xstr(APP_VERSION_SHORT);
|
textLeft = xstr(APP_VERSION_SHORT);
|
||||||
textRight = parseShortName(ourNode);
|
textRight = ourNode ? parseShortName(ourNode) : "";
|
||||||
textTitle = "Meshtastic";
|
textTitle = "Meshtastic";
|
||||||
} else {
|
} else {
|
||||||
fontTitle = fontSmall;
|
fontTitle = fontSmall;
|
||||||
@@ -146,10 +146,10 @@ void InkHUD::LogoApplet::onShutdown()
|
|||||||
|
|
||||||
// Prepare for the powered-off screen now
|
// Prepare for the powered-off screen now
|
||||||
// We can change these values because the initial "shutting down" screen has already rendered at this point
|
// 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 = "";
|
textLeft = "";
|
||||||
textRight = "";
|
textRight = "";
|
||||||
textTitle = parseShortName(ourNode);
|
textTitle = ourNode ? parseShortName(ourNode) : "";
|
||||||
fontTitle = fontMedium;
|
fontTitle = fontMedium;
|
||||||
|
|
||||||
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete
|
// This is then drawn by InkHUD::Events::onShutdown, with a blocking FULL update, after InkHUD's flash write is complete
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "Router.h"
|
#include "Router.h"
|
||||||
#include "airtime.h"
|
#include "airtime.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
#include "mesh/NodeDB.h"
|
||||||
#include "power.h"
|
#include "power.h"
|
||||||
|
|
||||||
#if !MESHTASTIC_EXCLUDE_GPS
|
#if !MESHTASTIC_EXCLUDE_GPS
|
||||||
@@ -616,7 +617,8 @@ void InkHUD::MenuApplet::populateRecipientPage()
|
|||||||
|
|
||||||
// Count favorites
|
// Count favorites
|
||||||
for (uint32_t i = 0; i < nodeCount; i++) {
|
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++;
|
favoriteCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,10 +626,12 @@ void InkHUD::MenuApplet::populateRecipientPage()
|
|||||||
// Don't want some monstrous list that takes 100 clicks to reach exit
|
// Don't want some monstrous list that takes 100 clicks to reach exit
|
||||||
if (favoriteCount < 20) {
|
if (favoriteCount < 20) {
|
||||||
for (uint32_t i = 0; i < nodeCount; i++) {
|
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
|
// Skip node if not a favorite
|
||||||
if (!node->is_favorite)
|
if (!detailIsFavorite(*node))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
CannedMessages::RecipientItem r;
|
CannedMessages::RecipientItem r;
|
||||||
@@ -637,8 +641,8 @@ void InkHUD::MenuApplet::populateRecipientPage()
|
|||||||
|
|
||||||
// Set a label for the menu item
|
// Set a label for the menu item
|
||||||
r.label = "DM: ";
|
r.label = "DM: ";
|
||||||
if (node->has_user)
|
if (detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER) && node->long_name[0] != '\0')
|
||||||
r.label += parse(node->user.long_name);
|
r.label += parse(node->long_name);
|
||||||
else
|
else
|
||||||
r.label += hexifyNodeNum(node->num); // Unsure if it's possible to favorite a node without NodeInfo?
|
r.label += hexifyNodeNum(node->num); // Unsure if it's possible to favorite a node without NodeInfo?
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "./Notification.h"
|
#include "./Notification.h"
|
||||||
#include "graphics/niche/InkHUD/Persistence.h"
|
#include "graphics/niche/InkHUD/Persistence.h"
|
||||||
|
|
||||||
|
#include "mesh/NodeDB.h"
|
||||||
#include "meshUtils.h"
|
#include "meshUtils.h"
|
||||||
#include "modules/TextMessageModule.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;
|
isBroadcast ? &inkhud->persistence->latestMessage.broadcast : &inkhud->persistence->latestMessage.dm;
|
||||||
|
|
||||||
// Find info about the sender
|
// Find info about the sender
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(message->sender);
|
meshtastic_NodeDetail *node = nodeDB->getMeshNode(message->sender);
|
||||||
|
|
||||||
// Leading tag (channel vs. DM)
|
// Leading tag (channel vs. DM)
|
||||||
text += isBroadcast ? "From:" : "DM: ";
|
text += isBroadcast ? "From:" : "DM: ";
|
||||||
|
|
||||||
// Sender id
|
// Sender id
|
||||||
if (node && node->has_user)
|
if (node && detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER))
|
||||||
text += parseShortName(node);
|
text += parseShortName(node);
|
||||||
else
|
else
|
||||||
text += hexifyNodeNum(message->sender);
|
text += hexifyNodeNum(message->sender);
|
||||||
@@ -226,7 +227,7 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila
|
|||||||
text += isBroadcast ? "Msg from " : "DM from ";
|
text += isBroadcast ? "Msg from " : "DM from ";
|
||||||
|
|
||||||
// Sender id
|
// Sender id
|
||||||
if (node && node->has_user)
|
if (node && detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER))
|
||||||
text += parseShortName(node);
|
text += parseShortName(node);
|
||||||
else
|
else
|
||||||
text += hexifyNodeNum(message->sender);
|
text += hexifyNodeNum(message->sender);
|
||||||
|
|||||||
@@ -69,11 +69,11 @@ void InkHUD::AllMessageApplet::onRender()
|
|||||||
// Sender's id
|
// Sender's id
|
||||||
// - short name and long name, if available, or
|
// - short name and long name, if available, or
|
||||||
// - node id
|
// - node id
|
||||||
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(message->sender);
|
meshtastic_NodeDetail *sender = nodeDB->getMeshNode(message->sender);
|
||||||
if (sender && sender->has_user) {
|
if (sender && detailHasFlag(*sender, NODEDETAIL_FLAG_HAS_USER)) {
|
||||||
header += parseShortName(sender); // May be last-four of node if unprintable (emoji, etc)
|
header += parseShortName(sender); // May be last-four of node if unprintable (emoji, etc)
|
||||||
header += " (";
|
header += " (";
|
||||||
header += parse(sender->user.long_name);
|
header += parse(sender->long_name);
|
||||||
header += ")";
|
header += ")";
|
||||||
} else
|
} else
|
||||||
header += hexifyNodeNum(message->sender);
|
header += hexifyNodeNum(message->sender);
|
||||||
|
|||||||
@@ -65,11 +65,11 @@ void InkHUD::DMApplet::onRender()
|
|||||||
// Sender's id
|
// Sender's id
|
||||||
// - shortname and long name, if available, or
|
// - shortname and long name, if available, or
|
||||||
// - node id
|
// - node id
|
||||||
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(latestMessage->dm.sender);
|
meshtastic_NodeDetail *sender = nodeDB->getMeshNode(latestMessage->dm.sender);
|
||||||
if (sender && sender->has_user) {
|
if (sender && detailHasFlag(*sender, NODEDETAIL_FLAG_HAS_USER)) {
|
||||||
header += parseShortName(sender); // May be last-four of node if unprintable (emoji, etc)
|
header += parseShortName(sender); // May be last-four of node if unprintable (emoji, etc)
|
||||||
header += " (";
|
header += " (";
|
||||||
header += parse(sender->user.long_name);
|
header += parse(sender->long_name);
|
||||||
header += ")";
|
header += ")";
|
||||||
} else
|
} else
|
||||||
header += hexifyNodeNum(latestMessage->dm.sender);
|
header += hexifyNodeNum(latestMessage->dm.sender);
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
#include "./HeardApplet.h"
|
#include "./HeardApplet.h"
|
||||||
|
|
||||||
|
#include "mesh/NodeDB.h"
|
||||||
|
|
||||||
using namespace NicheGraphics;
|
using namespace NicheGraphics;
|
||||||
|
|
||||||
void InkHUD::HeardApplet::onActivate()
|
void InkHUD::HeardApplet::onActivate()
|
||||||
@@ -61,7 +63,7 @@ void InkHUD::HeardApplet::handleParsed(CardInfo c)
|
|||||||
void InkHUD::HeardApplet::populateFromNodeDB()
|
void InkHUD::HeardApplet::populateFromNodeDB()
|
||||||
{
|
{
|
||||||
// Fill a collection with pointers to each node in db
|
// Fill a collection with pointers to each node in db
|
||||||
std::vector<meshtastic_NodeInfoLite *> ordered;
|
std::vector<meshtastic_NodeDetail *> ordered;
|
||||||
for (auto mn = nodeDB->meshNodes->begin(); mn != nodeDB->meshNodes->end(); ++mn) {
|
for (auto mn = nodeDB->meshNodes->begin(); mn != nodeDB->meshNodes->end(); ++mn) {
|
||||||
// Only copy if valid, and not our own node
|
// Only copy if valid, and not our own node
|
||||||
if (mn->num != 0 && mn->num != nodeDB->getNodeNum())
|
if (mn->num != 0 && mn->num != nodeDB->getNodeNum())
|
||||||
@@ -69,7 +71,7 @@ void InkHUD::HeardApplet::populateFromNodeDB()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sort the collection by age
|
// 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);
|
return (top->last_heard > bottom->last_heard);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -79,21 +81,21 @@ void InkHUD::HeardApplet::populateFromNodeDB()
|
|||||||
ordered.resize(maxCards());
|
ordered.resize(maxCards());
|
||||||
|
|
||||||
// Create card info for these (stale) node observations
|
// Create card info for these (stale) node observations
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeDetail *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
for (meshtastic_NodeInfoLite *node : ordered) {
|
for (meshtastic_NodeDetail *node : ordered) {
|
||||||
CardInfo c;
|
CardInfo c;
|
||||||
c.nodeNum = node->num;
|
c.nodeNum = node->num;
|
||||||
|
|
||||||
if (node->has_hops_away)
|
if (detailHasFlag(*node, NODEDETAIL_FLAG_HAS_HOPS_AWAY))
|
||||||
c.hopsAway = node->hops_away;
|
c.hopsAway = node->hops_away;
|
||||||
|
|
||||||
if (nodeDB->hasValidPosition(node) && nodeDB->hasValidPosition(ourNode)) {
|
if (nodeDB->hasValidPosition(node) && nodeDB->hasValidPosition(ourNode)) {
|
||||||
// Get lat and long as float
|
// Get lat and long as float
|
||||||
// Meshtastic stores these as integers internally
|
// Meshtastic stores these as integers internally
|
||||||
float ourLat = ourNode->position.latitude_i * 1e-7;
|
float ourLat = ourNode->latitude_i * 1e-7f;
|
||||||
float ourLong = ourNode->position.longitude_i * 1e-7;
|
float ourLong = ourNode->longitude_i * 1e-7f;
|
||||||
float theirLat = node->position.latitude_i * 1e-7;
|
float theirLat = node->latitude_i * 1e-7f;
|
||||||
float theirLong = node->position.longitude_i * 1e-7;
|
float theirLong = node->longitude_i * 1e-7f;
|
||||||
|
|
||||||
c.distanceMeters = (int32_t)GeoCoord::latLongToMeter(theirLat, theirLong, ourLat, ourLong);
|
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 we got a position packet from them
|
||||||
// We might be rendering because our own position updated
|
// We might be rendering because our own position updated
|
||||||
// Either way, we still highlight which node most recently sent us a position packet
|
// 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())
|
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
|
// 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";
|
info += "Me";
|
||||||
else {
|
else {
|
||||||
// Check if sender is node db
|
// Check if sender is node db
|
||||||
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(m.sender);
|
meshtastic_NodeDetail *sender = nodeDB->getMeshNode(m.sender);
|
||||||
if (sender)
|
if (sender && detailHasFlag(*sender, NODEDETAIL_FLAG_HAS_USER))
|
||||||
info += parseShortName(sender); // Handle any unprintable chars in short name
|
info += parseShortName(sender); // Handle any unprintable chars in short name
|
||||||
else
|
else
|
||||||
info += hexifyNodeNum(m.sender); // No node info at all. Print the node num
|
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.
|
For an example, see the `heltec-vision-master-e290-inkhud` platformio env.
|
||||||
|
|
||||||
- platformio.ini
|
- platformio.ini
|
||||||
|
|
||||||
- suppress default Meshtastic components (Screen, ButtonThread, etc)
|
- suppress default Meshtastic components (Screen, ButtonThread, etc)
|
||||||
- define `MESHTASTIC_INCLUDE_NICHE_GRAPHICS`
|
- define `MESHTASTIC_INCLUDE_NICHE_GRAPHICS`
|
||||||
- (possibly) Edit `build_src_filter` to include our new nicheGraphics.h file
|
- (possibly) Edit `build_src_filter` to include our new nicheGraphics.h file
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "InputBroker.h"
|
#include "InputBroker.h"
|
||||||
#include "PowerFSM.h" // needed for event trigger
|
#include "PowerFSM.h" // needed for event trigger
|
||||||
|
#include "configuration.h"
|
||||||
|
#include "modules/ExternalNotificationModule.h"
|
||||||
|
|
||||||
InputBroker *inputBroker = nullptr;
|
InputBroker *inputBroker = nullptr;
|
||||||
|
|
||||||
@@ -48,6 +50,12 @@ void InputBroker::processInputEventQueue()
|
|||||||
int InputBroker::handleInputEvent(const InputEvent *event)
|
int InputBroker::handleInputEvent(const InputEvent *event)
|
||||||
{
|
{
|
||||||
powerFSM.trigger(EVENT_INPUT); // todo: not every input should wake, like long hold release
|
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 && externalNotificationModule->nagging()) {
|
||||||
|
externalNotificationModule->stopNow();
|
||||||
|
}
|
||||||
|
|
||||||
this->notifyObservers(event);
|
this->notifyObservers(event);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ static unsigned char TDeckProTapMap[_TCA8418_NUM_KEYS][5] = {
|
|||||||
{0x00, 0x00, 0x00},
|
{0x00, 0x00, 0x00},
|
||||||
{0x00, 0x00, 0x00},
|
{0x00, 0x00, 0x00},
|
||||||
{0x20, 0x00, 0x00},
|
{0x20, 0x00, 0x00},
|
||||||
{0x00, 0x00, 0x00},
|
{0x00, 0x00, '0'},
|
||||||
{0x00, 0x00, 0x00} // R_Shift, sym, space, mic, L_Shift
|
{0x00, 0x00, 0x00} // R_Shift, sym, space, mic, L_Shift
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -477,6 +477,10 @@ void setup()
|
|||||||
#ifdef RESET_OLED
|
#ifdef RESET_OLED
|
||||||
pinMode(RESET_OLED, OUTPUT);
|
pinMode(RESET_OLED, OUTPUT);
|
||||||
digitalWrite(RESET_OLED, 1);
|
digitalWrite(RESET_OLED, 1);
|
||||||
|
delay(2);
|
||||||
|
digitalWrite(RESET_OLED, 0);
|
||||||
|
delay(10);
|
||||||
|
digitalWrite(RESET_OLED, 1);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef SENSOR_POWER_CTRL_PIN
|
#ifdef SENSOR_POWER_CTRL_PIN
|
||||||
@@ -959,6 +963,7 @@ void setup()
|
|||||||
i2cScanner.reset();
|
i2cScanner.reset();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
||||||
// warn the user about a low entropy key
|
// warn the user about a low entropy key
|
||||||
if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) {
|
if (nodeDB->keyIsLowEntropy && !nodeDB->hasWarned) {
|
||||||
LOG_WARN(LOW_ENTROPY_WARNING);
|
LOG_WARN(LOW_ENTROPY_WARNING);
|
||||||
@@ -969,6 +974,7 @@ void setup()
|
|||||||
service->sendClientNotification(cn);
|
service->sendClientNotification(cn);
|
||||||
nodeDB->hasWarned = true;
|
nodeDB->hasWarned = true;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// buttons are now inputBroker, so have to come after setupModules
|
// buttons are now inputBroker, so have to come after setupModules
|
||||||
#if HAS_BUTTON
|
#if HAS_BUTTON
|
||||||
@@ -1401,7 +1407,7 @@ void setup()
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
// check if the radio chip matches the selected region
|
// 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");
|
LOG_WARN("LoRa chip does not support 2.4GHz. Revert to unset");
|
||||||
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
|
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
|
||||||
nodeDB->saveToDisk(SEGMENT_CONFIG);
|
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);
|
LOG_DEBUG("Node %d or their public_key not found", toNode);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!crypto->setDHPublicKey(remotePublic.bytes)) {
|
if (!setDHPublicKey(remotePublic.bytes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
crypto->hash(shared_key, 32);
|
hash(shared_key, 32);
|
||||||
initNonce(fromNode, packetNum, extraNonceTmp);
|
initNonce(fromNode, packetNum, extraNonceTmp);
|
||||||
|
|
||||||
// Calculate the shared secret with the destination node and encrypt
|
// 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
|
// Calculate the shared secret with the sending node and decrypt
|
||||||
if (!crypto->setDHPublicKey(remotePublic.bytes)) {
|
if (!setDHPublicKey(remotePublic.bytes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
crypto->hash(shared_key, 32);
|
hash(shared_key, 32);
|
||||||
|
|
||||||
initNonce(fromNode, packetNum, extraNonce);
|
initNonce(fromNode, packetNum, extraNonce);
|
||||||
printBytes("Attempt decrypt with nonce: ", nonce, 13);
|
printBytes("Attempt decrypt with nonce: ", nonce, 13);
|
||||||
|
|||||||
@@ -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.
|
// We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly.
|
||||||
int err =
|
int err =
|
||||||
lora.startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, MESHTASTIC_RADIOLIB_IRQ_RX_FLAGS, RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
|
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);
|
assert(err == RADIOLIB_ERR_NONE);
|
||||||
|
|
||||||
RadioLibInterface::startReceive();
|
RadioLibInterface::startReceive();
|
||||||
|
|||||||
+18
-14
@@ -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
|
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;
|
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 &&
|
if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||||
mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) {
|
mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) {
|
||||||
LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo");
|
LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo");
|
||||||
// ignore our request for its NodeInfo
|
// ignore our request for its NodeInfo
|
||||||
} else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user &&
|
} else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && fromNode &&
|
||||||
nodeInfoModule && !isPreferredRebroadcaster && !nodeDB->isFull()) {
|
!detailHasFlag(*fromNode, NODEDETAIL_FLAG_HAS_USER) && nodeInfoModule && !isPreferredRebroadcaster &&
|
||||||
|
!nodeDB->isFull()) {
|
||||||
if (airTime->isTxAllowedChannelUtil(true)) {
|
if (airTime->isTxAllowedChannelUtil(true)) {
|
||||||
// Hops used by the request. If somebody in between running modified firmware modified it, ignore it
|
// Hops used by the request. If somebody in between running modified firmware modified it, ignore it
|
||||||
auto hopStart = mp->hop_start;
|
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)
|
bool MeshService::trySendPosition(NodeNum dest, bool wantReplies)
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeDetail *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
|
|
||||||
assert(node);
|
assert(node);
|
||||||
|
|
||||||
@@ -376,24 +379,25 @@ void MeshService::sendClientNotification(meshtastic_ClientNotification *n)
|
|||||||
fromNum++;
|
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);
|
assert(node);
|
||||||
|
|
||||||
// We might not have a position yet for our local node, in that case, at least try to send the time
|
// Ensure we have a position container so time fields stay fresh even without GPS fixes
|
||||||
if (!node->has_position) {
|
if (!detailHasFlag(*node, NODEDETAIL_FLAG_HAS_POSITION)) {
|
||||||
memset(&node->position, 0, sizeof(node->position));
|
detailSetFlag(*node, NODEDETAIL_FLAG_HAS_POSITION, true);
|
||||||
node->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)
|
// Update our local node info with our time (even if we don't decide to update anyone else)
|
||||||
node->last_heard =
|
node->last_heard =
|
||||||
getValidTime(RTCQualityFromNet); // This nodedb timestamp might be stale, so update it if our clock is kinda valid
|
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) {
|
if (powerStatus->getHasBattery() == 1) {
|
||||||
updateBatteryLevel(powerStatus->getBatteryChargePercent());
|
updateBatteryLevel(powerStatus->getBatteryChargePercent());
|
||||||
@@ -406,7 +410,7 @@ meshtastic_NodeInfoLite *MeshService::refreshLocalMeshNode()
|
|||||||
int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus)
|
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)
|
// 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;
|
meshtastic_Position pos = meshtastic_Position_init_default;
|
||||||
|
|
||||||
if (newStatus->getHasLock()) {
|
if (newStatus->getHasLock()) {
|
||||||
@@ -421,7 +425,7 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus)
|
|||||||
// Used fixed position if configured regardless of GPS lock
|
// Used fixed position if configured regardless of GPS lock
|
||||||
if (config.position.fixed_position) {
|
if (config.position.fixed_position) {
|
||||||
LOG_WARN("Use fixed position");
|
LOG_WARN("Use fixed position");
|
||||||
pos = TypeConversions::ConvertToPosition(node->position);
|
pos = TypeConversions::ConvertToPosition(detailToPositionLite(*node));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a fresh timestamp
|
// Add a fresh timestamp
|
||||||
|
|||||||
+13
-1
@@ -79,6 +79,18 @@ class MeshService
|
|||||||
uint32_t oldFromNum = 0;
|
uint32_t oldFromNum = 0;
|
||||||
|
|
||||||
public:
|
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)
|
static bool isTextPayload(const meshtastic_MeshPacket *p)
|
||||||
{
|
{
|
||||||
if (moduleConfig.range_test.enabled && p->decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP) {
|
if (moduleConfig.range_test.enabled && p->decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP) {
|
||||||
@@ -158,7 +170,7 @@ class MeshService
|
|||||||
bool cancelSending(PacketId id);
|
bool cancelSending(PacketId id);
|
||||||
|
|
||||||
/// Pull the latest power and time info into my nodeinfo
|
/// Pull the latest power and time info into my nodeinfo
|
||||||
meshtastic_NodeInfoLite *refreshLocalMeshNode();
|
meshtastic_NodeDetail *refreshLocalMeshNode();
|
||||||
|
|
||||||
/// Send a packet to the phone
|
/// Send a packet to the phone
|
||||||
void sendToPhone(meshtastic_MeshPacket *p);
|
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
|
// is not 0 (means implicit ACK) and original packet was also relayed by this node, or we sent it directly to the
|
||||||
// destination
|
// destination
|
||||||
if (p->from != 0) {
|
if (p->from != 0) {
|
||||||
meshtastic_NodeInfoLite *origTx = nodeDB->getMeshNode(p->from);
|
meshtastic_NodeDetail *origTx = nodeDB->getMeshNode(p->from);
|
||||||
if (origTx) {
|
if (origTx) {
|
||||||
// Either relayer of ACK was also a relayer of the packet, or we were the *only* relayer and the ACK came
|
// 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
|
// directly from the destination
|
||||||
@@ -176,7 +176,7 @@ uint8_t NextHopRouter::getNextHop(NodeNum to, uint8_t relay_node)
|
|||||||
if (isBroadcast(to))
|
if (isBroadcast(to))
|
||||||
return NO_NEXT_HOP_PREFERENCE;
|
return NO_NEXT_HOP_PREFERENCE;
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(to);
|
meshtastic_NodeDetail *node = nodeDB->getMeshNode(to);
|
||||||
if (node && node->next_hop) {
|
if (node && node->next_hop) {
|
||||||
// We are careful not to return the relay node as the next hop
|
// We are careful not to return the relay node as the next hop
|
||||||
if (node->next_hop != relay_node) {
|
if (node->next_hop != relay_node) {
|
||||||
@@ -296,7 +296,7 @@ int32_t NextHopRouter::doRetransmissions()
|
|||||||
// Last retransmission, reset next_hop (fallback to FloodingRouter)
|
// Last retransmission, reset next_hop (fallback to FloodingRouter)
|
||||||
p.packet->next_hop = NO_NEXT_HOP_PREFERENCE;
|
p.packet->next_hop = NO_NEXT_HOP_PREFERENCE;
|
||||||
// Also reset it in the nodeDB
|
// Also reset it in the nodeDB
|
||||||
meshtastic_NodeInfoLite *sentTo = nodeDB->getMeshNode(p.packet->to);
|
meshtastic_NodeDetail *sentTo = nodeDB->getMeshNode(p.packet->to);
|
||||||
if (sentTo) {
|
if (sentTo) {
|
||||||
LOG_INFO("Resetting next hop for packet with dest 0x%x\n", p.packet->to);
|
LOG_INFO("Resetting next hop for packet with dest 0x%x\n", p.packet->to);
|
||||||
sentTo->next_hop = NO_NEXT_HOP_PREFERENCE;
|
sentTo->next_hop = NO_NEXT_HOP_PREFERENCE;
|
||||||
|
|||||||
+171
-123
@@ -24,6 +24,7 @@
|
|||||||
#include "modules/NeighborInfoModule.h"
|
#include "modules/NeighborInfoModule.h"
|
||||||
#include <ErriezCRC32.h>
|
#include <ErriezCRC32.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
#include <pb_decode.h>
|
#include <pb_decode.h>
|
||||||
#include <pb_encode.h>
|
#include <pb_encode.h>
|
||||||
#include <vector>
|
#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)
|
bool meshtastic_NodeDatabase_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field)
|
||||||
{
|
{
|
||||||
if (ostream) {
|
if (ostream) {
|
||||||
std::vector<meshtastic_NodeInfoLite> const *vec = (std::vector<meshtastic_NodeInfoLite> *)field->pData;
|
const auto *vec = reinterpret_cast<const std::vector<meshtastic_NodeDetail> *>(field->pData);
|
||||||
for (auto item : *vec) {
|
for (const auto &item : *vec) {
|
||||||
if (!pb_encode_tag_for_field(ostream, field))
|
if (!pb_encode_tag_for_field(ostream, field))
|
||||||
return false;
|
return false;
|
||||||
pb_encode_submessage(ostream, meshtastic_NodeInfoLite_fields, &item);
|
if (!pb_encode_submessage(ostream, meshtastic_NodeDetail_fields, &item))
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (istream) {
|
if (istream) {
|
||||||
meshtastic_NodeInfoLite node; // this gets good data
|
meshtastic_NodeDetail node = meshtastic_NodeDetail_init_default; // this gets good data
|
||||||
std::vector<meshtastic_NodeInfoLite> *vec = (std::vector<meshtastic_NodeInfoLite> *)field->pData;
|
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);
|
vec->push_back(node);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -295,9 +297,8 @@ NodeDB::NodeDB()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
// Include our owner in the node db under our nodenum
|
// Include our owner in the node db under our nodenum
|
||||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum());
|
meshtastic_NodeDetail *info = getOrCreateMeshNode(getNodeNum());
|
||||||
info->user = TypeConversions::ConvertToUserLite(owner);
|
applyUserToDetail(*info, owner);
|
||||||
info->has_user = true;
|
|
||||||
|
|
||||||
// If node database has not been saved for the first time, save it now
|
// If node database has not been saved for the first time, save it now
|
||||||
#ifdef FSCom
|
#ifdef FSCom
|
||||||
@@ -520,7 +521,7 @@ void NodeDB::installDefaultNodeDatabase()
|
|||||||
{
|
{
|
||||||
LOG_DEBUG("Install default NodeDatabase");
|
LOG_DEBUG("Install default NodeDatabase");
|
||||||
nodeDatabase.version = DEVICESTATE_CUR_VER;
|
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;
|
numMeshNodes = 0;
|
||||||
meshNodes = &nodeDatabase.nodes;
|
meshNodes = &nodeDatabase.nodes;
|
||||||
}
|
}
|
||||||
@@ -653,7 +654,7 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
|||||||
strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
|
strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
|
||||||
|
|
||||||
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \
|
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \
|
||||||
defined(ELECROW_PANEL)) && \
|
defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT)) && \
|
||||||
HAS_TFT
|
HAS_TFT
|
||||||
// switch BT off by default; use TFT programming mode or hotkey to enable
|
// switch BT off by default; use TFT programming mode or hotkey to enable
|
||||||
config.bluetooth.enabled = false;
|
config.bluetooth.enabled = false;
|
||||||
@@ -984,12 +985,25 @@ void NodeDB::installDefaultChannels()
|
|||||||
channelFile.version = DEVICESTATE_CUR_VER;
|
channelFile.version = DEVICESTATE_CUR_VER;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDB::resetNodes()
|
void NodeDB::resetNodes(bool keepFavorites)
|
||||||
{
|
{
|
||||||
if (!config.position.fixed_position)
|
if (!config.position.fixed_position)
|
||||||
clearLocalPosition();
|
clearLocalPosition();
|
||||||
numMeshNodes = 1;
|
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_text_message = false;
|
||||||
devicestate.has_rx_waypoint = false;
|
devicestate.has_rx_waypoint = false;
|
||||||
saveNodeDatabaseToDisk();
|
saveNodeDatabaseToDisk();
|
||||||
@@ -1008,19 +1022,17 @@ void NodeDB::removeNodeByNum(NodeNum nodeNum)
|
|||||||
removed++;
|
removed++;
|
||||||
}
|
}
|
||||||
numMeshNodes -= removed;
|
numMeshNodes -= removed;
|
||||||
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + 1,
|
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + 1, makeDefaultDetail());
|
||||||
meshtastic_NodeInfoLite());
|
|
||||||
LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Save changes", removed);
|
LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Save changes", removed);
|
||||||
saveNodeDatabaseToDisk();
|
saveNodeDatabaseToDisk();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDB::clearLocalPosition()
|
void NodeDB::clearLocalPosition()
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *node = getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeDetail *detail = getMeshNode(nodeDB->getNodeNum());
|
||||||
node->position.latitude_i = 0;
|
if (detail) {
|
||||||
node->position.longitude_i = 0;
|
clearPositionFromDetail(*detail);
|
||||||
node->position.altitude = 0;
|
}
|
||||||
node->position.time = 0;
|
|
||||||
setLocalPosition(meshtastic_Position_init_default);
|
setLocalPosition(meshtastic_Position_init_default);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1028,10 +1040,10 @@ void NodeDB::cleanupMeshDB()
|
|||||||
{
|
{
|
||||||
int newPos = 0, removed = 0;
|
int newPos = 0, removed = 0;
|
||||||
for (int i = 0; i < numMeshNodes; i++) {
|
for (int i = 0; i < numMeshNodes; i++) {
|
||||||
if (meshNodes->at(i).has_user) {
|
if (detailHasFlag(meshNodes->at(i), NODEDETAIL_FLAG_HAS_USER)) {
|
||||||
if (meshNodes->at(i).user.public_key.size > 0) {
|
if (meshNodes->at(i).public_key.size > 0) {
|
||||||
if (memfll(meshNodes->at(i).user.public_key.bytes, 0, meshNodes->at(i).user.public_key.size)) {
|
if (memfll(meshNodes->at(i).public_key.bytes, 0, meshNodes->at(i).public_key.size)) {
|
||||||
meshNodes->at(i).user.public_key.size = 0;
|
meshNodes->at(i).public_key.size = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (newPos != i)
|
if (newPos != i)
|
||||||
@@ -1044,7 +1056,7 @@ void NodeDB::cleanupMeshDB()
|
|||||||
}
|
}
|
||||||
numMeshNodes -= removed;
|
numMeshNodes -= removed;
|
||||||
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + removed,
|
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + removed,
|
||||||
meshtastic_NodeInfoLite());
|
makeDefaultDetail());
|
||||||
LOG_DEBUG("cleanupMeshDB purged %d entries", removed);
|
LOG_DEBUG("cleanupMeshDB purged %d entries", removed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1096,14 +1108,14 @@ void NodeDB::pickNewNodeNum()
|
|||||||
nodeNum = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5];
|
nodeNum = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5];
|
||||||
}
|
}
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *found;
|
meshtastic_NodeDetail *found;
|
||||||
while (((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0) ||
|
while (((found = getMeshNode(nodeNum)) && memcmp(found->macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0) ||
|
||||||
(nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED)) {
|
(nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED)) {
|
||||||
NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice
|
NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice
|
||||||
if (found)
|
if (found)
|
||||||
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 "
|
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",
|
"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;
|
nodeNum = candidate;
|
||||||
}
|
}
|
||||||
LOG_DEBUG("Use nodenum 0x%x ", nodeNum);
|
LOG_DEBUG("Use nodenum 0x%x ", nodeNum);
|
||||||
@@ -1402,7 +1414,7 @@ bool NodeDB::saveDeviceStateToDisk()
|
|||||||
FSCom.mkdir("/prefs");
|
FSCom.mkdir("/prefs");
|
||||||
spiLock->unlock();
|
spiLock->unlock();
|
||||||
#endif
|
#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
|
// 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);
|
return saveProto(deviceStateFileName, meshtastic_DeviceState_size, &meshtastic_DeviceState_msg, &devicestate, true);
|
||||||
}
|
}
|
||||||
@@ -1495,7 +1507,7 @@ bool NodeDB::saveToDisk(int saveWhat)
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
const meshtastic_NodeInfoLite *NodeDB::readNextMeshNode(uint32_t &readIndex)
|
const meshtastic_NodeDetail *NodeDB::readNextMeshNode(uint32_t &readIndex)
|
||||||
{
|
{
|
||||||
if (readIndex < numMeshNodes)
|
if (readIndex < numMeshNodes)
|
||||||
return &meshNodes->at(readIndex++);
|
return &meshNodes->at(readIndex++);
|
||||||
@@ -1504,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
|
/// 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();
|
uint32_t now = getTime();
|
||||||
|
|
||||||
@@ -1534,9 +1546,10 @@ size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
|
|||||||
|
|
||||||
// FIXME this implementation is kinda expensive
|
// FIXME this implementation is kinda expensive
|
||||||
for (int i = 0; i < numMeshNodes; i++) {
|
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;
|
continue;
|
||||||
if (sinceLastSeen(&meshNodes->at(i)) < NUM_ONLINE_SECS)
|
if (sinceLastSeen(&detail) < NUM_ONLINE_SECS)
|
||||||
numseen++;
|
numseen++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1550,8 +1563,8 @@ size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
|
|||||||
*/
|
*/
|
||||||
void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSource src)
|
void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSource src)
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
|
meshtastic_NodeDetail *detail = getOrCreateMeshNode(nodeId);
|
||||||
if (!info) {
|
if (!detail) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1561,12 +1574,13 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou
|
|||||||
p.altitude);
|
p.altitude);
|
||||||
|
|
||||||
setLocalPosition(p);
|
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) {
|
} else if ((p.time > 0) && !p.latitude_i && !p.longitude_i && !p.timestamp && !p.location_source) {
|
||||||
// FIXME SPECIAL TIME SETTING PACKET FROM EUD TO RADIO
|
// FIXME SPECIAL TIME SETTING PACKET FROM EUD TO RADIO
|
||||||
// (stop-gap fix for issue #900)
|
// (stop-gap fix for issue #900)
|
||||||
LOG_DEBUG("updatePosition SPECIAL time setting time=%u", p.time);
|
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 {
|
} else {
|
||||||
// Be careful to only update fields that have been set by the REMOTE sender
|
// 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
|
// A lot of position reports don't have time populated. In that case, be careful to not blow away the time we
|
||||||
@@ -1576,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);
|
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
|
// 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
|
// Next, update atomically
|
||||||
info->position = TypeConversions::ConvertToPositionLite(p);
|
applyPositionToDetail(*detail, p);
|
||||||
|
|
||||||
// Last, restore any fields that may have been overwritten
|
// Last, restore any fields that may have been overwritten
|
||||||
if (!info->position.time)
|
if (!detail->position_time)
|
||||||
info->position.time = tmp_time;
|
detail->position_time = tmp_time;
|
||||||
}
|
}
|
||||||
info->has_position = true;
|
updateGUIforNode = detail;
|
||||||
updateGUIforNode = info;
|
|
||||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1595,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)
|
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
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1607,9 +1620,8 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
|
|||||||
} else {
|
} else {
|
||||||
LOG_DEBUG("updateTelemetry REMOTE node=0x%x ", nodeId);
|
LOG_DEBUG("updateTelemetry REMOTE node=0x%x ", nodeId);
|
||||||
}
|
}
|
||||||
info->device_metrics = t.variant.device_metrics;
|
applyMetricsToDetail(*detail, t.variant.device_metrics);
|
||||||
info->has_device_metrics = true;
|
updateGUIforNode = detail;
|
||||||
updateGUIforNode = info;
|
|
||||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1618,39 +1630,58 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
|
|||||||
*/
|
*/
|
||||||
void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(contact.node_num);
|
meshtastic_NodeDetail *detail = getOrCreateMeshNode(contact.node_num);
|
||||||
if (!info || !contact.has_user) {
|
if (!detail || !contact.has_user) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// If the local node has this node marked as manually verified
|
// If the local node has this node marked as manually verified
|
||||||
// and the client does not, do not allow the client to update the
|
// and the client does not, do not allow the client to update the
|
||||||
// saved public key.
|
// saved public key.
|
||||||
if ((info->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) && !contact.manually_verified) {
|
if (detailHasFlag(*detail, NODEDETAIL_FLAG_IS_KEY_MANUALLY_VERIFIED) && !contact.manually_verified) {
|
||||||
if (contact.user.public_key.size != info->user.public_key.size ||
|
if (contact.user.public_key.size != detail->public_key.size ||
|
||||||
memcmp(contact.user.public_key.bytes, info->user.public_key.bytes, info->user.public_key.size) != 0) {
|
memcmp(contact.user.public_key.bytes, detail->public_key.bytes, detail->public_key.size) != 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info->num = contact.node_num;
|
detail->num = contact.node_num;
|
||||||
info->has_user = true;
|
applyUserToDetail(*detail, contact.user);
|
||||||
info->user = TypeConversions::ConvertToUserLite(contact.user);
|
|
||||||
if (contact.should_ignore) {
|
if (contact.should_ignore) {
|
||||||
// If should_ignore is set,
|
// If should_ignore is set,
|
||||||
// we need to clear the public key and other cruft, in addition to setting the node as ignored
|
// we need to clear the public key and other cruft, in addition to setting the node as ignored
|
||||||
info->is_ignored = true;
|
detailSetFlag(*detail, NODEDETAIL_FLAG_IS_IGNORED, true);
|
||||||
info->has_device_metrics = false;
|
detailSetFlag(*detail, NODEDETAIL_FLAG_IS_FAVORITE, false);
|
||||||
info->has_position = false;
|
clearMetricsFromDetail(*detail);
|
||||||
info->user.public_key.size = 0;
|
clearPositionFromDetail(*detail);
|
||||||
info->user.public_key.bytes[0] = 0;
|
detail->public_key.size = 0;
|
||||||
|
memset(detail->public_key.bytes, 0, sizeof(detail->public_key.bytes));
|
||||||
} else {
|
} else {
|
||||||
info->last_heard = getValidTime(RTCQualityNTP);
|
detailSetFlag(*detail, NODEDETAIL_FLAG_IS_IGNORED, false);
|
||||||
info->is_favorite = true;
|
/* 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
|
// 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) {
|
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.
|
// Mark the node's key as manually verified to indicate trustworthiness.
|
||||||
updateGUIforNode = info;
|
updateGUIforNode = detail;
|
||||||
sortMeshDB();
|
sortMeshDB();
|
||||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||||
}
|
}
|
||||||
@@ -1661,8 +1692,8 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
|||||||
*/
|
*/
|
||||||
bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex)
|
bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelIndex)
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
|
meshtastic_NodeDetail *detail = getOrCreateMeshNode(nodeId);
|
||||||
if (!info) {
|
if (!detail) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1687,9 +1718,9 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
|||||||
return false;
|
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 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");
|
LOG_WARN("Public Key mismatch, dropping NodeInfo");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1702,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
|
// Always ensure user.id is derived from nodeId, regardless of what was received
|
||||||
snprintf(p.id, sizeof(p.id), "!%08x", nodeId);
|
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
|
meshtastic_NodeDetail before = *detail;
|
||||||
auto lite = TypeConversions::ConvertToUserLite(p);
|
|
||||||
bool changed = memcmp(&info->user, &lite, sizeof(info->user)) || (info->channel != channelIndex);
|
|
||||||
|
|
||||||
info->user = lite;
|
applyUserToDetail(*detail, p);
|
||||||
if (info->user.public_key.size == 32) {
|
|
||||||
printBytes("Saved Pubkey: ", info->user.public_key.bytes, 32);
|
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)
|
if (nodeId != getNodeNum()) {
|
||||||
LOG_DEBUG("Update changed=%d user %s/%s, id=0x%08x, channel=%d", changed, info->user.long_name, info->user.short_name, nodeId,
|
detail->channel = channelIndex; // Set channel we need to use to reach this node (but don't set our own channel)
|
||||||
info->channel);
|
}
|
||||||
info->has_user = true;
|
|
||||||
|
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) {
|
if (changed) {
|
||||||
updateGUIforNode = info;
|
updateGUIforNode = detail;
|
||||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||||
|
|
||||||
// We just changed something about a User,
|
// We just changed something about a User,
|
||||||
@@ -1745,23 +1792,24 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
|
|||||||
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
|
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
|
||||||
LOG_DEBUG("Update DB node 0x%x, rx_time=%u", mp.from, mp.rx_time);
|
LOG_DEBUG("Update DB node 0x%x, rx_time=%u", mp.from, mp.rx_time);
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getFrom(&mp));
|
meshtastic_NodeDetail *detail = getOrCreateMeshNode(getFrom(&mp));
|
||||||
if (!info) {
|
if (!detail) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mp.rx_time) // if the packet has a valid timestamp use it to update our last_heard
|
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)
|
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 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) {
|
if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start) {
|
||||||
info->has_hops_away = true;
|
detailSetFlag(*detail, NODEDETAIL_FLAG_HAS_HOPS_AWAY, true);
|
||||||
info->hops_away = mp.hop_start - mp.hop_limit;
|
uint32_t hopDelta = mp.hop_start - mp.hop_limit;
|
||||||
|
detail->hops_away = static_cast<uint8_t>(std::min<uint32_t>(hopDelta, UINT8_MAX));
|
||||||
}
|
}
|
||||||
sortMeshDB();
|
sortMeshDB();
|
||||||
}
|
}
|
||||||
@@ -1769,9 +1817,9 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
|
|||||||
|
|
||||||
void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
|
void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
meshtastic_NodeDetail *detail = getMeshNode(nodeId);
|
||||||
if (lite && lite->is_favorite != is_favorite) {
|
if (detail && detailIsFavorite(*detail) != is_favorite) {
|
||||||
lite->is_favorite = is_favorite;
|
detailSetFlag(*detail, NODEDETAIL_FLAG_IS_FAVORITE, is_favorite);
|
||||||
sortMeshDB();
|
sortMeshDB();
|
||||||
saveNodeDatabaseToDisk();
|
saveNodeDatabaseToDisk();
|
||||||
}
|
}
|
||||||
@@ -1785,10 +1833,10 @@ bool NodeDB::isFavorite(uint32_t nodeId)
|
|||||||
if (nodeId == NODENUM_BROADCAST)
|
if (nodeId == NODENUM_BROADCAST)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
meshtastic_NodeDetail *detail = getMeshNode(nodeId);
|
||||||
|
|
||||||
if (lite) {
|
if (detail) {
|
||||||
return lite->is_favorite;
|
return detailIsFavorite(*detail);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1804,23 +1852,21 @@ bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
|
|||||||
if (p.to == NODENUM_BROADCAST)
|
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
|
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 seenFrom = false;
|
||||||
bool seenTo = false;
|
bool seenTo = false;
|
||||||
|
|
||||||
for (int i = 0; i < numMeshNodes; i++) {
|
for (int i = 0; i < numMeshNodes; i++) {
|
||||||
lite = &meshNodes->at(i);
|
auto *detail = &meshNodes->at(i);
|
||||||
|
|
||||||
if (lite->num == p.from) {
|
if (detail->num == p.from) {
|
||||||
if (lite->is_favorite)
|
if (detailIsFavorite(*detail))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
seenFrom = true;
|
seenFrom = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lite->num == p.to) {
|
if (detail->num == p.to) {
|
||||||
if (lite->is_favorite)
|
if (detailIsFavorite(*detail))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
seenTo = true;
|
seenTo = true;
|
||||||
@@ -1856,10 +1902,10 @@ void NodeDB::sortMeshDB()
|
|||||||
// TODO: Look for at(i-1) also matching own node num, and throw the DB in the trash
|
// 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));
|
std::swap(meshNodes->at(i), meshNodes->at(i - 1));
|
||||||
changed = true;
|
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));
|
std::swap(meshNodes->at(i), meshNodes->at(i - 1));
|
||||||
changed = true;
|
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
|
// noop
|
||||||
} else if (meshNodes->at(i).last_heard > meshNodes->at(i - 1).last_heard) {
|
} else if (meshNodes->at(i).last_heard > meshNodes->at(i - 1).last_heard) {
|
||||||
std::swap(meshNodes->at(i), meshNodes->at(i - 1));
|
std::swap(meshNodes->at(i), meshNodes->at(i - 1));
|
||||||
@@ -1873,11 +1919,11 @@ void NodeDB::sortMeshDB()
|
|||||||
|
|
||||||
uint8_t NodeDB::getMeshNodeChannel(NodeNum n)
|
uint8_t NodeDB::getMeshNodeChannel(NodeNum n)
|
||||||
{
|
{
|
||||||
const meshtastic_NodeInfoLite *info = getMeshNode(n);
|
const meshtastic_NodeDetail *detail = getMeshNode(n);
|
||||||
if (!info) {
|
if (!detail) {
|
||||||
return 0; // defaults to PRIMARY
|
return 0; // defaults to PRIMARY
|
||||||
}
|
}
|
||||||
return info->channel;
|
return detail->channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string NodeDB::getNodeId() const
|
std::string NodeDB::getNodeId() const
|
||||||
@@ -1889,7 +1935,7 @@ std::string NodeDB::getNodeId() const
|
|||||||
|
|
||||||
/// Find a node in our DB, return null for missing
|
/// Find a node in our DB, return null for missing
|
||||||
/// NOTE: This function might be called from an ISR
|
/// NOTE: This function might be called from an ISR
|
||||||
meshtastic_NodeInfoLite *NodeDB::getMeshNode(NodeNum n)
|
meshtastic_NodeDetail *NodeDB::getMeshNode(NodeNum n)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < numMeshNodes; i++)
|
for (int i = 0; i < numMeshNodes; i++)
|
||||||
if (meshNodes->at(i).num == n)
|
if (meshNodes->at(i).num == n)
|
||||||
@@ -1905,11 +1951,11 @@ bool NodeDB::isFull()
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Find a node in our DB, create an empty NodeInfo if missing
|
/// Find a node in our DB, create an empty NodeInfo if missing
|
||||||
meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
meshtastic_NodeDetail *NodeDB::getOrCreateMeshNode(NodeNum n)
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *lite = getMeshNode(n);
|
meshtastic_NodeDetail *detail = getMeshNode(n);
|
||||||
|
|
||||||
if (!lite) {
|
if (!detail) {
|
||||||
if (isFull()) {
|
if (isFull()) {
|
||||||
LOG_INFO("Node database full with %i nodes and %u bytes free. Erasing oldest entry", numMeshNodes,
|
LOG_INFO("Node database full with %i nodes and %u bytes free. Erasing oldest entry", numMeshNodes,
|
||||||
memGet.getFreeHeap());
|
memGet.getFreeHeap());
|
||||||
@@ -1920,14 +1966,14 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
|||||||
int oldestBoringIndex = -1;
|
int oldestBoringIndex = -1;
|
||||||
for (int i = 1; i < numMeshNodes; i++) {
|
for (int i = 1; i < numMeshNodes; i++) {
|
||||||
// Simply the oldest non-favorite, non-ignored, non-verified node
|
// Simply the oldest non-favorite, non-ignored, non-verified node
|
||||||
if (!meshNodes->at(i).is_favorite && !meshNodes->at(i).is_ignored &&
|
const auto &candidate = meshNodes->at(i);
|
||||||
!(meshNodes->at(i).bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) &&
|
if (!detailIsFavorite(candidate) && !detailIsIgnored(candidate) &&
|
||||||
meshNodes->at(i).last_heard < oldest) {
|
!detailHasFlag(candidate, NODEDETAIL_FLAG_IS_KEY_MANUALLY_VERIFIED) && meshNodes->at(i).last_heard < oldest) {
|
||||||
oldest = meshNodes->at(i).last_heard;
|
oldest = meshNodes->at(i).last_heard;
|
||||||
oldestIndex = i;
|
oldestIndex = i;
|
||||||
}
|
}
|
||||||
// The oldest "boring" node
|
// 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) {
|
meshNodes->at(i).last_heard < oldestBoring) {
|
||||||
oldestBoring = meshNodes->at(i).last_heard;
|
oldestBoring = meshNodes->at(i).last_heard;
|
||||||
oldestBoringIndex = i;
|
oldestBoringIndex = i;
|
||||||
@@ -1947,35 +1993,36 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// add the node at the end
|
// add the node at the end
|
||||||
lite = &meshNodes->at((numMeshNodes)++);
|
detail = &meshNodes->at((numMeshNodes)++);
|
||||||
|
|
||||||
// everything is missing except the nodenum
|
// everything is missing except the nodenum
|
||||||
memset(lite, 0, sizeof(*lite));
|
*detail = makeDefaultDetail();
|
||||||
lite->num = n;
|
detail->num = n;
|
||||||
LOG_INFO("Adding node to database with %i nodes and %u bytes free!", numMeshNodes, memGet.getFreeHeap());
|
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
|
/// Sometimes we will have Position objects that only have a time, so check for
|
||||||
/// valid lat/lon
|
/// 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
|
/// If we have a node / user and they report is_licensed = true
|
||||||
/// we consider them licensed
|
/// we consider them licensed
|
||||||
UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum)
|
UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum)
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *info = getMeshNode(nodeNum);
|
meshtastic_NodeDetail *detail = getMeshNode(nodeNum);
|
||||||
if (!info || !info->has_user) {
|
if (!detail || !detailHasFlag(*detail, NODEDETAIL_FLAG_HAS_USER)) {
|
||||||
return UserLicenseStatus::NotKnown;
|
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)
|
bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest)
|
||||||
{
|
{
|
||||||
if (keyToTest.size == 32) {
|
if (keyToTest.size == 32) {
|
||||||
@@ -1990,6 +2037,7 @@ bool NodeDB::checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_pub
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location)
|
bool NodeDB::backupPreferences(meshtastic_AdminMessage_BackupLocation location)
|
||||||
{
|
{
|
||||||
|
|||||||
+277
-11
@@ -4,6 +4,8 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
#include <pb_encode.h>
|
#include <pb_encode.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#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 *channelFileName = "/prefs/channels.proto";
|
||||||
static constexpr const char *backupFileName = "/backups/backup.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
|
/// 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
|
/// Given a packet, return how many seconds in the past (vs now) it was received
|
||||||
uint32_t sinceReceived(const meshtastic_MeshPacket *p);
|
uint32_t sinceReceived(const meshtastic_MeshPacket *p);
|
||||||
@@ -135,9 +142,9 @@ class NodeDB
|
|||||||
// Note: these two references just point into our static array we serialize to/from disk
|
// Note: these two references just point into our static array we serialize to/from disk
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::vector<meshtastic_NodeInfoLite> *meshNodes;
|
std::vector<meshtastic_NodeDetail> *meshNodes;
|
||||||
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
|
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
|
||||||
meshtastic_NodeInfoLite *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
meshtastic_NodeDetail *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
||||||
Observable<const meshtastic::NodeStatus *> newStatus;
|
Observable<const meshtastic::NodeStatus *> newStatus;
|
||||||
pb_size_t numMeshNodes;
|
pb_size_t numMeshNodes;
|
||||||
|
|
||||||
@@ -229,7 +236,8 @@ class NodeDB
|
|||||||
*/
|
*/
|
||||||
size_t getNumOnlineMeshNodes(bool localOnly = false);
|
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);
|
bool factoryReset(bool eraseBleBonds = false);
|
||||||
|
|
||||||
@@ -240,15 +248,15 @@ class NodeDB
|
|||||||
|
|
||||||
void installRoleDefaults(meshtastic_Config_DeviceConfig_Role role);
|
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);
|
assert(x < numMeshNodes);
|
||||||
return &meshNodes->at(x);
|
return &meshNodes->at(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n);
|
virtual meshtastic_NodeDetail *getMeshNode(NodeNum n);
|
||||||
size_t getNumMeshNodes() { return numMeshNodes; }
|
size_t getNumMeshNodes() { return numMeshNodes; }
|
||||||
|
|
||||||
UserLicenseStatus getLicenseStatus(uint32_t nodeNum);
|
UserLicenseStatus getLicenseStatus(uint32_t nodeNum);
|
||||||
@@ -259,7 +267,7 @@ class NodeDB
|
|||||||
emptyNodeDatabase.version = DEVICESTATE_CUR_VER;
|
emptyNodeDatabase.version = DEVICESTATE_CUR_VER;
|
||||||
size_t nodeDatabaseSize;
|
size_t nodeDatabaseSize;
|
||||||
pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &emptyNodeDatabase);
|
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
|
// 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;
|
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);
|
bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest);
|
||||||
|
#endif
|
||||||
|
|
||||||
bool backupPreferences(meshtastic_AdminMessage_BackupLocation location);
|
bool backupPreferences(meshtastic_AdminMessage_BackupLocation location);
|
||||||
bool restorePreferences(meshtastic_AdminMessage_BackupLocation location,
|
bool restorePreferences(meshtastic_AdminMessage_BackupLocation location,
|
||||||
@@ -301,8 +311,8 @@ class NodeDB
|
|||||||
uint32_t lastNodeDbSave = 0; // when we last saved our db to flash
|
uint32_t lastNodeDbSave = 0; // when we last saved our db to flash
|
||||||
uint32_t lastBackupAttempt = 0; // when we last tried a backup automatically or manually
|
uint32_t lastBackupAttempt = 0; // when we last tried a backup automatically or manually
|
||||||
uint32_t lastSort = 0; // When last sorted the nodeDB
|
uint32_t lastSort = 0; // When last sorted the nodeDB
|
||||||
/// Find a node in our DB, create an empty NodeInfoLite if missing
|
/// Find a node in our DB, create an empty NodeDetail if missing
|
||||||
meshtastic_NodeInfoLite *getOrCreateMeshNode(NodeNum n);
|
meshtastic_NodeDetail *getOrCreateMeshNode(NodeNum n);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Internal boolean to track sorting paused
|
* Internal boolean to track sorting paused
|
||||||
@@ -367,6 +377,262 @@ extern uint32_t error_address;
|
|||||||
#define NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_SHIFT 0
|
#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)
|
#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 \
|
#define Module_Config_size \
|
||||||
(ModuleConfig_CannedMessageConfig_size + ModuleConfig_ExternalNotificationConfig_size + ModuleConfig_MQTTConfig_size + \
|
(ModuleConfig_CannedMessageConfig_size + ModuleConfig_ExternalNotificationConfig_size + ModuleConfig_MQTTConfig_size + \
|
||||||
ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \
|
ModuleConfig_RangeTestConfig_size + ModuleConfig_SerialConfig_size + ModuleConfig_StoreForwardConfig_size + \
|
||||||
|
|||||||
+29
-4
@@ -87,6 +87,18 @@ void PhoneAPI::handleStartConfig()
|
|||||||
void PhoneAPI::close()
|
void PhoneAPI::close()
|
||||||
{
|
{
|
||||||
LOG_DEBUG("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) {
|
if (state != STATE_SEND_NOTHING) {
|
||||||
state = STATE_SEND_NOTHING;
|
state = STATE_SEND_NOTHING;
|
||||||
@@ -255,9 +267,9 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
|||||||
|
|
||||||
case STATE_SEND_OWN_NODEINFO: {
|
case STATE_SEND_OWN_NODEINFO: {
|
||||||
LOG_DEBUG("Send My NodeInfo");
|
LOG_DEBUG("Send My NodeInfo");
|
||||||
auto us = nodeDB->readNextMeshNode(readIndex);
|
const meshtastic_NodeDetail *us = nodeDB->readNextMeshNode(readIndex);
|
||||||
if (us) {
|
if (us) {
|
||||||
auto info = TypeConversions::ConvertToNodeInfo(us);
|
auto info = TypeConversions::ConvertToNodeInfo(*us);
|
||||||
info.has_hops_away = false;
|
info.has_hops_away = false;
|
||||||
info.is_favorite = true;
|
info.is_favorite = true;
|
||||||
{
|
{
|
||||||
@@ -578,6 +590,19 @@ void PhoneAPI::sendConfigComplete()
|
|||||||
fromRadioScratch.config_complete_id = config_nonce;
|
fromRadioScratch.config_complete_id = config_nonce;
|
||||||
config_nonce = 0;
|
config_nonce = 0;
|
||||||
state = STATE_SEND_PACKETS;
|
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
|
// Allow subclasses to know we've entered steady-state so they can lower power consumption
|
||||||
onConfigComplete();
|
onConfigComplete();
|
||||||
@@ -608,11 +633,11 @@ void PhoneAPI::prefetchNodeInfos()
|
|||||||
{
|
{
|
||||||
concurrency::LockGuard guard(&nodeInfoMutex);
|
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||||
while (nodeInfoQueue.size() < kNodePrefetchDepth) {
|
while (nodeInfoQueue.size() < kNodePrefetchDepth) {
|
||||||
auto nextNode = nodeDB->readNextMeshNode(readIndex);
|
const meshtastic_NodeDetail *nextNode = nodeDB->readNextMeshNode(readIndex);
|
||||||
if (!nextNode)
|
if (!nextNode)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
auto info = TypeConversions::ConvertToNodeInfo(nextNode);
|
auto info = TypeConversions::ConvertToNodeInfo(*nextNode);
|
||||||
bool isUs = info.num == nodeDB->getNodeNum();
|
bool isUs = info.num == nodeDB->getNodeNum();
|
||||||
info.hops_away = isUs ? 0 : info.hops_away;
|
info.hops_away = isUs ? 0 : info.hops_away;
|
||||||
info.last_heard = isUs ? getValidTime(RTCQualityFromNet) : info.last_heard;
|
info.last_heard = isUs ? getValidTime(RTCQualityFromNet) : info.last_heard;
|
||||||
|
|||||||
@@ -167,6 +167,18 @@ class PhoneAPI
|
|||||||
/// begin a new connection
|
/// begin a new connection
|
||||||
void handleStartConfig();
|
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:
|
private:
|
||||||
void releasePhonePacket();
|
void releasePhonePacket();
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ template <class T> class ProtobufModule : protected SinglePortModule
|
|||||||
const char *getSenderShortName(const meshtastic_MeshPacket &mp)
|
const char *getSenderShortName(const meshtastic_MeshPacket &mp)
|
||||||
{
|
{
|
||||||
auto node = nodeDB->getMeshNode(getFrom(&mp));
|
auto node = nodeDB->getMeshNode(getFrom(&mp));
|
||||||
const char *sender = (node) ? node->user.short_name : "???";
|
const char *sender = (node) ? node->short_name : "???";
|
||||||
return sender;
|
return sender;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -119,11 +119,16 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
|
|||||||
// stop the immediate relayer's retransmissions.
|
// stop the immediate relayer's retransmissions.
|
||||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
|
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 &&
|
} 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)) {
|
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");
|
LOG_INFO("PKI packet from unknown node, send PKI_UNKNOWN_PUBKEY");
|
||||||
sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
|
sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
|
||||||
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
|
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
|
||||||
|
} else {
|
||||||
|
sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(),
|
||||||
|
routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded
|
// 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(),
|
sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(),
|
||||||
|
|||||||
+35
-19
@@ -100,21 +100,20 @@ bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
|
|||||||
// Optimized search for favorite routers with matching last byte
|
// Optimized search for favorite routers with matching last byte
|
||||||
// Check ordering optimized for IoT devices (cheapest checks first)
|
// Check ordering optimized for IoT devices (cheapest checks first)
|
||||||
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
const meshtastic_NodeDetail *node = nodeDB->getMeshNodeByIndex(i);
|
||||||
if (!node)
|
if (!node)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check 1: is_favorite (cheapest - single bool)
|
// Check 1: is_favorite (cheapest - single bool)
|
||||||
if (!node->is_favorite)
|
if (!detailIsFavorite(*node))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check 2: has_user (cheap - single bool)
|
// Check 2: has_user (cheap - single bool)
|
||||||
if (!node->has_user)
|
if (!detailHasFlag(*node, NODEDETAIL_FLAG_HAS_USER))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check 3: role check (moderate cost - multiple comparisons)
|
// Check 3: role check (moderate cost - multiple comparisons)
|
||||||
if (!IS_ONE_OF(node->user.role, meshtastic_Config_DeviceConfig_Role_ROUTER,
|
if (!IS_ONE_OF(node->role, meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
|
||||||
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE)) {
|
|
||||||
continue;
|
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
|
// 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)) {
|
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) {
|
if (node) {
|
||||||
p->channel = node->channel;
|
p->channel = node->channel;
|
||||||
LOG_DEBUG("localSend to channel %d", p->channel);
|
LOG_DEBUG("localSend to channel %d", p->channel);
|
||||||
@@ -409,8 +408,11 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
|||||||
{
|
{
|
||||||
concurrency::LockGuard g(cryptLock);
|
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 &&
|
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);
|
LOG_DEBUG("Node 0x%x not in nodeDB-> Rebroadcast mode KNOWN_ONLY will ignore packet", p->from);
|
||||||
return DecodeState::DECODE_FAILURE;
|
return DecodeState::DECODE_FAILURE;
|
||||||
}
|
}
|
||||||
@@ -427,13 +429,17 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
|||||||
ChannelIndex chIndex = 0;
|
ChannelIndex chIndex = 0;
|
||||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
#if !(MESHTASTIC_EXCLUDE_PKI)
|
||||||
// Attempt PKI decryption first
|
// Attempt PKI decryption first
|
||||||
if (p->channel == 0 && isToUs(p) && p->to > 0 && !isBroadcast(p->to) && nodeDB->getMeshNode(p->from) != nullptr &&
|
if (p->channel == 0 && isToUs(p) && p->to > 0 && !isBroadcast(p->to) && fromDetail != nullptr && toDetail != nullptr &&
|
||||||
nodeDB->getMeshNode(p->from)->user.public_key.size > 0 && nodeDB->getMeshNode(p->to)->user.public_key.size > 0 &&
|
|
||||||
rawSize > MESHTASTIC_PKC_OVERHEAD) {
|
rawSize > MESHTASTIC_PKC_OVERHEAD) {
|
||||||
|
meshtastic_UserLite fromLite = meshtastic_UserLite_init_default;
|
||||||
|
meshtastic_UserLite toLite = meshtastic_UserLite_init_default;
|
||||||
|
fromLite = detailToUserLite(*fromDetail);
|
||||||
|
toLite = detailToUserLite(*toDetail);
|
||||||
|
|
||||||
|
if (fromLite.public_key.size == 32 && toLite.public_key.size == 32) {
|
||||||
LOG_DEBUG("Attempt PKI decryption");
|
LOG_DEBUG("Attempt PKI decryption");
|
||||||
|
|
||||||
if (crypto->decryptCurve25519(p->from, nodeDB->getMeshNode(p->from)->user.public_key, p->id, rawSize, p->encrypted.bytes,
|
if (crypto->decryptCurve25519(p->from, fromLite.public_key, p->id, rawSize, p->encrypted.bytes, bytes)) {
|
||||||
bytes)) {
|
|
||||||
LOG_INFO("PKI Decryption worked!");
|
LOG_INFO("PKI Decryption worked!");
|
||||||
|
|
||||||
meshtastic_Data decodedtmp;
|
meshtastic_Data decodedtmp;
|
||||||
@@ -444,7 +450,7 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
|||||||
decrypted = true;
|
decrypted = true;
|
||||||
LOG_INFO("Packet decrypted using PKI!");
|
LOG_INFO("Packet decrypted using PKI!");
|
||||||
p->pki_encrypted = true;
|
p->pki_encrypted = true;
|
||||||
memcpy(&p->public_key.bytes, nodeDB->getMeshNode(p->from)->user.public_key.bytes, 32);
|
memcpy(&p->public_key.bytes, fromLite.public_key.bytes, 32);
|
||||||
p->public_key.size = 32;
|
p->public_key.size = 32;
|
||||||
p->decoded = decodedtmp;
|
p->decoded = decodedtmp;
|
||||||
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded
|
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded
|
||||||
@@ -456,6 +462,7 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
|||||||
LOG_WARN("PKC decrypt attempted but failed!");
|
LOG_WARN("PKC decrypt attempted but failed!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// assert(p->which_payloadVariant == MeshPacket_encrypted_tag);
|
// assert(p->which_payloadVariant == MeshPacket_encrypted_tag);
|
||||||
@@ -479,6 +486,11 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
|||||||
LOG_ERROR("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)!", p->id);
|
LOG_ERROR("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)!", p->id);
|
||||||
} else if (decodedtmp.portnum == meshtastic_PortNum_UNKNOWN_APP) {
|
} else if (decodedtmp.portnum == meshtastic_PortNum_UNKNOWN_APP) {
|
||||||
LOG_ERROR("Invalid portnum (bad psk?)!");
|
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 {
|
} else {
|
||||||
p->decoded = decodedtmp;
|
p->decoded = decodedtmp;
|
||||||
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded
|
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
|
ChannelIndex chIndex = p->channel; // keep as a local because we are about to change it
|
||||||
|
|
||||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
#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
|
// 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
|
// is not in the local nodedb
|
||||||
// First, only PKC encrypt packets we are originating
|
// 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
|
// Check for valid keys and single node destination
|
||||||
config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr &&
|
config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr &&
|
||||||
// Check for a known public key for the destination
|
// 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
|
// 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_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP &&
|
||||||
p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_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)
|
if (numbytes + MESHTASTIC_HEADER_LENGTH + MESHTASTIC_PKC_OVERHEAD > MAX_LORA_PAYLOAD_LEN)
|
||||||
return meshtastic_Routing_Error_TOO_LARGE;
|
return meshtastic_Routing_Error_TOO_LARGE;
|
||||||
if (p->pki_encrypted && !memfll(p->public_key.bytes, 0, 32) &&
|
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,
|
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;
|
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;
|
numbytes += MESHTASTIC_PKC_OVERHEAD;
|
||||||
p->channel = 0;
|
p->channel = 0;
|
||||||
p->pki_encrypted = true;
|
p->pki_encrypted = true;
|
||||||
@@ -771,8 +787,8 @@ void Router::perhapsHandleReceived(meshtastic_MeshPacket *p)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
meshtastic_NodeInfoLite const *node = nodeDB->getMeshNode(p->from);
|
const meshtastic_NodeDetail *node = nodeDB->getMeshNode(p->from);
|
||||||
if (node != NULL && node->is_ignored) {
|
if (node != NULL && detailIsIgnored(*node)) {
|
||||||
LOG_DEBUG("Ignore msg, 0x%x is ignored", p->from);
|
LOG_DEBUG("Ignore msg, 0x%x is ignored", p->from);
|
||||||
packetPool.release(p);
|
packetPool.release(p);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
#include "TypeConversions.h"
|
#include "TypeConversions.h"
|
||||||
#include "mesh/generated/meshtastic/deviceonly.pb.h"
|
#include "mesh/generated/meshtastic/deviceonly.pb.h"
|
||||||
#include "mesh/generated/meshtastic/mesh.pb.h"
|
#include "mesh/generated/meshtastic/mesh.pb.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfoLite *lite)
|
meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfoLite *lite)
|
||||||
{
|
{
|
||||||
@@ -45,6 +48,196 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo
|
|||||||
return info;
|
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 TypeConversions::ConvertToPositionLite(meshtastic_Position position)
|
||||||
{
|
{
|
||||||
meshtastic_PositionLite lite = meshtastic_PositionLite_init_default;
|
meshtastic_PositionLite lite = meshtastic_PositionLite_init_default;
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ class TypeConversions
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static meshtastic_NodeInfo ConvertToNodeInfo(const meshtastic_NodeInfoLite *lite);
|
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_PositionLite ConvertToPositionLite(meshtastic_Position position);
|
||||||
static meshtastic_Position ConvertToPosition(meshtastic_PositionLite lite);
|
static meshtastic_Position ConvertToPosition(meshtastic_PositionLite lite);
|
||||||
static meshtastic_UserLite ConvertToUserLite(meshtastic_User user);
|
static meshtastic_UserLite ConvertToUserLite(meshtastic_User user);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ PacketAPI *PacketAPI::create(PacketServer *_server)
|
|||||||
PacketAPI::PacketAPI(PacketServer *_server)
|
PacketAPI::PacketAPI(PacketServer *_server)
|
||||||
: concurrency::OSThread("PacketAPI"), isConnected(false), programmingMode(false), server(_server)
|
: concurrency::OSThread("PacketAPI"), isConnected(false), programmingMode(false), server(_server)
|
||||||
{
|
{
|
||||||
|
api_type = TYPE_PACKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t PacketAPI::runOnce()
|
int32_t PacketAPI::runOnce()
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ void deInitApiServer()
|
|||||||
|
|
||||||
WiFiServerAPI::WiFiServerAPI(WiFiClient &_client) : ServerAPI(_client)
|
WiFiServerAPI::WiFiServerAPI(WiFiClient &_client) : ServerAPI(_client)
|
||||||
{
|
{
|
||||||
|
api_type = TYPE_WIFI;
|
||||||
LOG_INFO("Incoming wifi connection");
|
LOG_INFO("Incoming wifi connection");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ void initApiServer(int port)
|
|||||||
ethServerAPI::ethServerAPI(EthernetClient &_client) : ServerAPI(_client)
|
ethServerAPI::ethServerAPI(EthernetClient &_client) : ServerAPI(_client)
|
||||||
{
|
{
|
||||||
LOG_INFO("Incoming ethernet connection");
|
LOG_INFO("Incoming ethernet connection");
|
||||||
|
api_type = TYPE_ETH;
|
||||||
}
|
}
|
||||||
|
|
||||||
ethServerPort::ethServerPort(int port) : APIServerPort(port) {}
|
ethServerPort::ethServerPort(int port) : APIServerPort(port) {}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user