mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-15 15:22:34 +00:00
Compare commits
264 Commits
v2.0.3.09f
...
v2.0.8.090
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
090e1660fe | ||
|
|
92c9b34d4f | ||
|
|
3f50371ff3 | ||
|
|
815f9bfc5f | ||
|
|
86ff23d13c | ||
|
|
201b786f77 | ||
|
|
755c0b7008 | ||
|
|
7396d0f241 | ||
|
|
220859d0aa | ||
|
|
941786669b | ||
|
|
4eb620d47b | ||
|
|
9794995d7a | ||
|
|
055146602a | ||
|
|
86d7860d86 | ||
|
|
0f2d0d1f07 | ||
|
|
ab6a5a5e07 | ||
|
|
44a33ed463 | ||
|
|
fab08b6451 | ||
|
|
d9cd3dd3e1 | ||
|
|
c75ea87f6b | ||
|
|
706ddf6e95 | ||
|
|
aec091e7aa | ||
|
|
cea8393a7f | ||
|
|
8f94463eac | ||
|
|
a0f5e44967 | ||
|
|
feb7181767 | ||
|
|
a0c1e9cdc6 | ||
|
|
7d1b6f63b5 | ||
|
|
ab6b6514cb | ||
|
|
0e6285edf2 | ||
|
|
8b58eaac20 | ||
|
|
8cbf292373 | ||
|
|
80d0b63c3a | ||
|
|
f5120a29ec | ||
|
|
efc3f4c0ee | ||
|
|
bd2bfd6822 | ||
|
|
88c3ab2636 | ||
|
|
6a5dd26907 | ||
|
|
4de557b4db | ||
|
|
af9d4328eb | ||
|
|
8c66940b78 | ||
|
|
72f1416b30 | ||
|
|
05f81922e6 | ||
|
|
72504a5e8b | ||
|
|
e8c034e988 | ||
|
|
aa19718ba4 | ||
|
|
088ab106dd | ||
|
|
110c3f619a | ||
|
|
d1cc503ca8 | ||
|
|
d3b3a4c148 | ||
|
|
46f1cee2a8 | ||
|
|
0386af721d | ||
|
|
de6b752db8 | ||
|
|
92fd5511ec | ||
|
|
59ec7f31ab | ||
|
|
779d2352bd | ||
|
|
0162db12b8 | ||
|
|
91ff7b9032 | ||
|
|
643f99f577 | ||
|
|
152288b4cc | ||
|
|
45b518baf2 | ||
|
|
0c65c73f90 | ||
|
|
0f0dbc3274 | ||
|
|
06d34daeab | ||
|
|
ba1f68d758 | ||
|
|
d4c0977a70 | ||
|
|
1a19d71e95 | ||
|
|
21c10934fc | ||
|
|
13cca91097 | ||
|
|
b335b1c66b | ||
|
|
cc2653bfb5 | ||
|
|
fc5bf5a68f | ||
|
|
63d7338311 | ||
|
|
37f716d27b | ||
|
|
0f2a835359 | ||
|
|
2a84d39e40 | ||
|
|
b14289e976 | ||
|
|
1fef6f0656 | ||
|
|
183ec2124f | ||
|
|
aeb9bfa063 | ||
|
|
b84c7ae49b | ||
|
|
61598c5942 | ||
|
|
a3a24e0216 | ||
|
|
31ec2da0e9 | ||
|
|
27a10b395f | ||
|
|
7570cdbd22 | ||
|
|
c857474116 | ||
|
|
8ff5dacc3c | ||
|
|
f1179d31ba | ||
|
|
abe60b96f1 | ||
|
|
206520f179 | ||
|
|
97fd5cf2ab | ||
|
|
d13a095516 | ||
|
|
4dc7d92cf1 | ||
|
|
e7dbbeb606 | ||
|
|
3e892fc391 | ||
|
|
dfec37dfd0 | ||
|
|
b82ab34f85 | ||
|
|
18a2cfeda4 | ||
|
|
082aa07e7f | ||
|
|
a703ab4418 | ||
|
|
185ceac9df | ||
|
|
7c9cada50e | ||
|
|
a5ba3dd445 | ||
|
|
63838a1632 | ||
|
|
30d7f188e2 | ||
|
|
47a47f1e69 | ||
|
|
daac79f314 | ||
|
|
1864216e78 | ||
|
|
71c0cf9b9a | ||
|
|
ef87ddb798 | ||
|
|
711c748b44 | ||
|
|
6eff09a260 | ||
|
|
c5fe878a6f | ||
|
|
39948c76de | ||
|
|
10f14d27b7 | ||
|
|
5e9d722b7d | ||
|
|
b324c04097 | ||
|
|
84a9d95b1f | ||
|
|
cdd499f147 | ||
|
|
c45a85547e | ||
|
|
8815746006 | ||
|
|
32a1e8ef0d | ||
|
|
0dff4538f3 | ||
|
|
6507683909 | ||
|
|
c71e32970c | ||
|
|
fcf21da843 | ||
|
|
ab464fe038 | ||
|
|
fd546af2a5 | ||
|
|
acfbe202b6 | ||
|
|
29fb283daf | ||
|
|
51ef9b7fbe | ||
|
|
025d2264a2 | ||
|
|
b2284b2097 | ||
|
|
5f8267c956 | ||
|
|
cf783a5bae | ||
|
|
32e5ced814 | ||
|
|
605fadabcf | ||
|
|
e91ace7329 | ||
|
|
c87cd136d4 | ||
|
|
0d7d59609a | ||
|
|
75ed0e5906 | ||
|
|
8e3b500307 | ||
|
|
d6f77bf07e | ||
|
|
65e8209d51 | ||
|
|
aae5247caa | ||
|
|
2fedb6b774 | ||
|
|
02d18d4831 | ||
|
|
b70c2d088d | ||
|
|
b3c396683e | ||
|
|
f08874dd37 | ||
|
|
648054da9b | ||
|
|
70bf7c490c | ||
|
|
adbed5de95 | ||
|
|
fe989f0bff | ||
|
|
679e346bcb | ||
|
|
832439b336 | ||
|
|
71c2af04ec | ||
|
|
0f4261d02f | ||
|
|
23466d8eee | ||
|
|
8edbba2180 | ||
|
|
5417671332 | ||
|
|
35d7e11678 | ||
|
|
7a63ba827b | ||
|
|
71c163a8ee | ||
|
|
5d8e661807 | ||
|
|
24244e8474 | ||
|
|
fb4f9bdc40 | ||
|
|
668c46e0cf | ||
|
|
abf8fdb661 | ||
|
|
a28a04b7a0 | ||
|
|
cd9671650b | ||
|
|
00bc762bf1 | ||
|
|
9a065bce03 | ||
|
|
3b2b0bdc97 | ||
|
|
53cd6bdf15 | ||
|
|
edc97c1c07 | ||
|
|
6a24ef2263 | ||
|
|
50ba523fb4 | ||
|
|
a33325f90f | ||
|
|
a173b7159a | ||
|
|
91295d3772 | ||
|
|
9c1c04a8db | ||
|
|
51d0d0d779 | ||
|
|
9cdf627ae3 | ||
|
|
4b9c482384 | ||
|
|
b1ba807ec9 | ||
|
|
c844f153e1 | ||
|
|
4a2b02347f | ||
|
|
e8a05d1874 | ||
|
|
08c4e3fbd6 | ||
|
|
1c5292ac86 | ||
|
|
aa553ea5d8 | ||
|
|
a00bd59e27 | ||
|
|
f02c6c49ee | ||
|
|
e54e37a600 | ||
|
|
b95103cab0 | ||
|
|
9b43e49116 | ||
|
|
457538c8f6 | ||
|
|
da48f0704b | ||
|
|
cf8d953bba | ||
|
|
5f2b859e38 | ||
|
|
3187b5abda | ||
|
|
ce16b50d5f | ||
|
|
4295720770 | ||
|
|
4392df0676 | ||
|
|
4ec3b025f0 | ||
|
|
f4704181e9 | ||
|
|
0e04bea39e | ||
|
|
b54044fd00 | ||
|
|
08c69c09c8 | ||
|
|
681ea420c1 | ||
|
|
48ea54748f | ||
|
|
4b7627595a | ||
|
|
6299e5483b | ||
|
|
6118a966a6 | ||
|
|
1fac9ee1f2 | ||
|
|
1e06b2d51e | ||
|
|
f3a6ed9d61 | ||
|
|
f71cbb6f6e | ||
|
|
23ea22c741 | ||
|
|
8be65bb0ab | ||
|
|
57e2e75d24 | ||
|
|
29cd7568f5 | ||
|
|
581076a5a1 | ||
|
|
27401bb9b8 | ||
|
|
10837ce549 | ||
|
|
1dcd411d00 | ||
|
|
0533fd9227 | ||
|
|
5ce7ffc888 | ||
|
|
9e914de995 | ||
|
|
0cc653263e | ||
|
|
d2d2f278cf | ||
|
|
eb34a95ab7 | ||
|
|
eb1f6c0de6 | ||
|
|
8de79e8fb6 | ||
|
|
fe00f0c369 | ||
|
|
f9ee8583b0 | ||
|
|
a4d5f8c717 | ||
|
|
35c50f074b | ||
|
|
dcfa226509 | ||
|
|
a9fde30a58 | ||
|
|
7ceb52103e | ||
|
|
8da5d37888 | ||
|
|
95cc328b5c | ||
|
|
990c0119a7 | ||
|
|
144afee29e | ||
|
|
9665c08b59 | ||
|
|
20ee6a509d | ||
|
|
70d44b8838 | ||
|
|
b260c8b058 | ||
|
|
5991b59ba3 | ||
|
|
bc1fed0fb4 | ||
|
|
b23c364fc0 | ||
|
|
28b428c5a0 | ||
|
|
5bfc58ed64 | ||
|
|
e9a34fca7b | ||
|
|
6ce9734ddd | ||
|
|
d42797ffeb | ||
|
|
b5ebfa9cc3 | ||
|
|
79eff42c3c | ||
|
|
7022807fa3 | ||
|
|
50a301899e | ||
|
|
351db5f6ef |
27
.github/ISSUE_TEMPLATE/feature.yml
vendored
Normal file
27
.github/ISSUE_TEMPLATE/feature.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Feature Request
|
||||
description: Request a new feature
|
||||
title: "[Feature Request]: "
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for your request this will not gurantee that we will implement it, but it will be reviewed.
|
||||
- type: dropdown
|
||||
id: soc
|
||||
attributes:
|
||||
label: Platform
|
||||
description: What device platform will support your feature?
|
||||
multiple: true
|
||||
options:
|
||||
- NRF52
|
||||
- ESP32
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: body
|
||||
attributes:
|
||||
label: Description
|
||||
description: Please provide details about your enhancement.
|
||||
validations:
|
||||
required: true
|
||||
41
.github/actions/setup-base/action.yml
vendored
Normal file
41
.github/actions/setup-base/action.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: 'Setup Build Base Composite Action'
|
||||
description: 'Base build actions for Meshtastic Platform IO steps'
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Install cppcheck
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get install -y cppcheck
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- name: Cache python libs
|
||||
uses: actions/cache@v3
|
||||
id: cache-pip # needed in if test
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip
|
||||
|
||||
- name: Upgrade python tools
|
||||
shell: bash
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio adafruit-nrfutil
|
||||
pip install -U meshtastic --pre
|
||||
|
||||
- name: Upgrade platformio
|
||||
shell: bash
|
||||
run: |
|
||||
pio upgrade
|
||||
54
.github/workflows/build_esp32.yml
vendored
Normal file
54
.github/workflows/build_esp32.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: Build ESP32
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
board:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build-esp32:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build base
|
||||
id: base
|
||||
uses: ./.github/actions/setup-base
|
||||
|
||||
- name: Pull web ui
|
||||
uses: dsaltares/fetch-gh-release-asset@master
|
||||
with:
|
||||
repo: "meshtastic/web"
|
||||
file: "build.tar"
|
||||
target: "build.tar"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Unpack web ui
|
||||
run: |
|
||||
tar -xf build.tar -C data/static
|
||||
rm build.tar
|
||||
|
||||
- name: Build ESP32
|
||||
run: bin/build-esp32.sh ${{ inputs.board }}
|
||||
|
||||
- name: Pull OTA Firmware
|
||||
uses: dsaltares/fetch-gh-release-asset@master
|
||||
with:
|
||||
repo: "meshtastic/firmware-ota"
|
||||
file: "firmware.bin"
|
||||
target: "release/bleota.bin"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get release version string
|
||||
shell: bash
|
||||
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
|
||||
path: |
|
||||
release/*.bin
|
||||
release/*.elf
|
||||
33
.github/workflows/build_nrf52.yml
vendored
Normal file
33
.github/workflows/build_nrf52.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Build NRF52
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
board:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build-nrf52:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build base
|
||||
id: base
|
||||
uses: ./.github/actions/setup-base
|
||||
|
||||
- name: Build NRF52
|
||||
run: bin/build-nrf52.sh ${{ inputs.board }}
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
|
||||
path: |
|
||||
release/*.uf2
|
||||
release/*.elf
|
||||
release/*.zip
|
||||
32
.github/workflows/build_rpi2040.yml
vendored
Normal file
32
.github/workflows/build_rpi2040.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Build RPI2040
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
board:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build-rpi2040:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build base
|
||||
id: base
|
||||
uses: ./.github/actions/setup-base
|
||||
|
||||
- name: Build Raspberry Pi 2040
|
||||
run: ./bin/build-rpi2040.sh ${{ inputs.board }}
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
|
||||
path: |
|
||||
release/*.uf2
|
||||
release/*.elf
|
||||
270
.github/workflows/main_matrix.yml
vendored
270
.github/workflows/main_matrix.yml
vendored
@@ -23,52 +23,22 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- board: rak11200
|
||||
- board: tlora-v1
|
||||
- board: tlora-v2-1-1.6
|
||||
- board: tbeam
|
||||
- board: heltec-v2.1
|
||||
- board: meshtastic-diy-v1
|
||||
- board: rak4631
|
||||
- board: t-echo
|
||||
- board: nano-g1
|
||||
- board: station-g1
|
||||
- board: m5stack-coreink
|
||||
- board: tbeam-s3-core
|
||||
# - board: pico
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Install cppcheck
|
||||
run: |
|
||||
sudo apt-get install -y cppcheck
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- name: Cache python libs
|
||||
uses: actions/cache@v3
|
||||
id: cache-pip # needed in if test
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip
|
||||
|
||||
- name: Upgrade python tools and install platformio
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio
|
||||
|
||||
- name: Upgrade platformio
|
||||
run: |
|
||||
pio upgrade
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build base
|
||||
id: base
|
||||
uses: ./.github/actions/setup-base
|
||||
|
||||
- name: Check ${{ matrix.board }}
|
||||
run: bin/check-all.sh ${{ matrix.board }}
|
||||
@@ -97,73 +67,9 @@ jobs:
|
||||
- board: m5stack-core
|
||||
- board: m5stack-coreink
|
||||
- board: tbeam-s3-core
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- name: Cache python libs
|
||||
uses: actions/cache@v3
|
||||
id: cache-pip # needed in if test
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip
|
||||
|
||||
- name: Upgrade python tools
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio adafruit-nrfutil
|
||||
|
||||
- name: Upgrade platformio
|
||||
run: |
|
||||
pio upgrade
|
||||
|
||||
- name: Pull web ui
|
||||
uses: dsaltares/fetch-gh-release-asset@master
|
||||
with:
|
||||
repo: "meshtastic/web"
|
||||
file: "build.tar"
|
||||
target: "build.tar"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Unpack web ui
|
||||
run: |
|
||||
tar -xf build.tar -C data/static
|
||||
rm build.tar
|
||||
|
||||
- name: Build ESP32
|
||||
run: bin/build-esp32.sh ${{ matrix.board }}
|
||||
|
||||
- name: Pull OTA Firmware
|
||||
uses: dsaltares/fetch-gh-release-asset@master
|
||||
with:
|
||||
repo: "meshtastic/firmware-ota"
|
||||
file: "firmware.bin"
|
||||
target: "release/bleota.bin"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: firmware-${{ matrix.board }}-${{ steps.version.outputs.version }}.zip
|
||||
path: |
|
||||
release/*.bin
|
||||
release/*.elf
|
||||
retention-days: 30
|
||||
uses: ./.github/workflows/build_esp32.yml
|
||||
with:
|
||||
board: ${{ matrix.board }}
|
||||
|
||||
build-nrf52:
|
||||
strategy:
|
||||
@@ -176,53 +82,9 @@ jobs:
|
||||
- board: t-echo
|
||||
- board: pca10059_diy_eink
|
||||
- board: feather_diy
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- name: Cache python libs
|
||||
uses: actions/cache@v3
|
||||
id: cache-pip # needed in if test
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip
|
||||
|
||||
- name: Upgrade python tools
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio adafruit-nrfutil
|
||||
|
||||
- name: Upgrade platformio
|
||||
run: |
|
||||
pio upgrade
|
||||
|
||||
- name: Build NRF52
|
||||
run: bin/build-nrf52.sh ${{ matrix.board }}
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: firmware-${{ matrix.board }}-${{ steps.version.outputs.version }}.zip
|
||||
path: |
|
||||
release/*.uf2
|
||||
release/*.elf
|
||||
release/*.zip
|
||||
retention-days: 30
|
||||
uses: ./.github/workflows/build_nrf52.yml
|
||||
with:
|
||||
board: ${{ matrix.board }}
|
||||
|
||||
build-rpi2040:
|
||||
strategy:
|
||||
@@ -231,84 +93,17 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- board: pico
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- name: Cache python libs
|
||||
uses: actions/cache@v3
|
||||
id: cache-pip # needed in if test
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip
|
||||
|
||||
- name: Upgrade python tools
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio adafruit-nrfutil
|
||||
|
||||
- name: Upgrade platformio
|
||||
run: |
|
||||
pio upgrade
|
||||
|
||||
- name: Build Raspberry Pi 2040
|
||||
run: ./bin/build-rpi2040.sh ${{ matrix.board }}
|
||||
|
||||
- name: Get release version string
|
||||
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
|
||||
id: version
|
||||
|
||||
- name: Store binaries as an artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: firmware-${{ matrix.board }}-${{ steps.version.outputs.version }}.zip
|
||||
path: |
|
||||
release/*.uf2
|
||||
release/*.elf
|
||||
retention-days: 30
|
||||
uses: ./.github/workflows/build_rpi2040.yml
|
||||
with:
|
||||
board: ${{ matrix.board }}
|
||||
|
||||
build-native:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- name: Cache python libs
|
||||
uses: actions/cache@v3
|
||||
id: cache-pip # needed in if test
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip
|
||||
|
||||
- name: Upgrade python tools
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -U platformio adafruit-nrfutil
|
||||
pip install -U meshtastic --pre
|
||||
|
||||
- name: Upgrade platformio
|
||||
run: |
|
||||
pio upgrade
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build base
|
||||
id: base
|
||||
uses: ./.github/actions/setup-base
|
||||
|
||||
# We now run integration test before other build steps (to quickly see runtime failures)
|
||||
- name: Build for native
|
||||
@@ -332,11 +127,29 @@ jobs:
|
||||
with:
|
||||
name: firmware-native-${{ steps.version.outputs.version }}.zip
|
||||
path: |
|
||||
release/meshtasticd_linux_amd64
|
||||
release/device-*.sh
|
||||
release/device-*.bat
|
||||
retention-days: 30
|
||||
|
||||
- name: Docker login
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: meshtastic
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Docker setup
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Docker build and push
|
||||
if: ${{ github.event_name == 'workflow_dispatch' }}
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: meshtastic/device-simulator:latest
|
||||
|
||||
after-checks:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [check]
|
||||
@@ -366,7 +179,7 @@ jobs:
|
||||
id: version
|
||||
|
||||
- name: Move files up
|
||||
run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./**/meshtasticd_linux_amd64 ./*native*/*device-*.sh ./*native*/*device-*.bat
|
||||
run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat
|
||||
|
||||
- name: Repackage in single firmware zip
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -376,7 +189,6 @@ jobs:
|
||||
./*.bin
|
||||
./*.uf2
|
||||
./firmware-*-ota.zip
|
||||
./meshtasticd_linux_amd64
|
||||
./device-*.sh
|
||||
./device-*.bat
|
||||
retention-days: 90
|
||||
@@ -396,7 +208,7 @@ jobs:
|
||||
chmod +x ./output/device-update.sh
|
||||
|
||||
- name: Zip firmware
|
||||
run: zip -j -r ./firmware-${{ steps.version.outputs.version }}.zip ./output
|
||||
run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output
|
||||
|
||||
- name: Repackage in single elfs zip
|
||||
uses: actions/upload-artifact@v3
|
||||
@@ -445,7 +257,7 @@ jobs:
|
||||
chmod +x ./output/device-update.sh
|
||||
|
||||
- name: Zip firmware
|
||||
run: zip -j -r ./firmware-${{ steps.version.outputs.version }}.zip ./output
|
||||
run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
@@ -453,7 +265,7 @@ jobs:
|
||||
path: ./elfs
|
||||
|
||||
- name: Zip Elfs
|
||||
run: zip -j -r ./debug-elfs-${{ steps.version.outputs.version }}.zip ./elfs
|
||||
run: zip -j -9 -r ./debug-elfs-${{ steps.version.outputs.version }}.zip ./elfs
|
||||
|
||||
# For diagnostics
|
||||
- name: Show artifacts
|
||||
|
||||
40
.github/workflows/sec_sast_flawfinder.yml
vendored
Normal file
40
.github/workflows/sec_sast_flawfinder.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: Flawfinder Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, develop]
|
||||
paths-ignore:
|
||||
- "**.md"
|
||||
- "version.properties"
|
||||
|
||||
jobs:
|
||||
flawfinder:
|
||||
runs-on: ubuntu-latest
|
||||
name: Flawfinder
|
||||
|
||||
steps:
|
||||
# step 1
|
||||
- name: clone application source code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# step 2
|
||||
- name: flawfinder_scan
|
||||
uses: david-a-wheeler/flawfinder@2.0.19
|
||||
with:
|
||||
arguments: '--sarif ./'
|
||||
output: 'flawfinder_report.sarif'
|
||||
|
||||
# step 3
|
||||
- name: save report as pipeline artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: flawfinder_report.sarif
|
||||
path: flawfinder_report.sarif
|
||||
|
||||
# step 4
|
||||
- name: publish code scanning alerts
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: flawfinder_report.sarif
|
||||
category: flawfinder
|
||||
44
.github/workflows/sec_sast_semgrep_cron.yml
vendored
Normal file
44
.github/workflows/sec_sast_semgrep_cron.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: Semgrep Full Scan
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
branches:
|
||||
- master
|
||||
schedule:
|
||||
- cron: '0 1 * * 6'
|
||||
|
||||
jobs:
|
||||
|
||||
semgrep-full:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: returntocorp/semgrep
|
||||
|
||||
steps:
|
||||
|
||||
# step 1
|
||||
- name: clone application source code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# step 2
|
||||
- name: full scan
|
||||
run: |
|
||||
semgrep \
|
||||
--sarif --output report.sarif \
|
||||
--metrics=off \
|
||||
--config="p/default"
|
||||
|
||||
# step 3
|
||||
- name: save report as pipeline artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: report.sarif
|
||||
path: report.sarif
|
||||
|
||||
# step 4
|
||||
- name: publish code scanning alerts
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
with:
|
||||
sarif_file: report.sarif
|
||||
category: semgrep
|
||||
28
.github/workflows/sec_sast_semgrep_pull.yml
vendored
Normal file
28
.github/workflows/sec_sast_semgrep_pull.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
name: Semgrep Differential Scan
|
||||
on:
|
||||
pull_request
|
||||
|
||||
jobs:
|
||||
|
||||
semgrep-diff:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: returntocorp/semgrep
|
||||
|
||||
steps:
|
||||
|
||||
# step 1
|
||||
- name: clone application source code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# step 2
|
||||
- name: differential scan
|
||||
run: |
|
||||
semgrep scan \
|
||||
--error \
|
||||
--metrics=off \
|
||||
--baseline-commit ${{ github.event.pull_request.base.sha }} \
|
||||
--config="p/default"
|
||||
6
.github/workflows/update_protobufs.yml
vendored
6
.github/workflows/update_protobufs.yml
vendored
@@ -17,9 +17,9 @@ jobs:
|
||||
|
||||
- name: Download nanopb
|
||||
run: |
|
||||
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.6-linux-x86.tar.gz
|
||||
tar xvzf nanopb-0.4.6-linux-x86.tar.gz
|
||||
mv nanopb-0.4.6-linux-x86 nanopb-0.4.6
|
||||
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.7-linux-x86.tar.gz
|
||||
tar xvzf nanopb-0.4.7-linux-x86.tar.gz
|
||||
mv nanopb-0.4.7-linux-x86 nanopb-0.4.7
|
||||
|
||||
- name: Re-generate protocol buffers
|
||||
run: |
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -29,3 +29,4 @@ __pycache__
|
||||
|
||||
venv/
|
||||
release/
|
||||
.vscode/extensions.json
|
||||
|
||||
1
.gitmodules
vendored
1
.gitmodules
vendored
@@ -1,4 +1,3 @@
|
||||
[submodule "protobufs"]
|
||||
path = protobufs
|
||||
url = https://github.com/meshtastic/protobufs.git
|
||||
branch = develop
|
||||
|
||||
2
.semgrepignore
Normal file
2
.semgrepignore
Normal file
@@ -0,0 +1,2 @@
|
||||
.github/workflows/main_matrix.yml
|
||||
src/mesh/compression/unishox2.c
|
||||
50
Dockerfile
50
Dockerfile
@@ -1,15 +1,41 @@
|
||||
FROM debian:bullseye-slim AS builder
|
||||
RUN apt-get update
|
||||
RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install wget python3 g++ zip python3-venv git vim
|
||||
RUN wget https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py -O get-platformio.py; chmod +x get-platformio.py
|
||||
RUN python3 get-platformio.py
|
||||
RUN git clone https://github.com/meshtastic/firmware --recurse-submodules
|
||||
RUN cd firmware
|
||||
RUN chmod +x ./firmware/bin/build-native.sh
|
||||
RUN . ~/.platformio/penv/bin/activate; cd firmware; sh ./bin/build-native.sh
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
ENV TZ=Etc/UTC
|
||||
|
||||
# http://bugs.python.org/issue19846
|
||||
# > At the moment, setting "LANG=C" on a Linux system *fundamentally breaks Python 3*, and that's not OK.
|
||||
ENV LANG C.UTF-8
|
||||
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
# Install build deps
|
||||
USER root
|
||||
RUN apt-get update && \
|
||||
apt-get -y install wget python3 g++ zip python3-venv git vim ca-certificates
|
||||
|
||||
# create a non-priveleged user & group
|
||||
RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh
|
||||
|
||||
USER mesh
|
||||
RUN wget https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py -qO /tmp/get-platformio.py && \
|
||||
chmod +x /tmp/get-platformio.py && \
|
||||
python3 /tmp/get-platformio.py && \
|
||||
git clone https://github.com/meshtastic/firmware --recurse-submodules /tmp/firmware && \
|
||||
cd /tmp/firmware && \
|
||||
chmod +x /tmp/firmware/bin/build-native.sh && \
|
||||
source ~/.platformio/penv/bin/activate && \
|
||||
./bin/build-native.sh
|
||||
|
||||
FROM frolvlad/alpine-glibc
|
||||
WORKDIR /root/
|
||||
COPY --from=builder /firmware/release/meshtasticd_linux_amd64 ./
|
||||
RUN apk --update add --no-cache g++
|
||||
CMD sh -cx "./meshtasticd_linux_amd64 --hwid '$RANDOM'"
|
||||
|
||||
RUN apk --update add --no-cache g++ shadow && \
|
||||
groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh
|
||||
|
||||
COPY --from=builder /tmp/firmware/release/meshtasticd_linux_amd64 /home/mesh/
|
||||
|
||||
USER mesh
|
||||
WORKDIR /home/mesh
|
||||
CMD sh -cx "./meshtasticd_linux_amd64 --hwid '$RANDOM'"
|
||||
|
||||
HEALTHCHECK NONE
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
This repository contains the device firmware for the Meshtastic project.
|
||||
|
||||
**[Building Instructions](https://meshtastic.org/docs/developers/Firmware/build)**
|
||||
**[Building Instructions](https://meshtastic.org/docs/development/firmware/build)**
|
||||
**[Flashing Instructions](https://meshtastic.org/docs/getting-started/flashing-firmware/)**
|
||||
|
||||
## Stats
|
||||
|
||||
@@ -26,14 +26,16 @@ build_flags =
|
||||
-DCONFIG_NIMBLE_CPP_LOG_LEVEL=2
|
||||
-DCONFIG_BT_NIMBLE_MAX_CCCDS=20
|
||||
-DESP_OPENSSL_SUPPRESS_LEGACY_WARNING
|
||||
-DDEBUG_HEAP
|
||||
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
${networking_base.lib_deps}
|
||||
${environmental_base.lib_deps}
|
||||
https://github.com/meshtastic/esp32_https_server.git#657509856ce97e9dddeffb89a559f544faefd5cd
|
||||
https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2
|
||||
h2zero/NimBLE-Arduino@^1.4.0
|
||||
https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6
|
||||
https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6
|
||||
caveman99/ESP32 Codec2@^1.0.1
|
||||
|
||||
lib_ignore =
|
||||
segger_rtt
|
||||
|
||||
47
arch/esp32/esp32s2.ini
Normal file
47
arch/esp32/esp32s2.ini
Normal file
@@ -0,0 +1,47 @@
|
||||
[esp32s2_base]
|
||||
extends = arduino_base
|
||||
platform = platformio/espressif32@^5.2.0
|
||||
build_src_filter =
|
||||
${arduino_base.build_src_filter} -<platform/nrf52/> -<platform/stm32wl> -<platform/rp2040> -<mesh/eth/> -<nimble/>
|
||||
upload_speed = 961200
|
||||
monitor_speed = 115200
|
||||
debug_init_break = tbreak setup
|
||||
monitor_filters = esp32_exception_decoder
|
||||
board_build.filesystem = littlefs
|
||||
|
||||
# Remove -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL for low level BLE logging.
|
||||
# See library directory for BLE logging possible values: .pio/libdeps/tbeam/NimBLE-Arduino/src/log_common/log_common.h
|
||||
# This overrides the BLE logging default of LOG_LEVEL_INFO (1) from: .pio/libdeps/tbeam/NimBLE-Arduino/src/esp_nimble_cfg.h
|
||||
build_flags =
|
||||
${arduino_base.build_flags}
|
||||
-Wall
|
||||
-Wextra
|
||||
-Isrc/platform/esp32
|
||||
-std=c++11
|
||||
-DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG
|
||||
-DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG
|
||||
-DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL
|
||||
-DAXP_DEBUG_PORT=Serial
|
||||
-DCONFIG_BT_NIMBLE_ENABLED
|
||||
-DCONFIG_NIMBLE_CPP_LOG_LEVEL=2
|
||||
-DCONFIG_BT_NIMBLE_MAX_CCCDS=20
|
||||
-DESP_OPENSSL_SUPPRESS_LEGACY_WARNING
|
||||
-DHAS_BLUETOOTH=0
|
||||
-DDEBUG_HEAP
|
||||
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
${networking_base.lib_deps}
|
||||
${environmental_base.lib_deps}
|
||||
https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2
|
||||
https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6
|
||||
caveman99/ESP32 Codec2@^1.0.1
|
||||
|
||||
lib_ignore =
|
||||
segger_rtt
|
||||
ESP32 BLE Arduino
|
||||
|
||||
; customize the partition table
|
||||
; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
|
||||
board_build.partitions = partition-table.csv
|
||||
|
||||
@@ -23,17 +23,19 @@ build_flags =
|
||||
-DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL
|
||||
-DAXP_DEBUG_PORT=Serial
|
||||
-DCONFIG_BT_NIMBLE_ENABLED
|
||||
-DCONFIG_NIMBLE_CPP_LOG_LEVEL=2
|
||||
-DCONFIG_NIMBLE_CPP_LOG_LEVEL=2
|
||||
-DCONFIG_BT_NIMBLE_MAX_CCCDS=20
|
||||
-DESP_OPENSSL_SUPPRESS_LEGACY_WARNING
|
||||
-DESP_OPENSSL_SUPPRESS_LEGACY_WARNING
|
||||
-DDEBUG_HEAP
|
||||
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
${networking_base.lib_deps}
|
||||
${environmental_base.lib_deps}
|
||||
https://github.com/meshtastic/esp32_https_server.git#657509856ce97e9dddeffb89a559f544faefd5cd
|
||||
https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2
|
||||
h2zero/NimBLE-Arduino@^1.4.0
|
||||
https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6
|
||||
caveman99/ESP32 Codec2@^1.0.1
|
||||
|
||||
lib_ignore =
|
||||
segger_rtt
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
; The Portduino based sim environment on top of any host OS, all hardware will be simulated
|
||||
[portduino_base]
|
||||
platform = https://github.com/meshtastic/platform-native.git#096b3c3e9c5c8e19d4c3b6cd803fffef2a9be4c5
|
||||
framework = arduino
|
||||
build_src_filter =
|
||||
${env.build_src_filter}
|
||||
-<platform/esp32/>
|
||||
@@ -16,5 +18,4 @@ lib_deps =
|
||||
${env.lib_deps}
|
||||
${networking_base.lib_deps}
|
||||
rweather/Crypto@^0.4.0
|
||||
https://github.com/meshtastic/RadioLib.git#5582ac30578ff3f53f20630a00b2a8a4b8f92c74
|
||||
build_flags = ${arduino_base.build_flags} -Isrc/platform/portduino
|
||||
|
||||
@@ -10,9 +10,6 @@ OUTDIR=release/
|
||||
rm -f $OUTDIR/firmware*
|
||||
rm -r $OUTDIR/* || true
|
||||
|
||||
# Make sure our submodules are current
|
||||
git submodule update
|
||||
|
||||
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
|
||||
platformio pkg update
|
||||
|
||||
@@ -32,6 +29,10 @@ echo "Copying ESP32 bin file"
|
||||
SRCBIN=.pio/build/$1/firmware.factory.bin
|
||||
cp $SRCBIN $OUTDIR/$basename.bin
|
||||
|
||||
echo "Copying ESP32 update bin file"
|
||||
SRCBIN=.pio/build/$1/firmware.bin
|
||||
cp $SRCBIN $OUTDIR/$basename-update.bin
|
||||
|
||||
echo "Building Filesystem for ESP32 targets"
|
||||
pio run --environment tbeam -t buildfs
|
||||
cp .pio/build/tbeam/littlefs.bin $OUTDIR/littlefs-$VERSION.bin
|
||||
|
||||
@@ -12,9 +12,6 @@ rm -f $OUTDIR/firmware*
|
||||
mkdir -p $OUTDIR/
|
||||
rm -r $OUTDIR/* || true
|
||||
|
||||
# Make sure our submodules are current
|
||||
git submodule update
|
||||
|
||||
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
|
||||
platformio pkg update
|
||||
|
||||
|
||||
@@ -10,9 +10,6 @@ OUTDIR=release/
|
||||
rm -f $OUTDIR/firmware*
|
||||
rm -r $OUTDIR/* || true
|
||||
|
||||
# Make sure our submodules are current
|
||||
git submodule update
|
||||
|
||||
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
|
||||
platformio pkg update
|
||||
|
||||
|
||||
@@ -10,9 +10,6 @@ OUTDIR=release/
|
||||
rm -f $OUTDIR/firmware*
|
||||
rm -r $OUTDIR/* || true
|
||||
|
||||
# Make sure our submodules are current
|
||||
git submodule update
|
||||
|
||||
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
|
||||
platformio pkg update
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ IF "__%FILENAME%__" == "____" (
|
||||
echo "Missing FILENAME"
|
||||
goto HELP
|
||||
)
|
||||
IF EXIST %FILENAME% (
|
||||
IF EXIST %FILENAME% IF x%FILENAME:update=%==x%FILENAME% (
|
||||
echo Trying to flash update %FILENAME%, but first erasing and writing system information"
|
||||
%PYTHON% -m esptool --baud 115200 erase_flash
|
||||
%PYTHON% -m esptool --baud 115200 write_flash 0x00 %FILENAME%
|
||||
@@ -37,6 +37,9 @@ IF EXIST %FILENAME% (
|
||||
) else (
|
||||
echo "Invalid file: %FILENAME%"
|
||||
goto HELP
|
||||
) else (
|
||||
echo "Invalid file: %FILENAME%"
|
||||
goto HELP
|
||||
)
|
||||
|
||||
:EOF
|
||||
|
||||
@@ -14,6 +14,7 @@ Flash image file to device, but first erasing and writing system information"
|
||||
-p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerous).
|
||||
-P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: "$PYTHON")
|
||||
-f FILENAME The .bin file to flash. Custom to your device type and region.
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -44,7 +45,7 @@ shift "$((OPTIND-1))"
|
||||
shift
|
||||
}
|
||||
|
||||
if [ -f "${FILENAME}" ]; then
|
||||
if [ -f "${FILENAME}" ] && [ ! -z "${FILENAME##*"update"*}" ]; then
|
||||
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
|
||||
"$PYTHON" -m esptool erase_flash
|
||||
"$PYTHON" -m esptool write_flash 0x00 ${FILENAME}
|
||||
@@ -52,8 +53,8 @@ if [ -f "${FILENAME}" ]; then
|
||||
"$PYTHON" -m esptool write_flash 0x300000 littlefs-*.bin
|
||||
|
||||
else
|
||||
echo "Invalid file: ${FILENAME}"
|
||||
show_help
|
||||
echo "Invalid file: ${FILENAME}"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -10,7 +10,7 @@ echo.
|
||||
echo -h Display this help and exit
|
||||
echo -p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerrous).
|
||||
echo -P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: %PYTHON%)
|
||||
echo -f FILENAME The .bin file to flash. Custom to your device type and region.
|
||||
echo -f FILENAME The *update.bin file to flash. Custom to your device type.
|
||||
goto EOF
|
||||
|
||||
:GETOPTS
|
||||
@@ -26,9 +26,12 @@ IF "__%FILENAME%__" == "____" (
|
||||
echo "Missing FILENAME"
|
||||
goto HELP
|
||||
)
|
||||
IF EXIST %FILENAME% (
|
||||
IF EXIST %FILENAME% IF NOT x%FILENAME:update=%==x%FILENAME% (
|
||||
echo Trying to flash update %FILENAME%
|
||||
%PYTHON% -m esptool --baud 115200 write_flash 0x00 %FILENAME%
|
||||
%PYTHON% -m esptool --baud 115200 write_flash 0x10000 %FILENAME%
|
||||
) else (
|
||||
echo "Invalid file: %FILENAME%"
|
||||
goto HELP
|
||||
) else (
|
||||
echo "Invalid file: %FILENAME%"
|
||||
goto HELP
|
||||
|
||||
@@ -11,7 +11,8 @@ Flash image file to device, leave existing system intact."
|
||||
-h Display this help and exit
|
||||
-p ESPTOOL_PORT Set the environment variable for ESPTOOL_PORT. If not set, ESPTOOL iterates all ports (Dangerrous).
|
||||
-P PYTHON Specify alternate python interpreter to use to invoke esptool. (Default: "$PYTHON")
|
||||
-f FILENAME The .bin file to flash. Custom to your device type and region.
|
||||
-f FILENAME The *update.bin file to flash. Custom to your device type.
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -42,12 +43,12 @@ shift "$((OPTIND-1))"
|
||||
shift
|
||||
}
|
||||
|
||||
if [ -f "${FILENAME}" ]; then
|
||||
echo "Trying to flash update ${FILENAME}."
|
||||
$PYTHON -m esptool --baud 115200 write_flash 0x00 ${FILENAME}
|
||||
if [ -f "${FILENAME}" ] && [ -z "${FILENAME##*"update"*}" ]; then
|
||||
printf "Trying to flash update ${FILENAME}"
|
||||
$PYTHON -m esptool --baud 115200 write_flash 0x10000 ${FILENAME}
|
||||
else
|
||||
echo "Invalid file: ${FILENAME}"
|
||||
show_help
|
||||
echo "Invalid file: ${FILENAME}"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1 +1 @@
|
||||
cd protobufs && ..\nanopb-0.4.6\generator-bin\protoc.exe --nanopb_out=-v:..\src\mesh\generated -I=..\protobufs *.proto
|
||||
cd protobufs && ..\nanopb-0.4.7\generator-bin\protoc.exe --nanopb_out=-v:..\src\mesh\generated -I=..\protobufs *.proto
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
set -e
|
||||
|
||||
echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.6 to be located in the"
|
||||
echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.7 to be located in the"
|
||||
echo "firmware root directory if the following step fails, you should download the correct"
|
||||
echo "prebuilt binaries for your computer into nanopb-0.4.6"
|
||||
echo "prebuilt binaries for your computer into nanopb-0.4.7"
|
||||
|
||||
# the nanopb tool seems to require that the .options file be in the current directory!
|
||||
cd protobufs
|
||||
../nanopb-0.4.6/generator-bin/protoc --nanopb_out=-v:../src/mesh/generated -I=../protobufs *.proto
|
||||
../nanopb-0.4.7/generator-bin/protoc --nanopb_out=-v:../src/mesh/generated -I=../protobufs *.proto
|
||||
|
||||
#echo "Regenerating protobuf documentation - if you see an error message"
|
||||
#echo "you can ignore it unless doing a new protobuf release to github."
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino":{
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld"
|
||||
},
|
||||
"core": "esp32",
|
||||
@@ -8,9 +8,7 @@
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DLILYGO_TBEAM_S3_CORE",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_DFU_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MSC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
@@ -45,4 +43,4 @@
|
||||
},
|
||||
"url": "http://www.lilygo.cn/",
|
||||
"vendor": "LilyGo"
|
||||
}
|
||||
}
|
||||
@@ -37,12 +37,24 @@ extra_scripts = bin/platformio-custom.py
|
||||
; note: TINYGPS_OPTION_NO_CUSTOM_FIELDS is VERY important. We don't use custom fields and somewhere in that pile
|
||||
; of code is a heap corruption bug!
|
||||
; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc
|
||||
; The Radiolib stuff will speed up building considerably. Exclud all the stuff we dont need.
|
||||
build_flags = -Wno-missing-field-initializers
|
||||
-Wno-format
|
||||
-Isrc -Isrc/mesh -Isrc/gps -Isrc/buzz -Wl,-Map,.pio/build/output.map
|
||||
-DUSE_THREAD_NAMES
|
||||
-DTINYGPS_OPTION_NO_CUSTOM_FIELDS
|
||||
-DPB_ENABLE_MALLOC=1
|
||||
-DRADIOLIB_EXCLUDE_CC1101
|
||||
-DRADIOLIB_EXCLUDE_NRF24
|
||||
-DRADIOLIB_EXCLUDE_RF69
|
||||
-DRADIOLIB_EXCLUDE_SX1231
|
||||
-DRADIOLIB_EXCLUDE_SI443X
|
||||
-DRADIOLIB_EXCLUDE_RFM2X
|
||||
-DRADIOLIB_EXCLUDE_AFSK
|
||||
-DRADIOLIB_EXCLUDE_HELLSCHREIBER
|
||||
-DRADIOLIB_EXCLUDE_MORSE
|
||||
-DRADIOLIB_EXCLUDE_RTTY
|
||||
-DRADIOLIB_EXCLUDE_SSTV
|
||||
|
||||
monitor_speed = 115200
|
||||
|
||||
@@ -54,6 +66,7 @@ lib_deps =
|
||||
https://github.com/meshtastic/ArduinoThread.git#72921ac222eed6f526ba1682023cee290d9aa1b3
|
||||
nanopb/Nanopb@^0.4.6
|
||||
erriez/ErriezCRC32@^1.0.1
|
||||
jgromes/RadioLib@^5.5.0
|
||||
|
||||
; Used for the code analysis in PIO Home / Inspect
|
||||
check_tool = cppcheck
|
||||
@@ -61,20 +74,17 @@ check_skip_packages = yes
|
||||
check_flags =
|
||||
-DAPP_VERSION=1.0.0
|
||||
--suppressions-list=suppressions.txt
|
||||
--inline-suppr
|
||||
|
||||
; Common settings for conventional (non Portduino) Arduino targets
|
||||
[arduino_base]
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
; Portduino is using meshtastic fork for now
|
||||
jgromes/RadioLib@5.4.1
|
||||
mprograms/QMC5883LCompass@^1.1.1
|
||||
https://github.com/meshtastic/SparkFun_ATECCX08a_Arduino_Library.git#52b5282639d08a8cbd4b748363089eed6102dc76
|
||||
|
||||
build_flags = ${env.build_flags} -Os
|
||||
-DRADIOLIB_SPI_PARANOID=0
|
||||
# -DRADIOLIB_GODMODE
|
||||
build_flags = ${env.build_flags} -Os -DRADIOLIB_SPI_PARANOID=0
|
||||
build_src_filter = ${env.build_src_filter} -<platform/portduino/>
|
||||
|
||||
; Common libs for communicating over TCP/IP networks such as MQTT
|
||||
@@ -82,7 +92,6 @@ build_src_filter = ${env.build_src_filter} -<platform/portduino/>
|
||||
lib_deps =
|
||||
knolleary/PubSubClient@^2.8
|
||||
arduino-libraries/NTPClient@^3.1.0
|
||||
meshtastic/json11@^1.0.2
|
||||
|
||||
; Common libs for environmental measurements in telemetry module
|
||||
; (not included in native / portduino)
|
||||
|
||||
Submodule protobufs updated: da9bba9c5d...0f2a3304ee
@@ -51,6 +51,7 @@ class ButtonThread : public concurrency::OSThread
|
||||
pinMode(BUTTON_PIN, INPUT_PULLUP_SENSE);
|
||||
#endif
|
||||
userButton.attachClick(userButtonPressed);
|
||||
userButton.setClickTicks(300);
|
||||
userButton.attachDuringLongPress(userButtonPressedLong);
|
||||
userButton.attachDoubleClick(userButtonDoublePressed);
|
||||
userButton.attachMultiClick(userButtonMultiPressed);
|
||||
@@ -159,9 +160,21 @@ class ButtonThread : public concurrency::OSThread
|
||||
|
||||
static void userButtonDoublePressed()
|
||||
{
|
||||
#if defined(USE_EINK) && defined(PIN_EINK_EN)
|
||||
#if defined(USE_EINK) && defined(PIN_EINK_EN)
|
||||
digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
|
||||
#endif
|
||||
#endif
|
||||
#if defined(GPS_POWER_TOGGLE)
|
||||
if(config.position.gps_enabled)
|
||||
{
|
||||
DEBUG_MSG("Flag set to false for gps power\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_MSG("Flag set to true to restore power\n");
|
||||
}
|
||||
config.position.gps_enabled = !(config.position.gps_enabled);
|
||||
doGPSpowersave(config.position.gps_enabled);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void userButtonMultiPressed()
|
||||
|
||||
@@ -20,32 +20,19 @@ class GPSStatus : public Status
|
||||
bool hasLock = false; // default to false, until we complete our first read
|
||||
bool isConnected = false; // Do we have a GPS we are talking to
|
||||
|
||||
bool isPowerSaving = false; //Are we in power saving state
|
||||
|
||||
Position p = Position_init_default;
|
||||
|
||||
public:
|
||||
GPSStatus() { statusType = STATUS_TYPE_GPS; }
|
||||
|
||||
// // proposed for deprecation
|
||||
// GPSStatus(bool hasLock, bool isConnected, int32_t latitude, int32_t longitude, int32_t altitude, uint32_t dop,
|
||||
// uint32_t heading, uint32_t numSatellites)
|
||||
// : Status()
|
||||
// {
|
||||
// this->hasLock = hasLock;
|
||||
// this->isConnected = isConnected;
|
||||
|
||||
// this->p.latitude_i = latitude;
|
||||
// this->p.longitude_i = longitude;
|
||||
// this->p.altitude = altitude;
|
||||
// this->p.PDOP = dop;
|
||||
// this->p.ground_track = heading;
|
||||
// this->p.sats_in_view = numSatellites;
|
||||
// }
|
||||
|
||||
// preferred method
|
||||
GPSStatus(bool hasLock, bool isConnected, const Position &pos) : Status()
|
||||
GPSStatus(bool hasLock, bool isConnected, bool isPowerSaving, const Position &pos) : Status()
|
||||
{
|
||||
this->hasLock = hasLock;
|
||||
this->isConnected = isConnected;
|
||||
this->isPowerSaving = isPowerSaving;
|
||||
|
||||
// all-in-one struct copy
|
||||
this->p = pos;
|
||||
@@ -60,6 +47,8 @@ class GPSStatus : public Status
|
||||
|
||||
bool getIsConnected() const { return isConnected; }
|
||||
|
||||
bool getIsPowerSaving() const { return isPowerSaving;}
|
||||
|
||||
int32_t getLatitude() const
|
||||
{
|
||||
if (config.position.fixed_position) {
|
||||
@@ -110,7 +99,7 @@ class GPSStatus : public Status
|
||||
#ifdef GPS_EXTRAVERBOSE
|
||||
DEBUG_MSG("GPSStatus.match() new pos@%x to old pos@%x\n", newStatus->p.pos_timestamp, p.pos_timestamp);
|
||||
#endif
|
||||
return (newStatus->hasLock != hasLock || newStatus->isConnected != isConnected ||
|
||||
return (newStatus->hasLock != hasLock || newStatus->isConnected != isConnected || newStatus->isPowerSaving !=isPowerSaving ||
|
||||
newStatus->p.latitude_i != p.latitude_i || newStatus->p.longitude_i != p.longitude_i ||
|
||||
newStatus->p.altitude != p.altitude || newStatus->p.altitude_hae != p.altitude_hae ||
|
||||
newStatus->p.PDOP != p.PDOP || newStatus->p.ground_track != p.ground_track ||
|
||||
|
||||
@@ -102,6 +102,10 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
#define ADC_MULTIPLIER 2.0
|
||||
#endif
|
||||
|
||||
#ifndef BATTERY_SENSE_SAMPLES
|
||||
#define BATTERY_SENSE_SAMPLES 30
|
||||
#endif
|
||||
|
||||
#ifdef BATTERY_PIN
|
||||
// Override variant or default ADC_MULTIPLIER if we have the override pref
|
||||
float operativeAdcMultiplier = config.power.adc_multiplier_override > 0
|
||||
@@ -112,16 +116,12 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
||||
if (millis() - last_read_time_ms > min_read_interval) {
|
||||
last_read_time_ms = millis();
|
||||
|
||||
#ifdef BATTERY_SENSE_SAMPLES
|
||||
//Set the number of samples, it has an effect of increasing sensitivity, especially in complex electromagnetic environment.
|
||||
uint32_t raw = 0;
|
||||
for(uint32_t i=0; i<BATTERY_SENSE_SAMPLES;i++){
|
||||
for(uint32_t i=0; i<BATTERY_SENSE_SAMPLES; i++){
|
||||
raw += analogRead(BATTERY_PIN);
|
||||
}
|
||||
raw = raw/BATTERY_SENSE_SAMPLES;
|
||||
#else
|
||||
uint32_t raw = analogRead(BATTERY_PIN);
|
||||
#endif
|
||||
|
||||
float scaled;
|
||||
#ifndef VBAT_RAW_TO_SCALED
|
||||
@@ -182,6 +182,9 @@ Power::Power() : OSThread("Power")
|
||||
{
|
||||
statusHandler = {};
|
||||
low_voltage_counter = 0;
|
||||
#ifdef DEBUG_HEAP
|
||||
lastheap = ESP.getFreeHeap();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Power::analogInit()
|
||||
@@ -283,6 +286,12 @@ void Power::readPowerStatus()
|
||||
DEBUG_MSG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d\n", powerStatus2.getHasUSB(),
|
||||
powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
|
||||
newStatus.notifyObservers(&powerStatus2);
|
||||
#ifdef DEBUG_HEAP
|
||||
if (lastheap != ESP.getFreeHeap()){
|
||||
DEBUG_MSG("Heap status: %d/%d bytes free (%d), running %d threads\n", ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getFreeHeap() - lastheap , concurrency::mainController.size(false));
|
||||
lastheap = ESP.getFreeHeap();
|
||||
}
|
||||
#endif
|
||||
|
||||
// If we have a battery at all and it is less than 10% full, force deep sleep if we have more than 3 low readings in a row
|
||||
// Supect fluctuating voltage on the RAK4631 to force it to deep sleep even if battery is at 85% after only a few days
|
||||
@@ -290,8 +299,12 @@ void Power::readPowerStatus()
|
||||
if (powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) {
|
||||
if (batteryLevel->getBattVoltage() < MIN_BAT_MILLIVOLTS) {
|
||||
low_voltage_counter++;
|
||||
if (low_voltage_counter > 3)
|
||||
powerFSM.trigger(EVENT_LOW_BATTERY);
|
||||
DEBUG_MSG("Warning RAK4631 Low voltage counter: %d/10\n", low_voltage_counter);
|
||||
if (low_voltage_counter > 10) {
|
||||
// We can't trigger deep sleep on NRF52, it's freezing the board
|
||||
//powerFSM.trigger(EVENT_LOW_BATTERY);
|
||||
DEBUG_MSG("Low voltage detected, but not triggering deep sleep\n");
|
||||
}
|
||||
} else {
|
||||
low_voltage_counter = 0;
|
||||
}
|
||||
@@ -455,6 +468,9 @@ bool Power::axpChipInit()
|
||||
// Set constant current charging current
|
||||
PMU->setChargerConstantCurr(XPOWERS_AXP192_CHG_CUR_450MA);
|
||||
|
||||
//Set up the charging voltage
|
||||
PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2);
|
||||
|
||||
} else if (PMU->getChipModel() == XPOWERS_AXP2101) {
|
||||
|
||||
// t-beam s3 core
|
||||
@@ -507,6 +523,8 @@ bool Power::axpChipInit()
|
||||
//Set the constant current charging current of AXP2101, temporarily use 500mA by default
|
||||
PMU->setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_500MA);
|
||||
|
||||
//Set up the charging voltage
|
||||
PMU->setChargeTargetVoltage(XPOWERS_AXP2101_CHG_VOL_4V2);
|
||||
}
|
||||
|
||||
|
||||
@@ -559,14 +577,15 @@ bool Power::axpChipInit()
|
||||
}
|
||||
DEBUG_MSG("=======================================================================\n");
|
||||
|
||||
|
||||
//Set up the charging voltage, AXP2101/AXP192 4.2V gear is the same
|
||||
// XPOWERS_AXP192_CHG_VOL_4V2 = XPOWERS_AXP2101_CHG_VOL_4V2
|
||||
PMU->setChargeTargetVoltage(XPOWERS_AXP192_CHG_VOL_4V2);
|
||||
|
||||
// We can safely ignore this approach for most (or all) boards because MCU turned off
|
||||
// earlier than battery discharged to 2.6V.
|
||||
//
|
||||
// Unfortanly for now we can't use this killswitch for RAK4630-based boards because they have a bug with
|
||||
// battery voltage measurement. Probably it sometimes drops to low values.
|
||||
#ifndef RAK4630
|
||||
// Set PMU shutdown voltage at 2.6V to maximize battery utilization
|
||||
PMU->setSysPowerDownVoltage(2600);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef PMU_IRQ
|
||||
|
||||
@@ -34,7 +34,7 @@ static void sdsEnter()
|
||||
{
|
||||
DEBUG_MSG("Enter state: SDS\n");
|
||||
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
|
||||
doDeepSleep(config.power.sds_secs * 1000);
|
||||
doDeepSleep(getConfiguredOrDefaultMs(config.power.sds_secs));
|
||||
}
|
||||
|
||||
extern Power *power;
|
||||
@@ -324,31 +324,24 @@ void PowerFSM_setup()
|
||||
|
||||
powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone");
|
||||
|
||||
// each time we get a new update packet make sure we are staying in the ON state so the screen stays awake (also we don't
|
||||
// shutdown bluetooth if is_router)
|
||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_FIRMWARE_UPDATE, NULL, "Got firmware update");
|
||||
powerFSM.add_transition(&stateON, &stateON, EVENT_FIRMWARE_UPDATE, NULL, "Got firmware update");
|
||||
|
||||
powerFSM.add_timed_transition(&stateON, &stateDARK, getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL, "Screen-on timeout");
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// On most boards we use light-sleep to be our main state, but on NRF52 we just stay in DARK
|
||||
State *lowPowerState = &stateLS;
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
|
||||
|
||||
// See: https://github.com/meshtastic/firmware/issues/1071
|
||||
if (isRouter || config.power.is_power_saving) {
|
||||
powerFSM.add_timed_transition(&stateNB, &stateLS, getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs), NULL, "Min wake timeout");
|
||||
powerFSM.add_timed_transition(&stateDARK, &stateLS, getConfiguredOrDefaultMs(config.power.wait_bluetooth_secs, default_wait_bluetooth_secs), NULL, "Bluetooth timeout");
|
||||
}
|
||||
|
||||
#elif defined (ARCH_NRF52)
|
||||
lowPowerState = &stateDARK;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (config.power.sds_secs != UINT32_MAX)
|
||||
powerFSM.add_timed_transition(lowPowerState, &stateSDS, config.power.sds_secs * 1000, NULL, "mesh timeout");
|
||||
powerFSM.add_timed_transition(lowPowerState, &stateSDS, getConfiguredOrDefaultMs(config.power.sds_secs), NULL, "mesh timeout");
|
||||
#endif
|
||||
|
||||
|
||||
powerFSM.run_machine(); // run one interation of the state machine, so we run our on enter tasks for the initial DARK state
|
||||
}
|
||||
|
||||
@@ -26,11 +26,9 @@ class PowerFSMThread : public OSThread
|
||||
|
||||
if (powerStatus->getHasUSB()) {
|
||||
timeLastPowered = millis();
|
||||
} else if (config.power.on_battery_shutdown_after_secs > 0 &&
|
||||
millis() >
|
||||
timeLastPowered +
|
||||
(1000 *
|
||||
config.power.on_battery_shutdown_after_secs)) { // shutdown after 30 minutes unpowered
|
||||
} else if (config.power.on_battery_shutdown_after_secs > 0 &&
|
||||
config.power.on_battery_shutdown_after_secs != UINT32_MAX &&
|
||||
millis() > (timeLastPowered + getConfiguredOrDefaultMs(config.power.on_battery_shutdown_after_secs))) { // shutdown after 30 minutes unpowered
|
||||
powerFSM.trigger(EVENT_SHUTDOWN);
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,20 @@ float AirTime::utilizationTXPercent()
|
||||
return (float(sum) / float(MS_IN_HOUR)) * 100;
|
||||
}
|
||||
|
||||
// Get the amount of minutes we have to be silent before we can send again
|
||||
uint8_t AirTime::getSilentMinutes(float txPercent, float dutyCycle)
|
||||
{
|
||||
float newTxPercent = txPercent;
|
||||
for (int8_t i = MINUTES_IN_HOUR-1; i >= 0; --i) {
|
||||
newTxPercent -= ((float)this->utilizationTX[i] / (MS_IN_MINUTE * MINUTES_IN_HOUR / 100));
|
||||
if (newTxPercent < dutyCycle)
|
||||
return MINUTES_IN_HOUR-1-i;
|
||||
}
|
||||
|
||||
return MINUTES_IN_HOUR;
|
||||
}
|
||||
|
||||
|
||||
AirTime::AirTime() : concurrency::OSThread("AirTime"),airtimes({}) {
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#define PERIODS_TO_LOG 8
|
||||
#define MINUTES_IN_HOUR 60
|
||||
#define SECONDS_IN_MINUTE 60
|
||||
#define MS_IN_MINUTE (SECONDS_IN_MINUTE * 1000)
|
||||
#define MS_IN_HOUR (MINUTES_IN_HOUR * SECONDS_IN_MINUTE * 1000)
|
||||
|
||||
|
||||
@@ -57,6 +58,7 @@ class AirTime : private concurrency::OSThread
|
||||
uint32_t getSecondsPerPeriod();
|
||||
uint32_t getSecondsSinceBoot();
|
||||
uint32_t *airtimeReport(reportTypes reportType);
|
||||
uint8_t getSilentMinutes(float txPercent, float dutyCycle);
|
||||
|
||||
private:
|
||||
bool firstTime = true;
|
||||
|
||||
@@ -2,22 +2,13 @@
|
||||
#include "configuration.h"
|
||||
#include "NodeDB.h"
|
||||
|
||||
#ifndef PIN_BUZZER
|
||||
|
||||
// Noop methods for boards w/o buzzer
|
||||
void playBeep(){};
|
||||
void playStartMelody(){};
|
||||
void playShutdownMelody(){};
|
||||
|
||||
#else
|
||||
#ifdef M5STACK
|
||||
#include "Speaker.h"
|
||||
TONE Tone;
|
||||
#else
|
||||
#if !defined(ARCH_ESP32) && !defined(ARCH_RP2040) && !defined(ARCH_PORTDUINO)
|
||||
#include "Tone.h"
|
||||
#endif
|
||||
|
||||
#if !defined(ARCH_PORTDUINO)
|
||||
extern "C" void delay(uint32_t dwMs);
|
||||
#endif
|
||||
|
||||
struct ToneDuration {
|
||||
int frequency_khz;
|
||||
@@ -43,30 +34,25 @@ const int DURATION_1_8 = 125; // 1/8 note
|
||||
const int DURATION_1_4 = 250; // 1/4 note
|
||||
|
||||
void playTones(const ToneDuration *tone_durations, int size) {
|
||||
if (config.network.eth_enabled != true) {
|
||||
#ifdef PIN_BUZZER
|
||||
if (!config.device.buzzer_gpio)
|
||||
config.device.buzzer_gpio = PIN_BUZZER;
|
||||
#endif
|
||||
if (config.device.buzzer_gpio) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
const auto &tone_duration = tone_durations[i];
|
||||
#ifdef M5STACK
|
||||
Tone.tone(tone_duration.frequency_khz);
|
||||
delay(tone_duration.duration_ms);
|
||||
Tone.mute();
|
||||
#else
|
||||
tone(PIN_BUZZER, tone_duration.frequency_khz, tone_duration.duration_ms);
|
||||
#endif
|
||||
tone(config.device.buzzer_gpio, tone_duration.frequency_khz, tone_duration.duration_ms);
|
||||
// to distinguish the notes, set a minimum time between them.
|
||||
delay(1.3 * tone_duration.duration_ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef M5STACK
|
||||
|
||||
void playBeep() {
|
||||
ToneDuration melody[] = {{NOTE_B3, DURATION_1_4}};
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
#else
|
||||
void playBeep() { tone(PIN_BUZZER, NOTE_B3, DURATION_1_4); }
|
||||
#endif
|
||||
|
||||
void playStartMelody() {
|
||||
ToneDuration melody[] = {{NOTE_FS3, DURATION_1_8},
|
||||
@@ -75,11 +61,9 @@ void playStartMelody() {
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
|
||||
|
||||
void playShutdownMelody() {
|
||||
ToneDuration melody[] = {{NOTE_CS4, DURATION_1_8},
|
||||
{NOTE_AS3, DURATION_1_8},
|
||||
{NOTE_FS3, DURATION_1_4}};
|
||||
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
|
||||
}
|
||||
#endif
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
void printATECCInfo()
|
||||
{
|
||||
#ifndef ARCH_PORTDUINO
|
||||
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||
atecc.readConfigZone(false);
|
||||
|
||||
DEBUG_MSG("ATECC608B Serial Number: ");
|
||||
@@ -114,7 +114,7 @@ void scanI2Cdevice()
|
||||
DEBUG_MSG("unknown display found\n");
|
||||
}
|
||||
}
|
||||
#ifndef ARCH_PORTDUINO
|
||||
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||
if (addr == ATECC608B_ADDR) {
|
||||
keystore_found = addr;
|
||||
if (atecc.begin(keystore_found) == true) {
|
||||
|
||||
@@ -150,9 +150,21 @@ bool GPS::setupGPS()
|
||||
_serial_gps->setRxBufferSize(2048); // the default is 256
|
||||
#endif
|
||||
|
||||
// if the overrides are not dialled in, set them from the board definitions, if they exist
|
||||
|
||||
#if defined(GPS_RX_PIN)
|
||||
if (!config.position.rx_gpio)
|
||||
config.position.rx_gpio = GPS_RX_PIN;
|
||||
#endif
|
||||
#if defined(GPS_TX_PIN)
|
||||
if (!config.position.tx_gpio)
|
||||
config.position.tx_gpio = GPS_TX_PIN;
|
||||
#endif
|
||||
|
||||
// ESP32 has a special set of parameters vs other arduino ports
|
||||
#if defined(GPS_RX_PIN) && defined(ARCH_ESP32)
|
||||
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
|
||||
#if defined(ARCH_ESP32)
|
||||
if(config.position.rx_gpio)
|
||||
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, config.position.rx_gpio, config.position.tx_gpio);
|
||||
#else
|
||||
_serial_gps->begin(GPS_BAUDRATE);
|
||||
#endif
|
||||
@@ -258,21 +270,30 @@ bool GPS::setup()
|
||||
pinMode(PIN_GPS_EN, OUTPUT);
|
||||
#endif
|
||||
|
||||
#ifdef HAS_PMU
|
||||
if(config.position.gps_enabled){
|
||||
setGPSPower(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PIN_GPS_RESET
|
||||
digitalWrite(PIN_GPS_RESET, 1); // assert for 10ms
|
||||
pinMode(PIN_GPS_RESET, OUTPUT);
|
||||
delay(10);
|
||||
digitalWrite(PIN_GPS_RESET, 0);
|
||||
#endif
|
||||
|
||||
setAwake(true); // Wake GPS power before doing any init
|
||||
bool ok = setupGPS();
|
||||
|
||||
if (ok) {
|
||||
notifySleepObserver.observe(¬ifySleep);
|
||||
notifyDeepSleepObserver.observe(¬ifyDeepSleep);
|
||||
notifyGPSSleepObserver.observe(¬ifyGPSSleep);
|
||||
}
|
||||
if (config.position.gps_enabled==false) {
|
||||
setAwake(false);
|
||||
doGPSpowersave(false);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
@@ -281,6 +302,7 @@ GPS::~GPS()
|
||||
// we really should unregister our sleep observer
|
||||
notifySleepObserver.unobserve(¬ifySleep);
|
||||
notifyDeepSleepObserver.unobserve(¬ifyDeepSleep);
|
||||
notifyGPSSleepObserver.observe(¬ifyGPSSleep);
|
||||
}
|
||||
|
||||
bool GPS::hasLock()
|
||||
@@ -393,7 +415,7 @@ void GPS::publishUpdate()
|
||||
DEBUG_MSG("publishing pos@%x:2, hasVal=%d, GPSlock=%d\n", p.timestamp, hasValidLocation, hasLock());
|
||||
|
||||
// Notify any status instances that are observing us
|
||||
const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasValidLocation, isConnected(), p);
|
||||
const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasValidLocation, isConnected(), isPowerSaving(), p);
|
||||
newStatus.notifyObservers(&status);
|
||||
}
|
||||
}
|
||||
@@ -404,7 +426,7 @@ int32_t GPS::runOnce()
|
||||
// if we have received valid NMEA claim we are connected
|
||||
setConnected();
|
||||
} else {
|
||||
if(gnssModel == GNSS_MODEL_UBLOX){
|
||||
if((config.position.gps_enabled == 1) && (gnssModel == GNSS_MODEL_UBLOX)){
|
||||
// reset the GPS on next bootup
|
||||
if(devicestate.did_gps_reset && (millis() > 60000) && !hasFlow()) {
|
||||
DEBUG_MSG("GPS is not communicating, trying factory reset on next bootup.\n");
|
||||
@@ -506,6 +528,7 @@ int GPS::prepareDeepSleep(void *unused)
|
||||
DEBUG_MSG("GPS deep sleep!\n");
|
||||
|
||||
// For deep sleep we also want abandon any lock attempts (because we want minimum power)
|
||||
getSleepTime();
|
||||
setAwake(false);
|
||||
|
||||
return 0;
|
||||
@@ -641,6 +664,11 @@ GPS *createGps()
|
||||
return new_gps;
|
||||
}
|
||||
}
|
||||
else{
|
||||
GPS *new_gps = new NMEAGPS();
|
||||
new_gps->setup();
|
||||
return new_gps;
|
||||
}
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ class GPS : private concurrency::OSThread
|
||||
|
||||
CallbackObserver<GPS, void *> notifySleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareSleep);
|
||||
CallbackObserver<GPS, void *> notifyDeepSleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareDeepSleep);
|
||||
CallbackObserver<GPS, void *> notifyGPSSleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareDeepSleep);
|
||||
|
||||
public:
|
||||
/** If !NULL we will use this serial port to construct our GPS */
|
||||
@@ -77,6 +78,8 @@ class GPS : private concurrency::OSThread
|
||||
/// Return true if we are connected to a GPS
|
||||
bool isConnected() const { return hasGPS; }
|
||||
|
||||
bool isPowerSaving() const { return !config.position.gps_enabled;}
|
||||
|
||||
/**
|
||||
* Restart our lock attempt - try to get and broadcast a GPS reading ASAP
|
||||
* called after the CPU wakes from light-sleep state
|
||||
|
||||
@@ -109,7 +109,7 @@ bool NMEAGPS::lookForLocation()
|
||||
|
||||
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
|
||||
fixType = atoi(gsafixtype.value()); // will set to zero if no data
|
||||
DEBUG_MSG("FIX QUAL=%d, TYPE=%d\n", fixQual, fixType);
|
||||
// DEBUG_MSG("FIX QUAL=%d, TYPE=%d\n", fixQual, fixType);
|
||||
#endif
|
||||
|
||||
// check if GPS has an acceptable lock
|
||||
@@ -168,7 +168,7 @@ bool NMEAGPS::lookForLocation()
|
||||
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
|
||||
p.HDOP = reader.hdop.value();
|
||||
p.PDOP = TinyGPSPlus::parseDecimal(gsapdop.value());
|
||||
DEBUG_MSG("PDOP=%d, HDOP=%d\n", dop, reader.hdop.value());
|
||||
// DEBUG_MSG("PDOP=%d, HDOP=%d\n", p.PDOP, p.HDOP);
|
||||
#else
|
||||
// FIXME! naive PDOP emulation (assumes VDOP==HDOP)
|
||||
// correct formula is PDOP = SQRT(HDOP^2 + VDOP^2)
|
||||
|
||||
@@ -35,6 +35,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "mesh/Channels.h"
|
||||
#include "mesh/generated/deviceonly.pb.h"
|
||||
#include "modules/TextMessageModule.h"
|
||||
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
#include "utils.h"
|
||||
@@ -42,6 +43,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#ifdef ARCH_ESP32
|
||||
#include "esp_task_wdt.h"
|
||||
#include "mesh/http/WiFiAPClient.h"
|
||||
#include "modules/esp32/StoreForwardModule.h"
|
||||
#endif
|
||||
|
||||
#ifdef OLED_RU
|
||||
@@ -95,17 +97,17 @@ static uint16_t displayWidth, displayHeight;
|
||||
|
||||
#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS)
|
||||
// The screen is bigger so use bigger fonts
|
||||
#define FONT_SMALL ArialMT_Plain_16
|
||||
#define FONT_MEDIUM ArialMT_Plain_24
|
||||
#define FONT_LARGE ArialMT_Plain_24
|
||||
#define FONT_SMALL ArialMT_Plain_16 // Height: 19
|
||||
#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28
|
||||
#define FONT_LARGE ArialMT_Plain_24 // Height: 28
|
||||
#else
|
||||
#ifdef OLED_RU
|
||||
#define FONT_SMALL ArialMT_Plain_10_RU
|
||||
#else
|
||||
#define FONT_SMALL ArialMT_Plain_10
|
||||
#define FONT_SMALL ArialMT_Plain_10 // Height: 13
|
||||
#endif
|
||||
#define FONT_MEDIUM ArialMT_Plain_16
|
||||
#define FONT_LARGE ArialMT_Plain_24
|
||||
#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19
|
||||
#define FONT_LARGE ArialMT_Plain_24 // Height: 28
|
||||
#endif
|
||||
|
||||
#define fontHeight(font) ((font)[1] + 1) // height is position 1
|
||||
@@ -283,27 +285,27 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int
|
||||
|
||||
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
int x_offset = display->width() / 2;
|
||||
int y_offset = display->height() == 64 ? 0 : 32;
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->setFont(FONT_MEDIUM);
|
||||
display->drawString(64 + x, y, "Bluetooth");
|
||||
display->drawString(x_offset + x, y_offset + y, "Bluetooth");
|
||||
|
||||
display->setFont(FONT_SMALL);
|
||||
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Enter this code");
|
||||
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_MEDIUM -4 : y_offset + FONT_HEIGHT_MEDIUM + 5;
|
||||
display->drawString(x_offset + x, y_offset + y, "Enter this code");
|
||||
|
||||
display->setFont(FONT_LARGE);
|
||||
String displayPin(btPIN);
|
||||
String pin = displayPin.substring(0, 3) + " " + displayPin.substring(3, 6);
|
||||
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_SMALL - 5 : y_offset + FONT_HEIGHT_SMALL + 5;
|
||||
display->drawString(x_offset + x, y_offset + y, pin);
|
||||
|
||||
auto displayPin = new String(btPIN);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->drawString(12 + x, 26 + y, displayPin->substring(0, 3));
|
||||
display->drawString(72 + x, 26 + y, displayPin->substring(3, 6));
|
||||
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->setFont(FONT_SMALL);
|
||||
char buf[30];
|
||||
const char *name = "Name: ";
|
||||
strcpy(buf, name);
|
||||
strcat(buf, getDeviceName());
|
||||
display->drawString(64 + x, 48 + y, buf);
|
||||
String deviceName = "Name: ";
|
||||
deviceName.concat(getDeviceName());
|
||||
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5;
|
||||
display->drawString(x_offset + x, y_offset + y, deviceName);
|
||||
}
|
||||
|
||||
static void drawFrameShutdown(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
@@ -329,11 +331,8 @@ static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
||||
display->drawString(64 + x, y, "Updating");
|
||||
|
||||
display->setFont(FONT_SMALL);
|
||||
if ((millis() / 1000) % 2) {
|
||||
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . .");
|
||||
} else {
|
||||
display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . ");
|
||||
}
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->drawStringMaxWidth(0 + x, 2 + y + FONT_HEIGHT_SMALL *2, x + display->getWidth(), "Please be patient and do not power off.");
|
||||
}
|
||||
|
||||
/// Draw the last text message we received
|
||||
@@ -364,6 +363,9 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
|
||||
{
|
||||
displayedNodeNum = 0; // Not currently showing a node pane
|
||||
|
||||
// the max length of this buffer is much longer than we can possibly print
|
||||
static char tempBuf[237];
|
||||
|
||||
MeshPacket &mp = devicestate.rx_text_message;
|
||||
NodeInfo *node = nodeDB.getNode(getFrom(&mp));
|
||||
// DEBUG_MSG("drawing text message from 0x%x: %s\n", mp.from,
|
||||
@@ -373,16 +375,14 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
|
||||
// with the third parameter you can define the width after which words will
|
||||
// be wrapped. Currently only spaces and "-" are allowed for wrapping
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_MEDIUM);
|
||||
String sender = (node && node->has_user) ? node->user.short_name : "???";
|
||||
display->drawString(0 + x, 0 + y, sender);
|
||||
display->setFont(FONT_SMALL);
|
||||
|
||||
// the max length of this buffer is much longer than we can possibly print
|
||||
static char tempBuf[96];
|
||||
snprintf(tempBuf, sizeof(tempBuf), " %s", mp.decoded.payload.bytes);
|
||||
|
||||
display->drawStringMaxWidth(4 + x, 10 + y, SCREEN_WIDTH - (6 + x), tempBuf);
|
||||
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
|
||||
display->setColor(BLACK);
|
||||
display->drawStringf(0 + x, 0 + y, tempBuf, "From: %s", (node && node->has_user) ? node->user.short_name : "???");
|
||||
display->drawStringf(1 + x, 0 + y, tempBuf, "From: %s", (node && node->has_user) ? node->user.short_name : "???");
|
||||
display->setColor(WHITE);
|
||||
snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes);
|
||||
display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf);
|
||||
}
|
||||
|
||||
/// Draw a series of fields in a column, wrapping to multiple colums if needed
|
||||
@@ -395,6 +395,10 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char *
|
||||
int xo = x, yo = y;
|
||||
while (*f) {
|
||||
display->drawString(xo, yo, *f);
|
||||
if (display->getColor() == BLACK)
|
||||
display->drawString(xo + 1, yo, *f);
|
||||
|
||||
display->setColor(WHITE);
|
||||
yo += FONT_HEIGHT_SMALL;
|
||||
if (yo > SCREEN_HEIGHT - FONT_HEIGHT_SMALL) {
|
||||
xo += SCREEN_WIDTH / 2;
|
||||
@@ -463,8 +467,13 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, NodeStatus *no
|
||||
{
|
||||
char usersString[20];
|
||||
sprintf(usersString, "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal());
|
||||
#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS)
|
||||
display->drawFastImage(x, y + 3, 8, 8, imgUser);
|
||||
#else
|
||||
display->drawFastImage(x, y, 8, 8, imgUser);
|
||||
#endif
|
||||
display->drawString(x + 10, y - 2, usersString);
|
||||
display->drawString(x + 11, y - 2, usersString);
|
||||
}
|
||||
|
||||
// Draw GPS status summary
|
||||
@@ -473,15 +482,18 @@ static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus
|
||||
if (config.position.fixed_position) {
|
||||
// GPS coordinates are currently fixed
|
||||
display->drawString(x - 1, y - 2, "Fixed GPS");
|
||||
display->drawString(x, y - 2, "Fixed GPS");
|
||||
return;
|
||||
}
|
||||
if (!gps->getIsConnected()) {
|
||||
display->drawString(x, y - 2, "No GPS");
|
||||
display->drawString(x + 1, y - 2, "No GPS");
|
||||
return;
|
||||
}
|
||||
display->drawFastImage(x, y, 6, 8, gps->getHasLock() ? imgPositionSolid : imgPositionEmpty);
|
||||
if (!gps->getHasLock()) {
|
||||
display->drawString(x + 8, y - 2, "No sats");
|
||||
display->drawString(x + 9, y - 2, "No sats");
|
||||
return;
|
||||
} else {
|
||||
char satsString[3];
|
||||
@@ -506,6 +518,23 @@ static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus
|
||||
}
|
||||
}
|
||||
|
||||
//Draw status when gps is disabled by PMU
|
||||
static void drawGPSpowerstat(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
|
||||
{
|
||||
#ifdef HAS_PMU
|
||||
String displayLine = "GPS disabled";
|
||||
int16_t xPos = display->getStringWidth(displayLine);
|
||||
|
||||
if (!config.position.gps_enabled){
|
||||
display->drawString(x + xPos, y, displayLine);
|
||||
#ifdef GPS_POWER_TOGGLE
|
||||
display->drawString(x + xPos, y - 2 + FONT_HEIGHT_SMALL, " by button");
|
||||
#endif
|
||||
//display->drawString(x + xPos, y + 2, displayLine);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static void drawGPSAltitude(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
|
||||
{
|
||||
String displayLine = "";
|
||||
@@ -669,16 +698,16 @@ static uint16_t getCompassDiam(OLEDDisplay *display)
|
||||
{
|
||||
uint16_t diam = 0;
|
||||
// get the smaller of the 2 dimensions and subtract 20
|
||||
if(display->getWidth() > display->getHeight()) {
|
||||
diam = display->getHeight();
|
||||
if(display->getWidth() > (display->getHeight() - FONT_HEIGHT_SMALL)) {
|
||||
diam = display->getHeight() - FONT_HEIGHT_SMALL;
|
||||
// if 2/3 of the other size would be smaller, use that
|
||||
if (diam > (display->getWidth() * 2 / 3)) {
|
||||
diam = display->getWidth() * 2 / 3;
|
||||
}
|
||||
} else {
|
||||
diam = display->getWidth();
|
||||
if (diam > (display->getHeight() * 2 / 3)) {
|
||||
diam = display->getHeight() * 2 / 3;
|
||||
if (diam > ((display->getHeight() - FONT_HEIGHT_SMALL) * 2 / 3)) {
|
||||
diam = (display->getHeight() - FONT_HEIGHT_SMALL) * 2 / 3;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -758,6 +787,8 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
// The coordinates define the left starting point of the text
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
|
||||
|
||||
const char *username = node->has_user ? node->user.long_name : "Unknown Name";
|
||||
|
||||
static char signalStr[20];
|
||||
@@ -788,7 +819,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
|
||||
|
||||
// coordinates for the center of the compass/circle
|
||||
int16_t compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5, compassY = y + SCREEN_HEIGHT / 2;
|
||||
int16_t compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5, compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2;
|
||||
bool hasNodeHeading = false;
|
||||
|
||||
if (ourNode && hasPosition(ourNode)) {
|
||||
@@ -831,33 +862,11 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
|
||||
display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?");
|
||||
display->drawCircle(compassX, compassY, getCompassDiam(display) / 2);
|
||||
|
||||
display->setColor(BLACK);
|
||||
// Must be after distStr is populated
|
||||
drawColumns(display, x, y, fields);
|
||||
}
|
||||
|
||||
#if 0
|
||||
void _screen_header()
|
||||
{
|
||||
if (!disp)
|
||||
return;
|
||||
|
||||
// Message count
|
||||
//snprintf(buffer, sizeof(buffer), "#%03d", ttn_get_count() % 1000);
|
||||
//display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
//display->drawString(0, 2, buffer);
|
||||
|
||||
// Datetime
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
display->drawString(display->getWidth()/2, 2, gps.getTimeStr());
|
||||
|
||||
// Satellite count
|
||||
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
||||
char buffer[10];
|
||||
display->drawString(display->getWidth() - SATELLITE_IMAGE_WIDTH - 4, 2, itoa(gps.satellites.value(), buffer, 10));
|
||||
display->drawXbm(display->getWidth() - SATELLITE_IMAGE_WIDTH, 0, SATELLITE_IMAGE_WIDTH, SATELLITE_IMAGE_HEIGHT, SATELLITE_IMAGE);
|
||||
}
|
||||
#endif
|
||||
|
||||
// #ifdef RAK4630
|
||||
// Screen::Screen(uint8_t address, int sda, int scl) : OSThread("Screen"), cmdQueue(32), dispdev(address, sda, scl),
|
||||
// dispdev_oled(address, sda, scl), ui(&dispdev)
|
||||
@@ -1357,6 +1366,9 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
// The coordinates define the left starting point of the text
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
|
||||
display->setColor(BLACK);
|
||||
|
||||
char channelStr[20];
|
||||
{
|
||||
concurrency::LockGuard guard(&lock);
|
||||
@@ -1366,18 +1378,54 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
||||
|
||||
// Display power status
|
||||
if (powerStatus->getHasBattery())
|
||||
drawBattery(display, x, y + 2, imgBattery, powerStatus);
|
||||
drawBattery(display, x + 1, y + 3, imgBattery, powerStatus);
|
||||
else if (powerStatus->knowsUSB())
|
||||
display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower);
|
||||
display->drawFastImage(x + 1, y + 3, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower);
|
||||
// Display nodes status
|
||||
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus);
|
||||
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus);
|
||||
// Display GPS status
|
||||
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus);
|
||||
if (!config.position.gps_enabled){
|
||||
int16_t yPos = y + 2;
|
||||
#ifdef GPS_POWER_TOGGLE
|
||||
yPos = (y + 10 + FONT_HEIGHT_SMALL);
|
||||
#endif
|
||||
drawGPSpowerstat(display, x, yPos, gpsStatus);
|
||||
} else {
|
||||
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus);
|
||||
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus);
|
||||
}
|
||||
|
||||
display->setColor(WHITE);
|
||||
// Draw the channel name
|
||||
display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr);
|
||||
// Draw our hardware ID to assist with bluetooth pairing
|
||||
display->drawFastImage(x + SCREEN_WIDTH - (10) - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, imgInfo);
|
||||
// Draw our hardware ID to assist with bluetooth pairing. Either prefix with Info or S&F Logo
|
||||
if (moduleConfig.store_forward.enabled) {
|
||||
#if 0
|
||||
if (millis() - storeForwardModule->lastHeartbeat > (storeForwardModule->heartbeatInterval * 1200)) { //no heartbeat, overlap a bit
|
||||
#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS)
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgQuestionL1);
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8, imgQuestionL2);
|
||||
#else
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, imgQuestion);
|
||||
#endif
|
||||
} else {
|
||||
#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS)
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, imgSFL1);
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 16, 8, imgSFL2);
|
||||
#else
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 13 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 11, 8, imgSF);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS)
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgInfoL1);
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8, imgInfoL2);
|
||||
#else
|
||||
display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, imgInfo);
|
||||
#endif
|
||||
}
|
||||
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT_SMALL, ourId);
|
||||
|
||||
// Draw any log messages
|
||||
@@ -1404,15 +1452,24 @@ void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, i
|
||||
// The coordinates define the left starting point of the text
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
|
||||
display->setColor(BLACK);
|
||||
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
display->drawString(x, y, String("WiFi: Not Connected"));
|
||||
display->drawString(x + 1, y, String("WiFi: Not Connected"));
|
||||
} else {
|
||||
display->drawString(x, y, String("WiFi: Connected"));
|
||||
display->drawString(x + 1, y, String("WiFi: Connected"));
|
||||
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())), y,
|
||||
"RSSI " + String(WiFi.RSSI()));
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())) - 1, y,
|
||||
"RSSI " + String(WiFi.RSSI()));
|
||||
}
|
||||
|
||||
display->setColor(WHITE);
|
||||
|
||||
/*
|
||||
- WL_CONNECTED: assigned when connected to a WiFi network;
|
||||
- WL_NO_SSID_AVAIL: assigned when no SSID are available;
|
||||
@@ -1521,6 +1578,9 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
// The coordinates define the left starting point of the text
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
|
||||
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
|
||||
display->setColor(BLACK);
|
||||
|
||||
char batStr[20];
|
||||
if (powerStatus->getHasBattery()) {
|
||||
int batV = powerStatus->getBatteryVoltageMv() / 1000;
|
||||
@@ -1531,9 +1591,11 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
|
||||
// Line 1
|
||||
display->drawString(x, y, batStr);
|
||||
display->drawString(x + 1, y, batStr);
|
||||
} else {
|
||||
// Line 1
|
||||
display->drawString(x, y, String("USB"));
|
||||
display->drawString(x + 1, y, String("USB"));
|
||||
}
|
||||
|
||||
auto mode = "";
|
||||
@@ -1566,6 +1628,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
}
|
||||
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode);
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode);
|
||||
|
||||
// Line 2
|
||||
uint32_t currentMillis = millis();
|
||||
@@ -1578,6 +1641,8 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
// minutes %= 60;
|
||||
// hours %= 24;
|
||||
|
||||
display->setColor(WHITE);
|
||||
|
||||
// Show uptime as days, hours, minutes OR seconds
|
||||
String uptime;
|
||||
if (days >= 2)
|
||||
@@ -1613,7 +1678,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
char chUtil[13];
|
||||
sprintf(chUtil, "ChUtil %2.0f%%", airTime->channelUtilizationPercent());
|
||||
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(chUtil), y + FONT_HEIGHT_SMALL * 1, chUtil);
|
||||
|
||||
if (config.position.gps_enabled) {
|
||||
// Line 3
|
||||
if (config.display.gps_format !=
|
||||
Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude
|
||||
@@ -1621,7 +1686,9 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
|
||||
|
||||
// Line 4
|
||||
drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus);
|
||||
|
||||
} else {
|
||||
drawGPSpowerstat(display, x - (SCREEN_WIDTH / 4), y + FONT_HEIGHT_SMALL * 2, gpsStatus);
|
||||
}
|
||||
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
|
||||
#ifdef SHOW_REDRAWS
|
||||
if (heartbeat)
|
||||
|
||||
@@ -21,6 +21,7 @@ class Screen
|
||||
void startBluetoothPinScreen(uint32_t pin) {}
|
||||
void stopBluetoothPinScreen() {}
|
||||
void startRebootScreen() {}
|
||||
void startFirmwareUpdateScreen() {}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,36 +12,18 @@ const uint8_t imgPower[] PROGMEM = { 0x40, 0x40, 0x40, 0x58, 0x48, 0x08,
|
||||
const uint8_t imgUser[] PROGMEM = { 0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3C };
|
||||
const uint8_t imgPositionEmpty[] PROGMEM = { 0x20, 0x30, 0x28, 0x24, 0x42, 0xFF };
|
||||
const uint8_t imgPositionSolid[] PROGMEM = { 0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF };
|
||||
const uint8_t imgInfo[] PROGMEM = { 0xFF, 0x81, 0x81, 0xB5, 0xB5, 0x81, 0x81, 0xFF };
|
||||
|
||||
#include "img/icon.xbm"
|
||||
|
||||
// We now programmatically draw our compass
|
||||
#if 0
|
||||
const
|
||||
#include "img/compass.xbm"
|
||||
#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS)
|
||||
const uint8_t imgQuestionL1[] PROGMEM = { 0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff };
|
||||
const uint8_t imgQuestionL2[] PROGMEM = { 0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f };
|
||||
const uint8_t imgInfoL1[] PROGMEM = { 0xff, 0x01, 0x01, 0x01, 0x1e, 0x7f, 0x1e, 0x01, 0x01, 0x01, 0x01, 0xff };
|
||||
const uint8_t imgInfoL2[] PROGMEM = { 0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f };
|
||||
const uint8_t imgSFL1[] PROGMEM = { 0xb6, 0x8f, 0x19, 0x11, 0x31, 0xe3, 0xc2, 0x01, 0x01, 0xf9, 0xf9, 0x89, 0x89, 0x89, 0x09, 0xeb};
|
||||
const uint8_t imgSFL2[] PROGMEM = { 0x0e, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x08, 0x00, 0x0f, 0x0f, 0x00, 0x08, 0x08, 0x08, 0x0f};
|
||||
#else
|
||||
const uint8_t imgInfo[] PROGMEM = { 0xff, 0x81, 0x00, 0xfb, 0xfb, 0x00, 0x81, 0xff };
|
||||
const uint8_t imgQuestion[] PROGMEM = { 0xbf, 0x41, 0xc0, 0x8b, 0xdb, 0x70, 0xa1, 0xdf };
|
||||
const uint8_t imgSF[] PROGMEM = { 0xd2, 0xb7, 0xad, 0xbb, 0x92, 0x01, 0xfd, 0xfd, 0x15, 0x85, 0xf5};
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
const uint8_t activeSymbol[] PROGMEM = {
|
||||
B00000000,
|
||||
B00000000,
|
||||
B00011000,
|
||||
B00100100,
|
||||
B01000010,
|
||||
B01000010,
|
||||
B00100100,
|
||||
B00011000
|
||||
};
|
||||
|
||||
const uint8_t inactiveSymbol[] PROGMEM = {
|
||||
B00000000,
|
||||
B00000000,
|
||||
B00000000,
|
||||
B00000000,
|
||||
B00011000,
|
||||
B00011000,
|
||||
B00000000,
|
||||
B00000000
|
||||
};
|
||||
#endif
|
||||
#include "img/icon.xbm"
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
#define compass_width 48
|
||||
#define compass_height 48
|
||||
static char compass_bits[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00,
|
||||
0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00,
|
||||
0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0x01, 0x00,
|
||||
0x00, 0xC0, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0x00,
|
||||
0x00, 0xF8, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0xFC, 0x07, 0xE0, 0x3F, 0x00,
|
||||
0x00, 0xFE, 0x01, 0x80, 0x7F, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x7F, 0x00,
|
||||
0x00, 0x7F, 0x00, 0x00, 0xFE, 0x00, 0x80, 0x3F, 0x00, 0x00, 0xFC, 0x01,
|
||||
0x80, 0x1F, 0x00, 0x00, 0xF8, 0x01, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x01,
|
||||
0xC0, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03,
|
||||
0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03, 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03,
|
||||
0xFC, 0x07, 0x00, 0x00, 0xE0, 0x3F, 0xFC, 0x07, 0x00, 0x00, 0xE0, 0x3F,
|
||||
0xFC, 0x07, 0x00, 0x00, 0xE0, 0x3F, 0xFC, 0x07, 0x00, 0x00, 0xE0, 0x3F,
|
||||
0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03, 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03,
|
||||
0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03, 0xC0, 0x0F, 0x00, 0x00, 0xF0, 0x03,
|
||||
0x80, 0x0F, 0x00, 0x00, 0xF0, 0x01, 0x80, 0x1F, 0x00, 0x00, 0xF8, 0x01,
|
||||
0x80, 0x3F, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x7F, 0x00, 0x00, 0xFE, 0x00,
|
||||
0x00, 0xFE, 0x00, 0x00, 0x7F, 0x00, 0x00, 0xFE, 0x01, 0x80, 0x7F, 0x00,
|
||||
0x00, 0xFC, 0x07, 0xE0, 0x3F, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0x1F, 0x00,
|
||||
0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0x03, 0x00,
|
||||
0x00, 0x80, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00,
|
||||
0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00,
|
||||
0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
#define pin_width 13
|
||||
#define pin_height 13
|
||||
static char pin_bits[] = {
|
||||
0x00, 0x00, 0xF0, 0x01, 0xF8, 0x03, 0xFC, 0x07, 0xBC, 0x07, 0xBC, 0x07,
|
||||
0xFC, 0x07, 0xF8, 0x03, 0xF8, 0x03, 0xF0, 0x01, 0xE0, 0x00, 0xE0, 0x00,
|
||||
0x00, 0x00, };
|
||||
38
src/main.cpp
38
src/main.cpp
@@ -8,8 +8,6 @@
|
||||
#include "configuration.h"
|
||||
#include "error.h"
|
||||
#include "power.h"
|
||||
// #include "rom/rtc.h"
|
||||
//#include "DSRRouter.h"
|
||||
#include "ReliableRouter.h"
|
||||
// #include "debug.h"
|
||||
#include "FSCommon.h"
|
||||
@@ -140,8 +138,9 @@ bool ButtonThread::shutdown_on_long_stop = false;
|
||||
#endif
|
||||
|
||||
static Periodic *ledPeriodic;
|
||||
static OSThread *powerFSMthread, *buttonThread;
|
||||
static OSThread *powerFSMthread;
|
||||
#if HAS_BUTTON
|
||||
static OSThread *buttonThread;
|
||||
uint32_t ButtonThread::longPressTime = 0;
|
||||
#endif
|
||||
|
||||
@@ -216,7 +215,6 @@ void setup()
|
||||
|
||||
fsInit();
|
||||
|
||||
// router = new DSRRouter();
|
||||
router = new ReliableRouter();
|
||||
|
||||
#ifdef I2C_SDA1
|
||||
@@ -269,7 +267,7 @@ void setup()
|
||||
#ifdef HAS_SDCARD
|
||||
setupSDCard();
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef RAK4630
|
||||
// scanEInkDevice();
|
||||
#endif
|
||||
@@ -303,11 +301,11 @@ void setup()
|
||||
nodeDB.init();
|
||||
|
||||
playStartMelody();
|
||||
|
||||
|
||||
// fixed screen override?
|
||||
if (config.display.oled != Config_DisplayConfig_OledType_OLED_AUTO)
|
||||
screen_model = config.display.oled;
|
||||
|
||||
|
||||
// Init our SPI controller (must be before screen and lora)
|
||||
initSPI();
|
||||
#ifndef ARCH_ESP32
|
||||
@@ -386,7 +384,7 @@ void setup()
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(USE_SX1280) && !defined(ARCH_PORTDUINO)
|
||||
#if defined(USE_SX1280)
|
||||
if (!rIf) {
|
||||
rIf = new SX1280Interface(SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY, SPI);
|
||||
if (!rIf->init()) {
|
||||
@@ -451,6 +449,30 @@ void setup()
|
||||
}
|
||||
#endif
|
||||
|
||||
// check if the radio chip matches the selected region
|
||||
|
||||
if((config.lora.region == Config_LoRaConfig_RegionCode_LORA_24) && (!rIf->wideLora())){
|
||||
DEBUG_MSG("Warning: Radio chip does not support 2.4GHz LoRa. Reverting to unset.\n");
|
||||
config.lora.region = Config_LoRaConfig_RegionCode_UNSET;
|
||||
nodeDB.saveToDisk(SEGMENT_CONFIG);
|
||||
if(!rIf->reconfigure()) {
|
||||
DEBUG_MSG("Reconfigure failed, rebooting\n");
|
||||
screen->startRebootScreen();
|
||||
rebootAtMsec = millis() + 5000;
|
||||
}
|
||||
}
|
||||
|
||||
if((config.lora.region != Config_LoRaConfig_RegionCode_LORA_24) && (rIf->wideLora())){
|
||||
DEBUG_MSG("Warning: Radio chip only supports 2.4GHz LoRa. Adjusting Region.\n");
|
||||
config.lora.region = Config_LoRaConfig_RegionCode_LORA_24;
|
||||
nodeDB.saveToDisk(SEGMENT_CONFIG);
|
||||
if(!rIf->reconfigure()) {
|
||||
DEBUG_MSG("Reconfigure failed, rebooting\n");
|
||||
screen->startRebootScreen();
|
||||
rebootAtMsec = millis() + 5000;
|
||||
}
|
||||
}
|
||||
|
||||
#if HAS_WIFI || HAS_ETHERNET
|
||||
mqttInit();
|
||||
#endif
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "PowerStatus.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "mesh/generated/telemetry.pb.h"
|
||||
#ifndef ARCH_PORTDUINO
|
||||
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||
#include <SparkFun_ATECCX08a_Arduino_Library.h>
|
||||
#endif
|
||||
|
||||
@@ -22,7 +22,7 @@ extern bool pmu_found;
|
||||
extern bool isCharging;
|
||||
extern bool isUSBPowered;
|
||||
|
||||
#ifndef ARCH_PORTDUINO
|
||||
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
|
||||
extern ATECCX08A atecc;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -196,6 +196,17 @@ Channel &Channels::getByIndex(ChannelIndex chIndex)
|
||||
return *ch;
|
||||
}
|
||||
|
||||
Channel &Channels::getByName(const char* chName)
|
||||
{
|
||||
for (ChannelIndex i = 0; i < getNumChannels(); i++) {
|
||||
if (strcasecmp(channelFile.channels[i].settings.name, chName) == 0) {
|
||||
return channelFile.channels[i];
|
||||
}
|
||||
}
|
||||
|
||||
return getByIndex(getPrimaryIndex());
|
||||
}
|
||||
|
||||
void Channels::setChannel(const Channel &c)
|
||||
{
|
||||
Channel &old = getByIndex(c.index);
|
||||
|
||||
@@ -40,6 +40,9 @@ class Channels
|
||||
/** Return the Channel for a specified index */
|
||||
Channel &getByIndex(ChannelIndex chIndex);
|
||||
|
||||
/** Return the Channel for a specified name, return primary if not found. */
|
||||
Channel &getByName(const char* chName);
|
||||
|
||||
/** Using the index inside the channel, update the specified channel's settings and role. If this channel is being promoted
|
||||
* to be primary, force all other channels to be secondary.
|
||||
*/
|
||||
|
||||
@@ -41,6 +41,11 @@ void FloodingRouter::sniffReceived(const MeshPacket *p, const Routing *c)
|
||||
|
||||
tosend->hop_limit--; // bump down the hop count
|
||||
|
||||
// If it is a traceRoute request, update the route that it went via me
|
||||
if (p->which_payload_variant == MeshPacket_decoded_tag && traceRouteModule->wantPacket(p)) {
|
||||
traceRouteModule->updateRoute(tosend);
|
||||
}
|
||||
|
||||
printPacket("Rebroadcasting received floodmsg to neighbors", p);
|
||||
// Note: we are careful to resend using the original senders node id
|
||||
// We are careful not to call our hooked version of send() - because we don't want to check this again
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "PacketHistory.h"
|
||||
#include "Router.h"
|
||||
#include "modules/TraceRouteModule.h"
|
||||
|
||||
/**
|
||||
* This is a mixin that extends Router with the ability to do Naive Flooding (in the standard mesh protocol sense)
|
||||
|
||||
@@ -7,7 +7,4 @@
|
||||
template class SX126xInterface<SX1262>;
|
||||
template class SX126xInterface<SX1268>;
|
||||
template class SX126xInterface<LLCC68>;
|
||||
|
||||
#if !defined(ARCH_PORTDUINO)
|
||||
template class SX128xInterface<SX1280>;
|
||||
#endif
|
||||
@@ -104,16 +104,17 @@ bool MeshService::reloadConfig(int saveWhat)
|
||||
}
|
||||
|
||||
/// The owner User record just got updated, update our node DB and broadcast the info into the mesh
|
||||
void MeshService::reloadOwner()
|
||||
void MeshService::reloadOwner(bool shouldSave)
|
||||
{
|
||||
// DEBUG_MSG("reloadOwner()\n");
|
||||
// update our local data directly
|
||||
nodeDB.updateUser(nodeDB.getNodeNum(), owner);
|
||||
assert(nodeInfoModule);
|
||||
// update everyone else
|
||||
if (nodeInfoModule)
|
||||
// update everyone else and save to disk
|
||||
if (nodeInfoModule && shouldSave) {
|
||||
nodeInfoModule->sendOurNodeInfo();
|
||||
nodeDB.saveToDisk(SEGMENT_DEVICESTATE);
|
||||
nodeDB.saveToDisk(SEGMENT_DEVICESTATE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -69,7 +69,7 @@ class MeshService
|
||||
bool reloadConfig(int saveWhat=SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS);
|
||||
|
||||
/// The owner User record just got updated, update our node DB and broadcast the info into the mesh
|
||||
void reloadOwner();
|
||||
void reloadOwner(bool shouldSave = true);
|
||||
|
||||
/// Called when the user wakes up our GUI, normally sends our latest location to the mesh (if we have it), otherwise at least
|
||||
/// sends our owner
|
||||
|
||||
@@ -162,6 +162,7 @@ void NodeDB::installDefaultConfig()
|
||||
config.has_network = true;
|
||||
config.has_bluetooth = true;
|
||||
config.lora.tx_enabled = true; // FIXME: maybe false in the future, and setting region to enable it. (unset region forces it off)
|
||||
config.lora.override_duty_cycle = false;
|
||||
config.lora.region = Config_LoRaConfig_RegionCode_UNSET;
|
||||
config.lora.modem_preset = Config_LoRaConfig_ModemPreset_LONG_FAST;
|
||||
config.lora.hop_limit = HOP_RELIABLE;
|
||||
@@ -204,6 +205,7 @@ void NodeDB::installDefaultModuleConfig()
|
||||
{
|
||||
DEBUG_MSG("Installing default ModuleConfig\n");
|
||||
memset(&moduleConfig, 0, sizeof(ModuleConfig));
|
||||
|
||||
moduleConfig.version = DEVICESTATE_CUR_VER;
|
||||
moduleConfig.has_mqtt = true;
|
||||
moduleConfig.has_range_test = true;
|
||||
@@ -213,6 +215,10 @@ void NodeDB::installDefaultModuleConfig()
|
||||
moduleConfig.has_external_notification = true;
|
||||
moduleConfig.has_canned_message = true;
|
||||
|
||||
strncpy(moduleConfig.mqtt.address, default_mqtt_address, sizeof(moduleConfig.mqtt.address));
|
||||
strncpy(moduleConfig.mqtt.username, default_mqtt_username, sizeof(moduleConfig.mqtt.username));
|
||||
strncpy(moduleConfig.mqtt.password, default_mqtt_password, sizeof(moduleConfig.mqtt.password));
|
||||
|
||||
initModuleConfigIntervals();
|
||||
}
|
||||
|
||||
@@ -470,6 +476,15 @@ bool saveProto(const char *filename, size_t protoSize, size_t objSize, const pb_
|
||||
DEBUG_MSG("Error: can't rename new pref file\n");
|
||||
} else {
|
||||
DEBUG_MSG("Can't write prefs\n");
|
||||
#ifdef ARCH_NRF52
|
||||
static uint8_t failedCounter = 0;
|
||||
failedCounter++;
|
||||
if(failedCounter >= 2){
|
||||
FSCom.format();
|
||||
//After formatting, the device needs to be restarted
|
||||
nodeDB.resetRadioConfig(true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
DEBUG_MSG("ERROR: Filesystem not implemented\n");
|
||||
@@ -741,9 +756,9 @@ void recordCriticalError(CriticalErrorCode code, uint32_t address, const char *f
|
||||
String lcd = String("Critical error ") + code + "!\n";
|
||||
screen->print(lcd.c_str());
|
||||
if (filename)
|
||||
DEBUG_MSG("NOTE! Recording critical error %d at %s:%lx\n", code, filename, address);
|
||||
DEBUG_MSG("NOTE! Recording critical error %d at %s:%lu\n", code, filename, address);
|
||||
else
|
||||
DEBUG_MSG("NOTE! Recording critical error %d, address=%lx\n", code, address);
|
||||
DEBUG_MSG("NOTE! Recording critical error %d, address=0x%lx\n", code, address);
|
||||
|
||||
// Record error to DB
|
||||
myNodeInfo.error_code = code;
|
||||
|
||||
@@ -58,7 +58,7 @@ class NodeDB
|
||||
void init();
|
||||
|
||||
/// write to flash
|
||||
void saveToDisk(int saveWhat=SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS), saveChannelsToDisk(), saveDeviceStateToDisk();
|
||||
void saveToDisk(int saveWhat = SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS), saveChannelsToDisk(), saveDeviceStateToDisk();
|
||||
|
||||
/** Reinit radio config if needed, because either:
|
||||
* a) sometimes a buggy android app might send us bogus settings or
|
||||
@@ -194,6 +194,10 @@ extern NodeDB nodeDB;
|
||||
#define default_min_wake_secs 10
|
||||
#define default_screen_on_secs 60 * 10
|
||||
|
||||
#define default_mqtt_address "mqtt.meshtastic.org"
|
||||
#define default_mqtt_username "meshdev"
|
||||
#define default_mqtt_password "large4cats"
|
||||
|
||||
inline uint32_t getConfiguredOrDefaultMs(uint32_t configuredInterval)
|
||||
{
|
||||
if (configuredInterval > 0) return configuredInterval * 1000;
|
||||
|
||||
@@ -129,6 +129,7 @@ bool RF95Interface::reconfigure()
|
||||
|
||||
if (power > MAX_POWER) // This chip has lower power limits than some
|
||||
power = MAX_POWER;
|
||||
|
||||
err = lora->setOutputPower(power);
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
@@ -177,7 +177,7 @@ uint32_t RadioInterface::getRetransmissionMsec(const MeshPacket *p)
|
||||
static uint8_t bytes[MAX_RHPACKETLEN];
|
||||
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), Data_fields, &p->decoded);
|
||||
uint32_t packetAirtime = getPacketTime(numbytes + sizeof(PacketHeader));
|
||||
// Make sure enough time has elapsed for this packet to be sent and an ACK is received.
|
||||
// Make sure enough time has elapsed for this packet to be sent and an ACK is received.
|
||||
// DEBUG_MSG("Waiting for flooding message with airtime %d and slotTime is %d\n", packetAirtime, slotTimeMsec);
|
||||
float channelUtil = airTime->channelUtilizationPercent();
|
||||
uint8_t CWsize = map(channelUtil, 0, 100, CWmin, CWmax);
|
||||
@@ -189,7 +189,7 @@ uint32_t RadioInterface::getRetransmissionMsec(const MeshPacket *p)
|
||||
uint32_t RadioInterface::getTxDelayMsec()
|
||||
{
|
||||
/** We wait a random multiple of 'slotTimes' (see definition in header file) in order to avoid collisions.
|
||||
The pool to take a random multiple from is the contention window (CW), which size depends on the
|
||||
The pool to take a random multiple from is the contention window (CW), which size depends on the
|
||||
current channel utilization. */
|
||||
float channelUtil = airTime->channelUtilizationPercent();
|
||||
uint8_t CWsize = map(channelUtil, 0, 100, CWmin, CWmax);
|
||||
@@ -225,7 +225,7 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(float snr)
|
||||
|
||||
void printPacket(const char *prefix, const MeshPacket *p)
|
||||
{
|
||||
DEBUG_MSG("%s (id=0x%08x Fr0x%02x To0x%02x, WantAck%d, HopLim%d Ch0x%x", prefix, p->id, p->from & 0xff, p->to & 0xff,
|
||||
DEBUG_MSG("%s (id=0x%08x fr=0x%02x to=0x%02x, WantAck=%d, HopLim=%d Ch=0x%x", prefix, p->id, p->from & 0xff, p->to & 0xff,
|
||||
p->want_ack, p->hop_limit, p->channel);
|
||||
if (p->which_payload_variant == MeshPacket_decoded_tag) {
|
||||
auto &s = p->decoded;
|
||||
@@ -432,7 +432,7 @@ void RadioInterface::applyModemConfig()
|
||||
|
||||
// Set final tx_power back onto config
|
||||
loraConfig.tx_power = (int8_t)power; // cppcheck-suppress assignmentAddressToInteger
|
||||
|
||||
|
||||
// Calculate the number of channels
|
||||
uint32_t numChannels = floor((myRegion->freqEnd - myRegion->freqStart) / (myRegion->spacing + (bw / 1000)));
|
||||
|
||||
@@ -449,8 +449,9 @@ void RadioInterface::applyModemConfig()
|
||||
saveChannelNum(channel_num);
|
||||
saveFreq(freq + config.lora.frequency_offset);
|
||||
|
||||
DEBUG_MSG("Radio freq=%.3f, config.lora.frequency_offset=%.3f\n", freq, config.lora.frequency_offset);
|
||||
DEBUG_MSG("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d\n", myRegion->name, channelName, loraConfig.modem_preset, channel_num, power);
|
||||
DEBUG_MSG("Radio myRegion->freqStart / myRegion->freqEnd: %f -> %f (%f mhz)\n", myRegion->freqStart, myRegion->freqEnd, myRegion->freqEnd - myRegion->freqStart);
|
||||
DEBUG_MSG("Radio myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f mhz)\n", myRegion->freqStart, myRegion->freqEnd, myRegion->freqEnd - myRegion->freqStart);
|
||||
DEBUG_MSG("Radio myRegion->numChannels: %d x %.3fkHz\n", numChannels, bw);
|
||||
DEBUG_MSG("Radio channel_num: %d\n", channel_num);
|
||||
DEBUG_MSG("Radio frequency: %f\n", getFreq());
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "../concurrency/NotifiedWorkerThread.h"
|
||||
#include "MemoryPool.h"
|
||||
#include "MeshTypes.h"
|
||||
#include "Observer.h"
|
||||
@@ -97,6 +96,8 @@ class RadioInterface
|
||||
*/
|
||||
virtual bool canSleep() { return true; }
|
||||
|
||||
virtual bool wideLora() { return false; }
|
||||
|
||||
/// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep.
|
||||
virtual bool sleep() { return true; }
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "NodeDB.h"
|
||||
#include "SPILock.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include "error.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include <pb_decode.h>
|
||||
@@ -11,17 +12,6 @@
|
||||
// FIXME, we default to 4MHz SPI, SPI mode 0, check if the datasheet says it can really do that
|
||||
static SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
|
||||
void LockingModule::SPItransfer(uint8_t cmd, uint8_t reg, uint8_t *dataOut, uint8_t *dataIn, uint8_t numBytes)
|
||||
{
|
||||
concurrency::LockGuard g(spiLock);
|
||||
|
||||
Module::SPItransfer(cmd, reg, dataOut, dataIn, numBytes);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void LockingModule::SPIbeginTransaction()
|
||||
{
|
||||
spiLock->lock();
|
||||
@@ -36,8 +26,6 @@ void LockingModule::SPIendTransaction()
|
||||
Module::SPIendTransaction();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
|
||||
SPIClass &spi, PhysicalLayer *_iface)
|
||||
: NotifiedWorkerThread("RadioIf"), module(cs, irq, rst, busy, spi, spiSettings), iface(_iface)
|
||||
@@ -100,10 +88,8 @@ bool RadioLibInterface::canSendImmediately()
|
||||
if (busyTx && (millis() - lastTxStart > 60000)) {
|
||||
DEBUG_MSG("Hardware Failure! busyTx for more than 60s\n");
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_TRANSMIT_FAILED);
|
||||
#ifdef ARCH_ESP32
|
||||
if (busyTx && (millis() - lastTxStart > 65000)) // After 5s more, reboot
|
||||
ESP.restart();
|
||||
#endif
|
||||
// reboot in 5 seconds when this condition occurs.
|
||||
rebootAtMsec = lastTxStart + 65000;
|
||||
}
|
||||
if (busyRx)
|
||||
DEBUG_MSG("Can not send yet, busyRx\n");
|
||||
@@ -399,6 +385,7 @@ ErrorCode RadioLibInterface::send(MeshPacket *p)
|
||||
|
||||
int res = iface->startTransmit(radiobuf, numbytes);
|
||||
if (res != RADIOLIB_ERR_NONE) {
|
||||
DEBUG_MSG("startTransmit failed, error=%d\n", res);
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_RADIO_SPI_BUG);
|
||||
|
||||
// This send failed, but make sure to 'complete' it properly
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "../concurrency/OSThread.h"
|
||||
#include "concurrency/NotifiedWorkerThread.h"
|
||||
#include "RadioInterface.h"
|
||||
#include "MeshPacketQueue.h"
|
||||
|
||||
#define RADIOLIB_EXCLUDE_HTTP
|
||||
#include <RadioLib.h>
|
||||
|
||||
// ESP32 has special rules about ISR code
|
||||
@@ -41,12 +40,8 @@ class LockingModule : public Module
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef ARCH_PORTDUINO
|
||||
void SPItransfer(uint8_t cmd, uint8_t reg, uint8_t *dataOut, uint8_t *dataIn, uint8_t numBytes) override;
|
||||
#else
|
||||
void SPIbeginTransaction() override;
|
||||
void SPIendTransaction() override;
|
||||
#endif
|
||||
};
|
||||
|
||||
class RadioLibInterface : public RadioInterface, protected concurrency::NotifiedWorkerThread
|
||||
|
||||
@@ -90,13 +90,16 @@ void ReliableRouter::sniffReceived(const MeshPacket *p, const Routing *c)
|
||||
{
|
||||
NodeNum ourNode = getNodeNum();
|
||||
|
||||
if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability
|
||||
// - not DSR routing)
|
||||
if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability)
|
||||
if (p->want_ack) {
|
||||
if (MeshModule::currentReply)
|
||||
DEBUG_MSG("Some other module has replied to this message, no need for a 2nd ack\n");
|
||||
else
|
||||
sendAckNak(Routing_Error_NONE, getFrom(p), p->id, p->channel);
|
||||
if (p->which_payload_variant == MeshPacket_decoded_tag)
|
||||
sendAckNak(Routing_Error_NONE, getFrom(p), p->id, p->channel);
|
||||
else
|
||||
// Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded
|
||||
sendAckNak(Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex());
|
||||
}
|
||||
|
||||
// We consider an ack to be either a !routing packet with a request ID or a routing packet with !error
|
||||
@@ -196,8 +199,7 @@ int32_t ReliableRouter::doRetransmissions()
|
||||
DEBUG_MSG("Reliable send failed, returning a nak for fr=0x%x,to=0x%x,id=0x%x\n", p.packet->from, p.packet->to,
|
||||
p.packet->id);
|
||||
sendAckNak(Routing_Error_MAX_RETRANSMIT, getFrom(p.packet), p.packet->id, p.packet->channel);
|
||||
// Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived - which
|
||||
// allows the DSR version to still be able to look at the PendingPacket
|
||||
// Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived
|
||||
stopRetransmission(it->first);
|
||||
stillValid = false; // just deleted it
|
||||
} else {
|
||||
|
||||
@@ -38,12 +38,6 @@ struct PendingPacket {
|
||||
/** Starts at NUM_RETRANSMISSIONS -1(normally 3) and counts down. Once zero it will be removed from the list */
|
||||
uint8_t numRetransmissions = 0;
|
||||
|
||||
/** True if we have started trying to find a route - for DSR usage
|
||||
* While trying to find a route we don't actually send the data packet. We just leave it here pending until
|
||||
* we have a route or we've failed to find one.
|
||||
*/
|
||||
bool wantRoute = false;
|
||||
|
||||
PendingPacket() {}
|
||||
explicit PendingPacket(MeshPacket *p);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "Channels.h"
|
||||
#include "CryptoEngine.h"
|
||||
#include "NodeDB.h"
|
||||
#include "MeshRadio.h"
|
||||
#include "RTC.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
@@ -21,7 +22,6 @@ extern "C" {
|
||||
* DONE: Implement basic interface and use it elsewhere in app
|
||||
* Add naive flooding mixin (& drop duplicate rx broadcasts), add tools for sending broadcasts with incrementing sequence #s
|
||||
* Add an optional adjacent node only 'send with ack' mixin. If we timeout waiting for the ack, call handleAckTimeout(packet)
|
||||
* Add DSR mixin
|
||||
*
|
||||
**/
|
||||
|
||||
@@ -188,6 +188,18 @@ ErrorCode Router::send(MeshPacket *p)
|
||||
{
|
||||
assert(p->to != nodeDB.getNodeNum()); // should have already been handled by sendLocal
|
||||
|
||||
// Abort sending if we are violating the duty cycle
|
||||
if (!config.lora.override_duty_cycle && myRegion->dutyCycle != 100) {
|
||||
float hourlyTxPercent = airTime->utilizationTXPercent();
|
||||
if (hourlyTxPercent > myRegion->dutyCycle) {
|
||||
uint8_t silentMinutes = airTime->getSilentMinutes(hourlyTxPercent, myRegion->dutyCycle);
|
||||
DEBUG_MSG("WARNING: Duty cycle limit exceeded. Aborting send for now, you can send again in %d minutes.\n", silentMinutes);
|
||||
Routing_Error err = Routing_Error_DUTY_CYCLE_LIMIT;
|
||||
abortSendAndNak(err, p);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// PacketId nakId = p->decoded.which_ackVariant == SubPacket_fail_id_tag ? p->decoded.ackVariant.fail_id : 0;
|
||||
// assert(!nakId); // I don't think we ever send 0hop naks over the wire (other than to the phone), test that assumption with
|
||||
// assert
|
||||
|
||||
@@ -12,6 +12,7 @@ SX126xInterface<T>::SX126xInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq,
|
||||
SPIClass &spi)
|
||||
: RadioLibInterface(cs, irq, rst, busy, spi, &lora), lora(&module)
|
||||
{
|
||||
DEBUG_MSG("SX126xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
|
||||
}
|
||||
|
||||
/// Initialise the Driver transport hardware and software.
|
||||
@@ -25,11 +26,11 @@ bool SX126xInterface<T>::init()
|
||||
pinMode(SX126X_POWER_EN, OUTPUT);
|
||||
#endif
|
||||
|
||||
#ifdef SX126X_RXEN // set not rx or tx mode
|
||||
#if defined(SX126X_RXEN) && (SX126X_RXEN != RADIOLIB_NC) // set not rx or tx mode
|
||||
digitalWrite(SX126X_RXEN, LOW); // Set low before becoming an output
|
||||
pinMode(SX126X_RXEN, OUTPUT);
|
||||
#endif
|
||||
#ifdef SX126X_TXEN
|
||||
#if defined(SX126X_TXEN) && (SX126X_TXEN != RADIOLIB_NC)
|
||||
digitalWrite(SX126X_TXEN, LOW);
|
||||
pinMode(SX126X_TXEN, OUTPUT);
|
||||
#endif
|
||||
@@ -56,9 +57,9 @@ bool SX126xInterface<T>::init()
|
||||
// \todo Display actual typename of the adapter, not just `SX126x`
|
||||
DEBUG_MSG("SX126x init result %d\n", res);
|
||||
|
||||
DEBUG_MSG("Frequency set to %f\n", getFreq());
|
||||
DEBUG_MSG("Bandwidth set to %f\n", bw);
|
||||
DEBUG_MSG("Power output set to %d\n", power);
|
||||
DEBUG_MSG("Frequency set to %f\n", getFreq());
|
||||
DEBUG_MSG("Bandwidth set to %f\n", bw);
|
||||
DEBUG_MSG("Power output set to %d\n", power);
|
||||
|
||||
// current limit was removed from module' ctor
|
||||
// override default value (60 mA)
|
||||
@@ -66,10 +67,10 @@ bool SX126xInterface<T>::init()
|
||||
DEBUG_MSG("Current limit set to %f\n", currentLimit);
|
||||
DEBUG_MSG("Current limit set result %d\n", res);
|
||||
|
||||
#ifdef SX126X_TXEN
|
||||
#if defined(SX126X_TXEN) && (SX126X_TXEN != RADIOLIB_NC)
|
||||
// lora.begin sets Dio2 as RF switch control, which is not true if we are manually controlling RX and TX
|
||||
if (res == RADIOLIB_ERR_NONE)
|
||||
res = lora.setDio2AsRfSwitch(true);
|
||||
res = lora.setDio2AsRfSwitch(false);
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
@@ -81,11 +82,11 @@ bool SX126xInterface<T>::init()
|
||||
|
||||
//if(crcLSB != 0x0f)
|
||||
// RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
||||
|
||||
|
||||
crcLSB = 0x5a;
|
||||
err = lora.writeRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1);
|
||||
if(err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_SX1262Failure);
|
||||
|
||||
err = lora.readRegister(SX126X_REG_CRC_POLYNOMIAL_LSB, &crcLSB, 1);
|
||||
if(err != RADIOLIB_ERR_NONE)
|
||||
@@ -144,8 +145,9 @@ bool SX126xInterface<T>::reconfigure()
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
if (power > 22) // This chip has lower power limits than some
|
||||
power = 22;
|
||||
if (power > SX126X_MAX_POWER) // This chip has lower power limits than some
|
||||
power = SX126X_MAX_POWER;
|
||||
|
||||
err = lora.setOutputPower(power);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
@@ -164,14 +166,18 @@ template<typename T>
|
||||
void SX126xInterface<T>::setStandby()
|
||||
{
|
||||
checkNotification(); // handle any pending interrupts before we force standby
|
||||
|
||||
|
||||
int err = lora.standby();
|
||||
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
DEBUG_MSG("SX126x standby failed with error %d\n", err);
|
||||
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
#ifdef SX126X_RXEN // we have RXEN/TXEN control - turn off RX and TX power
|
||||
#if defined(SX126X_RXEN) && (SX126X_RXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn off RX and TX power
|
||||
digitalWrite(SX126X_RXEN, LOW);
|
||||
#endif
|
||||
#ifdef SX126X_TXEN
|
||||
#if defined(SX126X_TXEN) && (SX126X_TXEN != RADIOLIB_NC)
|
||||
digitalWrite(SX126X_TXEN, LOW);
|
||||
#endif
|
||||
|
||||
@@ -196,10 +202,10 @@ void SX126xInterface<T>::addReceiveMetadata(MeshPacket *mp)
|
||||
template<typename T>
|
||||
void SX126xInterface<T>::configHardwareForSend()
|
||||
{
|
||||
#ifdef SX126X_TXEN // we have RXEN/TXEN control - turn on TX power / off RX power
|
||||
#if defined(SX126X_TXEN) && (SX126X_TXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn on TX power / off RX power
|
||||
digitalWrite(SX126X_TXEN, HIGH);
|
||||
#endif
|
||||
#ifdef SX126X_RXEN
|
||||
#if defined(SX126X_RXEN) && (SX126X_RXEN != RADIOLIB_NC)
|
||||
digitalWrite(SX126X_RXEN, LOW);
|
||||
#endif
|
||||
|
||||
@@ -218,13 +224,13 @@ void SX126xInterface<T>::startReceive()
|
||||
|
||||
setStandby();
|
||||
|
||||
#ifdef SX126X_RXEN // we have RXEN/TXEN control - turn on RX power / off TX power
|
||||
#if defined(SX126X_RXEN) && (SX126X_RXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn on RX power / off TX power
|
||||
digitalWrite(SX126X_RXEN, HIGH);
|
||||
#endif
|
||||
#ifdef SX126X_TXEN
|
||||
#if defined(SX126X_TXEN) && (SX126X_TXEN != RADIOLIB_NC)
|
||||
digitalWrite(SX126X_TXEN, LOW);
|
||||
#endif
|
||||
|
||||
|
||||
// int err = lora.startReceive();
|
||||
int err = lora.startReceiveDutyCycleAuto(); // We use a 32 bit preamble so this should save some power by letting radio sit in
|
||||
// standby mostly.
|
||||
@@ -244,13 +250,13 @@ bool SX126xInterface<T>::isChannelActive()
|
||||
// check if we can detect a LoRa preamble on the current channel
|
||||
int16_t result;
|
||||
|
||||
setStandby();
|
||||
setStandby();
|
||||
result = lora.scanChannel();
|
||||
if (result == RADIOLIB_PREAMBLE_DETECTED)
|
||||
if (result == RADIOLIB_PREAMBLE_DETECTED)
|
||||
return true;
|
||||
|
||||
|
||||
assert(result != RADIOLIB_ERR_WRONG_MODEM);
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,8 @@
|
||||
#include "SX1280Interface.h"
|
||||
#include "error.h"
|
||||
|
||||
#if !defined(ARCH_PORTDUINO)
|
||||
|
||||
SX1280Interface::SX1280Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy,
|
||||
SPIClass &spi)
|
||||
: SX128xInterface(cs, irq, rst, busy, spi)
|
||||
{
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -6,12 +6,9 @@
|
||||
* Our adapter for SX1280 radios
|
||||
*/
|
||||
|
||||
#if !defined(ARCH_PORTDUINO)
|
||||
|
||||
class SX1280Interface : public SX128xInterface<SX1280>
|
||||
{
|
||||
public:
|
||||
SX1280Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi);
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -2,8 +2,6 @@
|
||||
#include "SX128xInterface.h"
|
||||
#include "error.h"
|
||||
|
||||
#if !defined(ARCH_PORTDUINO)
|
||||
|
||||
// Particular boards might define a different max power based on what their hardware can do
|
||||
#ifndef SX128X_MAX_POWER
|
||||
#define SX128X_MAX_POWER 13
|
||||
@@ -27,11 +25,11 @@ bool SX128xInterface<T>::init()
|
||||
pinMode(SX128X_POWER_EN, OUTPUT);
|
||||
#endif
|
||||
|
||||
#ifdef SX128X_RXEN // set not rx or tx mode
|
||||
#if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // set not rx or tx mode
|
||||
digitalWrite(SX128X_RXEN, LOW); // Set low before becoming an output
|
||||
pinMode(SX128X_RXEN, OUTPUT);
|
||||
#endif
|
||||
#ifdef SX128X_TXEN
|
||||
#if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC)
|
||||
digitalWrite(SX128X_TXEN, LOW);
|
||||
pinMode(SX128X_TXEN, OUTPUT);
|
||||
#endif
|
||||
@@ -46,6 +44,8 @@ bool SX128xInterface<T>::init()
|
||||
|
||||
limitPower();
|
||||
|
||||
preambleLength = 12; // 12 is the default for this chip, 32 does not RX at all
|
||||
|
||||
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength);
|
||||
// \todo Display actual typename of the adapter, not just `SX128x`
|
||||
DEBUG_MSG("SX128x init result %d\n", res);
|
||||
@@ -54,12 +54,6 @@ bool SX128xInterface<T>::init()
|
||||
DEBUG_MSG("Bandwidth set to %f\n", bw);
|
||||
DEBUG_MSG("Power output set to %d\n", power);
|
||||
|
||||
#ifdef SX128X_TXEN
|
||||
// lora.begin sets Dio2 as RF switch control, which is not true if we are manually controlling RX and TX
|
||||
if (res == RADIOLIB_ERR_NONE)
|
||||
res = lora.setDio2AsRfSwitch(true);
|
||||
#endif
|
||||
|
||||
if (res == RADIOLIB_ERR_NONE)
|
||||
res = lora.setCRC(2);
|
||||
|
||||
@@ -105,8 +99,9 @@ bool SX128xInterface<T>::reconfigure()
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
RECORD_CRITICALERROR(CriticalErrorCode_INVALID_RADIO_SETTING);
|
||||
|
||||
if (power > 22) // This chip has lower power limits than some
|
||||
power = 22;
|
||||
if (power > SX128X_MAX_POWER) // This chip has lower power limits than some
|
||||
power = SX128X_MAX_POWER;
|
||||
|
||||
err = lora.setOutputPower(power);
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
@@ -121,18 +116,28 @@ void INTERRUPT_ATTR SX128xInterface<T>::disableInterrupt()
|
||||
lora.clearDio1Action();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool SX128xInterface<T>::wideLora()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void SX128xInterface<T>::setStandby()
|
||||
{
|
||||
checkNotification(); // handle any pending interrupts before we force standby
|
||||
|
||||
int err = lora.standby();
|
||||
|
||||
if (err != RADIOLIB_ERR_NONE)
|
||||
DEBUG_MSG("SX128x standby failed with error %d\n", err);
|
||||
|
||||
assert(err == RADIOLIB_ERR_NONE);
|
||||
|
||||
#ifdef SX128X_RXEN // we have RXEN/TXEN control - turn off RX and TX power
|
||||
#if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn off RX and TX power
|
||||
digitalWrite(SX128X_RXEN, LOW);
|
||||
#endif
|
||||
#ifdef SX128X_TXEN
|
||||
#if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC)
|
||||
digitalWrite(SX128X_TXEN, LOW);
|
||||
#endif
|
||||
|
||||
@@ -157,10 +162,10 @@ void SX128xInterface<T>::addReceiveMetadata(MeshPacket *mp)
|
||||
template<typename T>
|
||||
void SX128xInterface<T>::configHardwareForSend()
|
||||
{
|
||||
#ifdef SX128X_TXEN // we have RXEN/TXEN control - turn on TX power / off RX power
|
||||
#if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn on TX power / off RX power
|
||||
digitalWrite(SX128X_TXEN, HIGH);
|
||||
#endif
|
||||
#ifdef SX128X_RXEN
|
||||
#if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC)
|
||||
digitalWrite(SX128X_RXEN, LOW);
|
||||
#endif
|
||||
|
||||
@@ -179,10 +184,10 @@ void SX128xInterface<T>::startReceive()
|
||||
|
||||
setStandby();
|
||||
|
||||
#ifdef SX128X_RXEN // we have RXEN/TXEN control - turn on RX power / off TX power
|
||||
#if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn on RX power / off TX power
|
||||
digitalWrite(SX128X_RXEN, HIGH);
|
||||
#endif
|
||||
#ifdef SX128X_TXEN
|
||||
#if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC)
|
||||
digitalWrite(SX128X_TXEN, LOW);
|
||||
#endif
|
||||
|
||||
@@ -218,7 +223,13 @@ bool SX128xInterface<T>::isChannelActive()
|
||||
template<typename T>
|
||||
bool SX128xInterface<T>::isActivelyReceiving()
|
||||
{
|
||||
#ifdef RADIOLIB_GODMODE
|
||||
uint16_t irq = lora.getIrqStatus();
|
||||
bool hasPreamble = (irq & RADIOLIB_SX128X_IRQ_HEADER_VALID);
|
||||
return hasPreamble;
|
||||
#else
|
||||
return isChannelActive();
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
@@ -243,5 +254,3 @@ bool SX128xInterface<T>::sleep()
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,7 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#if !defined(ARCH_PORTDUINO)
|
||||
|
||||
#include "RadioLibInterface.h"
|
||||
|
||||
/**
|
||||
@@ -19,6 +17,8 @@ class SX128xInterface : public RadioLibInterface
|
||||
/// \return true if initialisation succeeded.
|
||||
virtual bool init() override;
|
||||
|
||||
virtual bool wideLora() override;
|
||||
|
||||
/// Apply any radio provisioning changes
|
||||
/// Make sure the Driver is properly configured before calling init().
|
||||
/// \return true if initialisation succeeded.
|
||||
@@ -27,9 +27,11 @@ class SX128xInterface : public RadioLibInterface
|
||||
/// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep.
|
||||
virtual bool sleep() override;
|
||||
|
||||
protected:
|
||||
#ifdef RADIOLIB_GODMODE
|
||||
bool isIRQPending() override { return lora.getIrqStatus() != 0; }
|
||||
#endif
|
||||
|
||||
float currentLimit = 140; // Higher OCP limit for SX128x PA
|
||||
protected:
|
||||
|
||||
/**
|
||||
* Specific module instance
|
||||
@@ -71,5 +73,3 @@ class SX128xInterface : public RadioLibInterface
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -82,10 +82,11 @@ typedef struct _AdminMessage {
|
||||
ModuleConfig set_module_config;
|
||||
/* Set the Canned Message Module messages text. */
|
||||
char set_canned_message_module_messages[201];
|
||||
/* Sent immediatly after a config change has been sent to ensure comms, if this is not recieved, the config will be reverted after 10 mins */
|
||||
bool confirm_set_config;
|
||||
/* Sent immediatly after a config change has been sent to ensure comms, if this is not recieved, the config will be reverted after 10 mins */
|
||||
bool confirm_set_module_config;
|
||||
/* Begins an edit transaction for config, module config, owner, and channel settings changes
|
||||
This will delay the standard *implicit* save to the file system and subsequent reboot behavior until committed (commit_edit_settings) */
|
||||
bool begin_edit_settings;
|
||||
/* Commits an open transaction for any edits made to config, module config, owner, and channel settings */
|
||||
bool commit_edit_settings;
|
||||
/* Setting channels/radio config remotely carries the risk that you might send an invalid config and the radio never talks to your mesh again.
|
||||
Therefore if setting either of these properties remotely, you must send a confirm_xxx message within 10 minutes.
|
||||
If you fail to do so, the radio will assume loss of comms and revert your changes.
|
||||
@@ -147,8 +148,8 @@ extern "C" {
|
||||
#define AdminMessage_set_config_tag 34
|
||||
#define AdminMessage_set_module_config_tag 35
|
||||
#define AdminMessage_set_canned_message_module_messages_tag 36
|
||||
#define AdminMessage_confirm_set_config_tag 64
|
||||
#define AdminMessage_confirm_set_module_config_tag 65
|
||||
#define AdminMessage_begin_edit_settings_tag 64
|
||||
#define AdminMessage_commit_edit_settings_tag 65
|
||||
#define AdminMessage_confirm_set_channel_tag 66
|
||||
#define AdminMessage_confirm_set_radio_tag 67
|
||||
#define AdminMessage_reboot_ota_seconds_tag 95
|
||||
@@ -177,8 +178,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_channel,set_channel), 3
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_config,set_config), 34) \
|
||||
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_module_config,set_module_config), 35) \
|
||||
X(a, STATIC, ONEOF, STRING, (payload_variant,set_canned_message_module_messages,set_canned_message_module_messages), 36) \
|
||||
X(a, STATIC, ONEOF, BOOL, (payload_variant,confirm_set_config,confirm_set_config), 64) \
|
||||
X(a, STATIC, ONEOF, BOOL, (payload_variant,confirm_set_module_config,confirm_set_module_config), 65) \
|
||||
X(a, STATIC, ONEOF, BOOL, (payload_variant,begin_edit_settings,begin_edit_settings), 64) \
|
||||
X(a, STATIC, ONEOF, BOOL, (payload_variant,commit_edit_settings,commit_edit_settings), 65) \
|
||||
X(a, STATIC, ONEOF, BOOL, (payload_variant,confirm_set_channel,confirm_set_channel), 66) \
|
||||
X(a, STATIC, ONEOF, BOOL, (payload_variant,confirm_set_radio,confirm_set_radio), 67) \
|
||||
X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_ota_seconds,reboot_ota_seconds), 95) \
|
||||
|
||||
@@ -54,7 +54,7 @@ extern const pb_msgdesc_t ChannelSet_msg;
|
||||
#define ChannelSet_fields &ChannelSet_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define ChannelSet_size 582
|
||||
#define ChannelSet_size 584
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -100,6 +100,8 @@ typedef struct _Config_DeviceConfig {
|
||||
Config_DeviceConfig_Role role;
|
||||
bool serial_enabled;
|
||||
bool debug_log_enabled;
|
||||
uint32_t button_gpio;
|
||||
uint32_t buzzer_gpio;
|
||||
} Config_DeviceConfig;
|
||||
|
||||
typedef struct _Config_DisplayConfig {
|
||||
@@ -124,6 +126,7 @@ typedef struct _Config_LoRaConfig {
|
||||
bool tx_enabled;
|
||||
int8_t tx_power;
|
||||
uint16_t channel_num;
|
||||
bool override_duty_cycle;
|
||||
pb_size_t ignore_incoming_count;
|
||||
uint32_t ignore_incoming[3];
|
||||
} Config_LoRaConfig;
|
||||
@@ -143,6 +146,8 @@ typedef struct _Config_PositionConfig {
|
||||
uint32_t gps_update_interval;
|
||||
uint32_t gps_attempt_time;
|
||||
uint32_t position_flags;
|
||||
uint32_t rx_gpio;
|
||||
uint32_t tx_gpio;
|
||||
} Config_PositionConfig;
|
||||
|
||||
typedef struct _Config_PowerConfig {
|
||||
@@ -225,22 +230,22 @@ extern "C" {
|
||||
|
||||
/* Initializer values for message structs */
|
||||
#define Config_init_default {0, {Config_DeviceConfig_init_default}}
|
||||
#define Config_DeviceConfig_init_default {_Config_DeviceConfig_Role_MIN, 0, 0}
|
||||
#define Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0}
|
||||
#define Config_DeviceConfig_init_default {_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0}
|
||||
#define Config_PositionConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define Config_NetworkConfig_init_default {0, "", "", "", 0, _Config_NetworkConfig_EthMode_MIN, false, Config_NetworkConfig_IpV4Config_init_default}
|
||||
#define Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0}
|
||||
#define Config_DisplayConfig_init_default {0, _Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _Config_DisplayConfig_DisplayUnits_MIN, _Config_DisplayConfig_OledType_MIN}
|
||||
#define Config_LoRaConfig_init_default {0, _Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, {0, 0, 0}}
|
||||
#define Config_LoRaConfig_init_default {0, _Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, {0, 0, 0}}
|
||||
#define Config_BluetoothConfig_init_default {0, _Config_BluetoothConfig_PairingMode_MIN, 0}
|
||||
#define Config_init_zero {0, {Config_DeviceConfig_init_zero}}
|
||||
#define Config_DeviceConfig_init_zero {_Config_DeviceConfig_Role_MIN, 0, 0}
|
||||
#define Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0}
|
||||
#define Config_DeviceConfig_init_zero {_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0}
|
||||
#define Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define Config_NetworkConfig_init_zero {0, "", "", "", 0, _Config_NetworkConfig_EthMode_MIN, false, Config_NetworkConfig_IpV4Config_init_zero}
|
||||
#define Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0}
|
||||
#define Config_DisplayConfig_init_zero {0, _Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _Config_DisplayConfig_DisplayUnits_MIN, _Config_DisplayConfig_OledType_MIN}
|
||||
#define Config_LoRaConfig_init_zero {0, _Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, {0, 0, 0}}
|
||||
#define Config_LoRaConfig_init_zero {0, _Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, {0, 0, 0}}
|
||||
#define Config_BluetoothConfig_init_zero {0, _Config_BluetoothConfig_PairingMode_MIN, 0}
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
@@ -250,6 +255,8 @@ extern "C" {
|
||||
#define Config_DeviceConfig_role_tag 1
|
||||
#define Config_DeviceConfig_serial_enabled_tag 2
|
||||
#define Config_DeviceConfig_debug_log_enabled_tag 3
|
||||
#define Config_DeviceConfig_button_gpio_tag 4
|
||||
#define Config_DeviceConfig_buzzer_gpio_tag 5
|
||||
#define Config_DisplayConfig_screen_on_secs_tag 1
|
||||
#define Config_DisplayConfig_gps_format_tag 2
|
||||
#define Config_DisplayConfig_auto_screen_carousel_secs_tag 3
|
||||
@@ -268,6 +275,7 @@ extern "C" {
|
||||
#define Config_LoRaConfig_tx_enabled_tag 9
|
||||
#define Config_LoRaConfig_tx_power_tag 10
|
||||
#define Config_LoRaConfig_channel_num_tag 11
|
||||
#define Config_LoRaConfig_override_duty_cycle_tag 12
|
||||
#define Config_LoRaConfig_ignore_incoming_tag 103
|
||||
#define Config_NetworkConfig_IpV4Config_ip_tag 1
|
||||
#define Config_NetworkConfig_IpV4Config_gateway_tag 2
|
||||
@@ -280,6 +288,8 @@ extern "C" {
|
||||
#define Config_PositionConfig_gps_update_interval_tag 5
|
||||
#define Config_PositionConfig_gps_attempt_time_tag 6
|
||||
#define Config_PositionConfig_position_flags_tag 7
|
||||
#define Config_PositionConfig_rx_gpio_tag 8
|
||||
#define Config_PositionConfig_tx_gpio_tag 9
|
||||
#define Config_PowerConfig_is_power_saving_tag 1
|
||||
#define Config_PowerConfig_on_battery_shutdown_after_secs_tag 2
|
||||
#define Config_PowerConfig_adc_multiplier_override_tag 3
|
||||
@@ -325,7 +335,9 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,bluetooth,payload_variant.bl
|
||||
#define Config_DeviceConfig_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, UENUM, role, 1) \
|
||||
X(a, STATIC, SINGULAR, BOOL, serial_enabled, 2) \
|
||||
X(a, STATIC, SINGULAR, BOOL, debug_log_enabled, 3)
|
||||
X(a, STATIC, SINGULAR, BOOL, debug_log_enabled, 3) \
|
||||
X(a, STATIC, SINGULAR, UINT32, button_gpio, 4) \
|
||||
X(a, STATIC, SINGULAR, UINT32, buzzer_gpio, 5)
|
||||
#define Config_DeviceConfig_CALLBACK NULL
|
||||
#define Config_DeviceConfig_DEFAULT NULL
|
||||
|
||||
@@ -336,7 +348,9 @@ X(a, STATIC, SINGULAR, BOOL, fixed_position, 3) \
|
||||
X(a, STATIC, SINGULAR, BOOL, gps_enabled, 4) \
|
||||
X(a, STATIC, SINGULAR, UINT32, gps_update_interval, 5) \
|
||||
X(a, STATIC, SINGULAR, UINT32, gps_attempt_time, 6) \
|
||||
X(a, STATIC, SINGULAR, UINT32, position_flags, 7)
|
||||
X(a, STATIC, SINGULAR, UINT32, position_flags, 7) \
|
||||
X(a, STATIC, SINGULAR, UINT32, rx_gpio, 8) \
|
||||
X(a, STATIC, SINGULAR, UINT32, tx_gpio, 9)
|
||||
#define Config_PositionConfig_CALLBACK NULL
|
||||
#define Config_PositionConfig_DEFAULT NULL
|
||||
|
||||
@@ -395,6 +409,7 @@ X(a, STATIC, SINGULAR, UINT32, hop_limit, 8) \
|
||||
X(a, STATIC, SINGULAR, BOOL, tx_enabled, 9) \
|
||||
X(a, STATIC, SINGULAR, INT32, tx_power, 10) \
|
||||
X(a, STATIC, SINGULAR, UINT32, channel_num, 11) \
|
||||
X(a, STATIC, SINGULAR, BOOL, override_duty_cycle, 12) \
|
||||
X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103)
|
||||
#define Config_LoRaConfig_CALLBACK NULL
|
||||
#define Config_LoRaConfig_DEFAULT NULL
|
||||
@@ -429,12 +444,12 @@ extern const pb_msgdesc_t Config_BluetoothConfig_msg;
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define Config_BluetoothConfig_size 10
|
||||
#define Config_DeviceConfig_size 6
|
||||
#define Config_DeviceConfig_size 18
|
||||
#define Config_DisplayConfig_size 22
|
||||
#define Config_LoRaConfig_size 68
|
||||
#define Config_LoRaConfig_size 70
|
||||
#define Config_NetworkConfig_IpV4Config_size 20
|
||||
#define Config_NetworkConfig_size 161
|
||||
#define Config_PositionConfig_size 30
|
||||
#define Config_PositionConfig_size 42
|
||||
#define Config_PowerConfig_size 43
|
||||
#define Config_size 164
|
||||
|
||||
|
||||
@@ -150,8 +150,8 @@ extern const pb_msgdesc_t LocalModuleConfig_msg;
|
||||
#define LocalModuleConfig_fields &LocalModuleConfig_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define LocalConfig_size 361
|
||||
#define LocalModuleConfig_size 294
|
||||
#define LocalConfig_size 387
|
||||
#define LocalModuleConfig_size 358
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -185,9 +185,11 @@ typedef enum _Routing_Error {
|
||||
/* TODO: REPLACE */
|
||||
Routing_Error_NO_RESPONSE = 8,
|
||||
/* TODO: REPLACE */
|
||||
Routing_Error_BAD_REQUEST = 32,
|
||||
Routing_Error_DUTY_CYCLE_LIMIT = 9,
|
||||
/* The new version of the heltec WiFi_Lora_32_V2 board that has battery sensing hooked to GPIO 37.
|
||||
Sadly they did not update anything on the silkscreen to identify this board */
|
||||
Routing_Error_BAD_REQUEST = 32,
|
||||
/* Ancient heltec WiFi_Lora_32 board */
|
||||
Routing_Error_NOT_AUTHORIZED = 33
|
||||
} Routing_Error;
|
||||
|
||||
|
||||
@@ -63,10 +63,12 @@ typedef enum _ModuleConfig_CannedMessageConfig_InputEventChar {
|
||||
/* Struct definitions */
|
||||
typedef struct _ModuleConfig_AudioConfig {
|
||||
bool codec2_enabled;
|
||||
uint32_t mic_chan;
|
||||
uint32_t amp_pin;
|
||||
uint32_t ptt_pin;
|
||||
uint8_t ptt_pin;
|
||||
ModuleConfig_AudioConfig_Audio_Baud bitrate;
|
||||
uint8_t i2s_ws;
|
||||
uint8_t i2s_sd;
|
||||
uint8_t i2s_din;
|
||||
uint8_t i2s_sck;
|
||||
} ModuleConfig_AudioConfig;
|
||||
|
||||
typedef struct _ModuleConfig_CannedMessageConfig {
|
||||
@@ -90,13 +92,14 @@ typedef struct _ModuleConfig_ExternalNotificationConfig {
|
||||
bool active;
|
||||
bool alert_message;
|
||||
bool alert_bell;
|
||||
bool use_pwm;
|
||||
} ModuleConfig_ExternalNotificationConfig;
|
||||
|
||||
typedef struct _ModuleConfig_MQTTConfig {
|
||||
bool enabled;
|
||||
char address[32];
|
||||
char username[32];
|
||||
char password[32];
|
||||
char username[64];
|
||||
char password[64];
|
||||
bool encryption_enabled;
|
||||
bool json_enabled;
|
||||
} ModuleConfig_MQTTConfig;
|
||||
@@ -182,18 +185,18 @@ extern "C" {
|
||||
/* Initializer values for message structs */
|
||||
#define ModuleConfig_init_default {0, {ModuleConfig_MQTTConfig_init_default}}
|
||||
#define ModuleConfig_MQTTConfig_init_default {0, "", "", "", 0, 0}
|
||||
#define ModuleConfig_AudioConfig_init_default {0, 0, 0, 0, _ModuleConfig_AudioConfig_Audio_Baud_MIN}
|
||||
#define ModuleConfig_AudioConfig_init_default {0, 0, _ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0}
|
||||
#define ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _ModuleConfig_SerialConfig_Serial_Mode_MIN}
|
||||
#define ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0}
|
||||
#define ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0}
|
||||
#define ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0}
|
||||
#define ModuleConfig_RangeTestConfig_init_default {0, 0, 0}
|
||||
#define ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0}
|
||||
#define ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
|
||||
#define ModuleConfig_init_zero {0, {ModuleConfig_MQTTConfig_init_zero}}
|
||||
#define ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0}
|
||||
#define ModuleConfig_AudioConfig_init_zero {0, 0, 0, 0, _ModuleConfig_AudioConfig_Audio_Baud_MIN}
|
||||
#define ModuleConfig_AudioConfig_init_zero {0, 0, _ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0}
|
||||
#define ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _ModuleConfig_SerialConfig_Serial_Mode_MIN}
|
||||
#define ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0}
|
||||
#define ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0}
|
||||
#define ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0}
|
||||
#define ModuleConfig_RangeTestConfig_init_zero {0, 0, 0}
|
||||
#define ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0}
|
||||
@@ -201,10 +204,12 @@ extern "C" {
|
||||
|
||||
/* Field tags (for use in manual encoding/decoding) */
|
||||
#define ModuleConfig_AudioConfig_codec2_enabled_tag 1
|
||||
#define ModuleConfig_AudioConfig_mic_chan_tag 2
|
||||
#define ModuleConfig_AudioConfig_amp_pin_tag 3
|
||||
#define ModuleConfig_AudioConfig_ptt_pin_tag 4
|
||||
#define ModuleConfig_AudioConfig_bitrate_tag 5
|
||||
#define ModuleConfig_AudioConfig_ptt_pin_tag 2
|
||||
#define ModuleConfig_AudioConfig_bitrate_tag 3
|
||||
#define ModuleConfig_AudioConfig_i2s_ws_tag 4
|
||||
#define ModuleConfig_AudioConfig_i2s_sd_tag 5
|
||||
#define ModuleConfig_AudioConfig_i2s_din_tag 6
|
||||
#define ModuleConfig_AudioConfig_i2s_sck_tag 7
|
||||
#define ModuleConfig_CannedMessageConfig_rotary1_enabled_tag 1
|
||||
#define ModuleConfig_CannedMessageConfig_inputbroker_pin_a_tag 2
|
||||
#define ModuleConfig_CannedMessageConfig_inputbroker_pin_b_tag 3
|
||||
@@ -222,6 +227,7 @@ extern "C" {
|
||||
#define ModuleConfig_ExternalNotificationConfig_active_tag 4
|
||||
#define ModuleConfig_ExternalNotificationConfig_alert_message_tag 5
|
||||
#define ModuleConfig_ExternalNotificationConfig_alert_bell_tag 6
|
||||
#define ModuleConfig_ExternalNotificationConfig_use_pwm_tag 7
|
||||
#define ModuleConfig_MQTTConfig_enabled_tag 1
|
||||
#define ModuleConfig_MQTTConfig_address_tag 2
|
||||
#define ModuleConfig_MQTTConfig_username_tag 3
|
||||
@@ -290,10 +296,12 @@ X(a, STATIC, SINGULAR, BOOL, json_enabled, 6)
|
||||
|
||||
#define ModuleConfig_AudioConfig_FIELDLIST(X, a) \
|
||||
X(a, STATIC, SINGULAR, BOOL, codec2_enabled, 1) \
|
||||
X(a, STATIC, SINGULAR, UINT32, mic_chan, 2) \
|
||||
X(a, STATIC, SINGULAR, UINT32, amp_pin, 3) \
|
||||
X(a, STATIC, SINGULAR, UINT32, ptt_pin, 4) \
|
||||
X(a, STATIC, SINGULAR, UENUM, bitrate, 5)
|
||||
X(a, STATIC, SINGULAR, UINT32, ptt_pin, 2) \
|
||||
X(a, STATIC, SINGULAR, UENUM, bitrate, 3) \
|
||||
X(a, STATIC, SINGULAR, UINT32, i2s_ws, 4) \
|
||||
X(a, STATIC, SINGULAR, UINT32, i2s_sd, 5) \
|
||||
X(a, STATIC, SINGULAR, UINT32, i2s_din, 6) \
|
||||
X(a, STATIC, SINGULAR, UINT32, i2s_sck, 7)
|
||||
#define ModuleConfig_AudioConfig_CALLBACK NULL
|
||||
#define ModuleConfig_AudioConfig_DEFAULT NULL
|
||||
|
||||
@@ -314,7 +322,8 @@ X(a, STATIC, SINGULAR, UINT32, output_ms, 2) \
|
||||
X(a, STATIC, SINGULAR, UINT32, output, 3) \
|
||||
X(a, STATIC, SINGULAR, BOOL, active, 4) \
|
||||
X(a, STATIC, SINGULAR, BOOL, alert_message, 5) \
|
||||
X(a, STATIC, SINGULAR, BOOL, alert_bell, 6)
|
||||
X(a, STATIC, SINGULAR, BOOL, alert_bell, 6) \
|
||||
X(a, STATIC, SINGULAR, BOOL, use_pwm, 7)
|
||||
#define ModuleConfig_ExternalNotificationConfig_CALLBACK NULL
|
||||
#define ModuleConfig_ExternalNotificationConfig_DEFAULT NULL
|
||||
|
||||
@@ -380,15 +389,15 @@ extern const pb_msgdesc_t ModuleConfig_CannedMessageConfig_msg;
|
||||
#define ModuleConfig_CannedMessageConfig_fields &ModuleConfig_CannedMessageConfig_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define ModuleConfig_AudioConfig_size 22
|
||||
#define ModuleConfig_AudioConfig_size 19
|
||||
#define ModuleConfig_CannedMessageConfig_size 49
|
||||
#define ModuleConfig_ExternalNotificationConfig_size 20
|
||||
#define ModuleConfig_MQTTConfig_size 105
|
||||
#define ModuleConfig_ExternalNotificationConfig_size 22
|
||||
#define ModuleConfig_MQTTConfig_size 169
|
||||
#define ModuleConfig_RangeTestConfig_size 10
|
||||
#define ModuleConfig_SerialConfig_size 26
|
||||
#define ModuleConfig_StoreForwardConfig_size 22
|
||||
#define ModuleConfig_TelemetryConfig_size 18
|
||||
#define ModuleConfig_size 107
|
||||
#define ModuleConfig_size 172
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -82,6 +82,9 @@ typedef enum _PortNum {
|
||||
Maintained by GitHub user GUVWAF.
|
||||
Project files at https://github.com/GUVWAF/Meshtasticator */
|
||||
PortNum_SIMULATOR_APP = 69,
|
||||
/* Provides a traceroute functionality to show the route a packet towards
|
||||
a certain destination would take on the mesh. */
|
||||
PortNum_TRACEROUTE_APP = 70,
|
||||
/* Private applications should use portnums >= 256.
|
||||
To simplify initial development and testing you can use "PRIVATE_APP"
|
||||
in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/firmware/blob/master/bin/regen-protos.sh)) */
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include <HTTPBodyParser.hpp>
|
||||
#include <HTTPMultipartBodyParser.hpp>
|
||||
#include <HTTPURLEncodedBodyParser.hpp>
|
||||
#include <json11.hpp>
|
||||
#include "mqtt/JSON.h"
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
#include "esp_task_wdt.h"
|
||||
@@ -246,15 +246,15 @@ void htmlDeleteDir(const char *dirname)
|
||||
root.close();
|
||||
}
|
||||
|
||||
std::vector<std::map<char *, char *>> *htmlListDir(std::vector<std::map<char *, char *>> *fileList, const char *dirname,
|
||||
uint8_t levels)
|
||||
JSONArray htmlListDir(const char *dirname, uint8_t levels)
|
||||
{
|
||||
File root = FSCom.open(dirname, FILE_O_READ);
|
||||
JSONArray fileList;
|
||||
if (!root) {
|
||||
return NULL;
|
||||
return fileList;
|
||||
}
|
||||
if (!root.isDirectory()) {
|
||||
return NULL;
|
||||
return fileList;
|
||||
}
|
||||
|
||||
// iterate over the file list
|
||||
@@ -263,19 +263,19 @@ std::vector<std::map<char *, char *>> *htmlListDir(std::vector<std::map<char *,
|
||||
if (file.isDirectory() && !String(file.name()).endsWith(".")) {
|
||||
if (levels) {
|
||||
#ifdef ARCH_ESP32
|
||||
htmlListDir(fileList, file.path(), levels - 1);
|
||||
fileList.push_back(new JSONValue(htmlListDir(file.path(), levels - 1)));
|
||||
#else
|
||||
htmlListDir(fileList, file.name(), levels - 1);
|
||||
fileList.push_back(new JSONValue(htmlListDir(file.name(), levels - 1)));
|
||||
#endif
|
||||
file.close();
|
||||
}
|
||||
} else {
|
||||
std::map<char *, char *> thisFileMap;
|
||||
thisFileMap[strdup("size")] = strdup(String(file.size()).c_str());
|
||||
JSONObject thisFileMap;
|
||||
thisFileMap["size"] = new JSONValue((int)file.size());
|
||||
#ifdef ARCH_ESP32
|
||||
thisFileMap[strdup("name")] = strdup(String(file.path()).substring(1).c_str());
|
||||
thisFileMap["name"] = new JSONValue(String(file.path()).substring(1).c_str());
|
||||
#else
|
||||
thisFileMap[strdup("name")] = strdup(String(file.name()).substring(1).c_str());
|
||||
thisFileMap["name"] = new JSONValue(String(file.name()).substring(1).c_str());
|
||||
#endif
|
||||
if (String(file.name()).substring(1).endsWith(".gz")) {
|
||||
#ifdef ARCH_ESP32
|
||||
@@ -284,9 +284,9 @@ std::vector<std::map<char *, char *>> *htmlListDir(std::vector<std::map<char *,
|
||||
String modifiedFile = String(file.name()).substring(1);
|
||||
#endif
|
||||
modifiedFile.remove((modifiedFile.length() - 3), 3);
|
||||
thisFileMap[strdup("nameModified")] = strdup(modifiedFile.c_str());
|
||||
thisFileMap["nameModified"] = new JSONValue(modifiedFile.c_str());
|
||||
}
|
||||
fileList->push_back(thisFileMap);
|
||||
fileList.push_back(new JSONValue(thisFileMap));
|
||||
}
|
||||
file.close();
|
||||
file = root.openNextFile();
|
||||
@@ -301,29 +301,31 @@ void handleFsBrowseStatic(HTTPRequest *req, HTTPResponse *res)
|
||||
res->setHeader("Access-Control-Allow-Origin", "*");
|
||||
res->setHeader("Access-Control-Allow-Methods", "GET");
|
||||
|
||||
using namespace json11;
|
||||
auto fileList = htmlListDir(new std::vector<std::map<char *, char *>>(), "/static", 10);
|
||||
auto fileList = htmlListDir("/static", 10);
|
||||
|
||||
// create json output structure
|
||||
Json filesystemObj = Json::object{
|
||||
{"total", String(FSCom.totalBytes()).c_str()},
|
||||
{"used", String(FSCom.usedBytes()).c_str()},
|
||||
{"free", String(FSCom.totalBytes() - FSCom.usedBytes()).c_str()},
|
||||
};
|
||||
JSONObject filesystemObj;
|
||||
filesystemObj["total"] = new JSONValue((int)FSCom.totalBytes());
|
||||
filesystemObj["used"] = new JSONValue((int)FSCom.usedBytes());
|
||||
filesystemObj["free"] = new JSONValue(int(FSCom.totalBytes() - FSCom.usedBytes()));
|
||||
|
||||
Json jsonObjInner = Json::object{{"files", Json(*fileList)}, {"filesystem", filesystemObj}};
|
||||
JSONObject jsonObjInner;
|
||||
jsonObjInner["files"] = new JSONValue(fileList);
|
||||
jsonObjInner["filesystem"] = new JSONValue(filesystemObj);
|
||||
|
||||
Json jsonObjOuter = Json::object{{"data", jsonObjInner}, {"status", "ok"}};
|
||||
JSONObject jsonObjOuter;
|
||||
jsonObjOuter["data"] = new JSONValue(jsonObjInner);
|
||||
jsonObjOuter["status"] = new JSONValue("ok");
|
||||
|
||||
// serialize and write it to the stream
|
||||
std::string jsonStr = jsonObjOuter.dump();
|
||||
res->print(jsonStr.c_str());
|
||||
JSONValue *value = new JSONValue(jsonObjOuter);
|
||||
|
||||
res->print(value->Stringify().c_str());
|
||||
|
||||
delete value;
|
||||
}
|
||||
|
||||
void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
using namespace json11;
|
||||
|
||||
ResourceParameters *params = req->getParams();
|
||||
std::string paramValDelete;
|
||||
|
||||
@@ -334,15 +336,19 @@ void handleFsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
|
||||
std::string pathDelete = "/" + paramValDelete;
|
||||
if (FSCom.remove(pathDelete.c_str())) {
|
||||
Serial.println(pathDelete.c_str());
|
||||
Json jsonObjOuter = Json::object{{"status", "ok"}};
|
||||
std::string jsonStr = jsonObjOuter.dump();
|
||||
res->print(jsonStr.c_str());
|
||||
JSONObject jsonObjOuter;
|
||||
jsonObjOuter["status"] = new JSONValue("ok");
|
||||
JSONValue *value = new JSONValue(jsonObjOuter);
|
||||
res->print(value->Stringify().c_str());
|
||||
delete value;
|
||||
return;
|
||||
} else {
|
||||
Serial.println(pathDelete.c_str());
|
||||
Json jsonObjOuter = Json::object{{"status", "Error"}};
|
||||
std::string jsonStr = jsonObjOuter.dump();
|
||||
res->print(jsonStr.c_str());
|
||||
JSONObject jsonObjOuter;
|
||||
jsonObjOuter["status"] = new JSONValue("Error");
|
||||
JSONValue *value = new JSONValue(jsonObjOuter);
|
||||
res->print(value->Stringify().c_str());
|
||||
delete value;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -559,8 +565,6 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res)
|
||||
|
||||
void handleReport(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
using namespace json11;
|
||||
|
||||
ResourceParameters *params = req->getParams();
|
||||
std::string content;
|
||||
|
||||
@@ -579,81 +583,87 @@ void handleReport(HTTPRequest *req, HTTPResponse *res)
|
||||
}
|
||||
|
||||
// data->airtime->tx_log
|
||||
std::vector<String> txLogValues;
|
||||
JSONArray txLogValues;
|
||||
uint32_t *logArray;
|
||||
logArray = airTime->airtimeReport(TX_LOG);
|
||||
for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
|
||||
uint32_t tmp;
|
||||
tmp = *(logArray + i);
|
||||
txLogValues.push_back(String(tmp));
|
||||
txLogValues.push_back(new JSONValue((int)logArray[i]));
|
||||
}
|
||||
|
||||
// data->airtime->rx_log
|
||||
std::vector<String> rxLogValues;
|
||||
JSONArray rxLogValues;
|
||||
logArray = airTime->airtimeReport(RX_LOG);
|
||||
for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
|
||||
uint32_t tmp;
|
||||
tmp = *(logArray + i);
|
||||
rxLogValues.push_back(String(tmp));
|
||||
rxLogValues.push_back(new JSONValue((int)logArray[i]));
|
||||
}
|
||||
|
||||
// data->airtime->rx_all_log
|
||||
std::vector<String> rxAllLogValues;
|
||||
JSONArray rxAllLogValues;
|
||||
logArray = airTime->airtimeReport(RX_ALL_LOG);
|
||||
for (int i = 0; i < airTime->getPeriodsToLog(); i++) {
|
||||
uint32_t tmp;
|
||||
tmp = *(logArray + i);
|
||||
rxAllLogValues.push_back(String(tmp));
|
||||
rxAllLogValues.push_back(new JSONValue((int)logArray[i]));
|
||||
}
|
||||
|
||||
Json jsonObjAirtime = Json::object{
|
||||
{"tx_log", Json(txLogValues)},
|
||||
{"rx_log", Json(rxLogValues)},
|
||||
{"rx_all_log", Json(rxAllLogValues)},
|
||||
{"channel_utilization", Json(airTime->channelUtilizationPercent())},
|
||||
{"utilization_tx", Json(airTime->utilizationTXPercent())},
|
||||
{"seconds_since_boot", Json(int(airTime->getSecondsSinceBoot()))},
|
||||
{"seconds_per_period", Json(int(airTime->getSecondsPerPeriod()))},
|
||||
{"periods_to_log", Json(airTime->getPeriodsToLog())},
|
||||
};
|
||||
// data->airtime
|
||||
JSONObject jsonObjAirtime;
|
||||
jsonObjAirtime["tx_log"] = new JSONValue(txLogValues);
|
||||
jsonObjAirtime["rx_log"] = new JSONValue(rxLogValues);
|
||||
jsonObjAirtime["rx_all_log"] = new JSONValue(rxAllLogValues);
|
||||
jsonObjAirtime["channel_utilization"] = new JSONValue(airTime->channelUtilizationPercent());
|
||||
jsonObjAirtime["utilization_tx"] = new JSONValue(airTime->utilizationTXPercent());
|
||||
jsonObjAirtime["seconds_since_boot"] = new JSONValue(int(airTime->getSecondsSinceBoot()));
|
||||
jsonObjAirtime["seconds_per_period"] = new JSONValue(int(airTime->getSecondsPerPeriod()));
|
||||
jsonObjAirtime["periods_to_log"] = new JSONValue(airTime->getPeriodsToLog());
|
||||
|
||||
// data->wifi
|
||||
String ipStr = String(WiFi.localIP().toString());
|
||||
|
||||
Json jsonObjWifi = Json::object{{"rssi", String(WiFi.RSSI())}, {"ip", ipStr.c_str()}};
|
||||
JSONObject jsonObjWifi;
|
||||
jsonObjWifi["rssi"] = new JSONValue(WiFi.RSSI());
|
||||
jsonObjWifi["ip"] = new JSONValue(WiFi.localIP().toString().c_str());
|
||||
|
||||
// data->memory
|
||||
Json jsonObjMemory = Json::object{{"heap_total", Json(int(ESP.getHeapSize()))},
|
||||
{"heap_free", Json(int(ESP.getFreeHeap()))},
|
||||
{"psram_total", Json(int(ESP.getPsramSize()))},
|
||||
{"psram_free", Json(int(ESP.getFreePsram()))},
|
||||
{"fs_total", String(FSCom.totalBytes()).c_str()},
|
||||
{"fs_used", String(FSCom.usedBytes()).c_str()},
|
||||
{"fs_free", String(FSCom.totalBytes() - FSCom.usedBytes()).c_str()}};
|
||||
JSONObject jsonObjMemory;
|
||||
jsonObjMemory["heap_total"] = new JSONValue((int)ESP.getHeapSize());
|
||||
jsonObjMemory["heap_free"] = new JSONValue((int)ESP.getFreeHeap());
|
||||
jsonObjMemory["psram_total"] = new JSONValue((int)ESP.getPsramSize());
|
||||
jsonObjMemory["psram_free"] = new JSONValue((int)ESP.getFreePsram());
|
||||
jsonObjMemory["fs_total"] = new JSONValue((int)FSCom.totalBytes());
|
||||
jsonObjMemory["fs_used"] = new JSONValue((int)FSCom.usedBytes());
|
||||
jsonObjMemory["fs_free"] = new JSONValue(int(FSCom.totalBytes() - FSCom.usedBytes()));
|
||||
|
||||
// data->power
|
||||
Json jsonObjPower = Json::object{{"battery_percent", Json(powerStatus->getBatteryChargePercent())},
|
||||
{"battery_voltage_mv", Json(powerStatus->getBatteryVoltageMv())},
|
||||
{"has_battery", BoolToString(powerStatus->getHasBattery())},
|
||||
{"has_usb", BoolToString(powerStatus->getHasUSB())},
|
||||
{"is_charging", BoolToString(powerStatus->getIsCharging())}};
|
||||
JSONObject jsonObjPower;
|
||||
jsonObjPower["battery_percent"] = new JSONValue(powerStatus->getBatteryChargePercent());
|
||||
jsonObjPower["battery_voltage_mv"] = new JSONValue(powerStatus->getBatteryVoltageMv());
|
||||
jsonObjPower["has_battery"] = new JSONValue(BoolToString(powerStatus->getHasBattery()));
|
||||
jsonObjPower["has_usb"] = new JSONValue(BoolToString(powerStatus->getHasUSB()));
|
||||
jsonObjPower["is_charging"] = new JSONValue(BoolToString(powerStatus->getIsCharging()));
|
||||
|
||||
// data->device
|
||||
Json jsonObjDevice = Json::object{{"reboot_counter", Json(int(myNodeInfo.reboot_count))}};
|
||||
JSONObject jsonObjDevice;
|
||||
jsonObjDevice["reboot_counter"] = new JSONValue((int)myNodeInfo.reboot_count);
|
||||
|
||||
// data->radio
|
||||
Json jsonObjRadio = Json::object{{"frequency", Json(RadioLibInterface::instance->getFreq())},
|
||||
{"lora_channel", Json(int(RadioLibInterface::instance->getChannelNum()))}};
|
||||
JSONObject jsonObjRadio;
|
||||
jsonObjRadio["frequency"] = new JSONValue(RadioLibInterface::instance->getFreq());
|
||||
jsonObjRadio["lora_channel"] = new JSONValue((int)RadioLibInterface::instance->getChannelNum());
|
||||
|
||||
// collect data to inner data object
|
||||
Json jsonObjInner = Json::object{{"airtime", jsonObjAirtime}, {"wifi", jsonObjWifi}, {"memory", jsonObjMemory},
|
||||
{"power", jsonObjPower}, {"device", jsonObjDevice}, {"radio", jsonObjRadio}};
|
||||
JSONObject jsonObjInner;
|
||||
jsonObjInner["airtime"] = new JSONValue(jsonObjAirtime);
|
||||
jsonObjInner["wifi"] = new JSONValue(jsonObjWifi);
|
||||
jsonObjInner["memory"] = new JSONValue(jsonObjMemory);
|
||||
jsonObjInner["power"] = new JSONValue(jsonObjPower);
|
||||
jsonObjInner["device"] = new JSONValue(jsonObjDevice);
|
||||
jsonObjInner["radio"] = new JSONValue(jsonObjRadio);
|
||||
|
||||
// create json output structure
|
||||
Json jsonObjOuter = Json::object{{"data", jsonObjInner}, {"status", "ok"}};
|
||||
JSONObject jsonObjOuter;
|
||||
jsonObjOuter["data"] = new JSONValue(jsonObjInner);
|
||||
jsonObjOuter["status"] = new JSONValue("ok");
|
||||
// serialize and write it to the stream
|
||||
std::string jsonStr = jsonObjOuter.dump();
|
||||
res->print(jsonStr.c_str());
|
||||
JSONValue *value = new JSONValue(jsonObjOuter);
|
||||
res->print(value->Stringify().c_str());
|
||||
delete value;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -767,8 +777,6 @@ void handleRestart(HTTPRequest *req, HTTPResponse *res)
|
||||
|
||||
void handleBlinkLED(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
using namespace json11;
|
||||
|
||||
res->setHeader("Content-Type", "application/json");
|
||||
res->setHeader("Access-Control-Allow-Origin", "*");
|
||||
res->setHeader("Access-Control-Allow-Methods", "POST");
|
||||
@@ -797,15 +805,15 @@ void handleBlinkLED(HTTPRequest *req, HTTPResponse *res)
|
||||
#endif
|
||||
}
|
||||
|
||||
Json jsonObjOuter = Json::object{{"status", "ok"}};
|
||||
std::string jsonStr = jsonObjOuter.dump();
|
||||
res->print(jsonStr.c_str());
|
||||
JSONObject jsonObjOuter;
|
||||
jsonObjOuter["status"] = new JSONValue("ok");
|
||||
JSONValue *value = new JSONValue(jsonObjOuter);
|
||||
res->print(value->Stringify().c_str());
|
||||
delete value;
|
||||
}
|
||||
|
||||
void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
using namespace json11;
|
||||
|
||||
res->setHeader("Content-Type", "application/json");
|
||||
res->setHeader("Access-Control-Allow-Origin", "*");
|
||||
res->setHeader("Access-Control-Allow-Methods", "GET");
|
||||
@@ -814,7 +822,7 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
|
||||
int n = WiFi.scanNetworks();
|
||||
|
||||
// build list of network objects
|
||||
std::vector<Json> networkObjs;
|
||||
JSONArray networkObjs;
|
||||
if (n > 0) {
|
||||
for (int i = 0; i < n; ++i) {
|
||||
char ssidArray[50];
|
||||
@@ -823,8 +831,10 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
|
||||
ssidString.toCharArray(ssidArray, 50);
|
||||
|
||||
if (WiFi.encryptionType(i) != WIFI_AUTH_OPEN) {
|
||||
Json thisNetwork = Json::object{{"ssid", ssidArray}, {"rssi", WiFi.RSSI(i)}};
|
||||
networkObjs.push_back(thisNetwork);
|
||||
JSONObject thisNetwork;
|
||||
thisNetwork["ssid"] = new JSONValue(ssidArray);
|
||||
thisNetwork["rssi"] = new JSONValue(WiFi.RSSI(i));
|
||||
networkObjs.push_back(new JSONValue(thisNetwork));
|
||||
}
|
||||
// Yield some cpu cycles to IP stack.
|
||||
// This is important in case the list is large and it takes us time to return
|
||||
@@ -834,9 +844,12 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
|
||||
}
|
||||
|
||||
// build output structure
|
||||
Json jsonObjOuter = Json::object{{"data", networkObjs}, {"status", "ok"}};
|
||||
JSONObject jsonObjOuter;
|
||||
jsonObjOuter["data"] = new JSONValue(networkObjs);
|
||||
jsonObjOuter["status"] = new JSONValue("ok");
|
||||
|
||||
// serialize and write it to the stream
|
||||
std::string jsonStr = jsonObjOuter.dump();
|
||||
res->print(jsonStr.c_str());
|
||||
JSONValue *value = new JSONValue(jsonObjOuter);
|
||||
res->print(value->Stringify().c_str());
|
||||
delete value;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "mesh/http/WiFiAPClient.h"
|
||||
#include "NodeDB.h"
|
||||
#include "RTC.h"
|
||||
#include "concurrency/Periodic.h"
|
||||
#include "mesh/http/WiFiAPClient.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include "mesh/http/WebServer.h"
|
||||
@@ -35,33 +35,36 @@ char ourHost[16];
|
||||
|
||||
bool APStartupComplete = 0;
|
||||
|
||||
static bool needReconnect = true; // If we create our reconnector, run it once at the beginning
|
||||
unsigned long lastrun_ntp = 0;
|
||||
|
||||
bool needReconnect = true; // If we create our reconnector, run it once at the beginning
|
||||
|
||||
Periodic *wifiReconnect;
|
||||
|
||||
static int32_t reconnectWiFi()
|
||||
{
|
||||
const char *wifiName = config.network.wifi_ssid;
|
||||
const char *wifiPsw = config.network.wifi_psk;
|
||||
|
||||
if (config.network.wifi_enabled && needReconnect && !WiFi.isConnected()) {
|
||||
if (config.network.wifi_enabled && needReconnect) {
|
||||
|
||||
if (!*wifiPsw) // Treat empty password as no password
|
||||
wifiPsw = NULL;
|
||||
|
||||
if (*wifiName) {
|
||||
needReconnect = false;
|
||||
needReconnect = false;
|
||||
|
||||
// Make sure we clear old connection credentials
|
||||
WiFi.disconnect(false, true);
|
||||
// Make sure we clear old connection credentials
|
||||
WiFi.disconnect(false, true);
|
||||
|
||||
DEBUG_MSG("... Reconnecting to WiFi access point\n");
|
||||
WiFi.mode(WIFI_MODE_STA);
|
||||
WiFi.begin(wifiName, wifiPsw);
|
||||
}
|
||||
DEBUG_MSG("Reconnecting to WiFi access point %s\n",wifiName);
|
||||
|
||||
WiFi.mode(WIFI_MODE_STA);
|
||||
WiFi.begin(wifiName, wifiPsw);
|
||||
}
|
||||
|
||||
#ifndef DISABLE_NTP
|
||||
if (WiFi.isConnected()) {
|
||||
DEBUG_MSG("Updating NTP time\n");
|
||||
if (WiFi.isConnected() && (((millis() - lastrun_ntp) > 43200000) || (lastrun_ntp == 0))) { // every 12 hours
|
||||
DEBUG_MSG("Updating NTP time from %s\n",config.network.ntp_server);
|
||||
if (timeClient.update()) {
|
||||
DEBUG_MSG("NTP Request Success - Setting RTCQualityNTP if needed\n");
|
||||
|
||||
@@ -70,6 +73,7 @@ static int32_t reconnectWiFi()
|
||||
tv.tv_usec = 0;
|
||||
|
||||
perhapsSetRTC(RTCQualityNTP, &tv);
|
||||
lastrun_ntp = millis();
|
||||
|
||||
} else {
|
||||
DEBUG_MSG("NTP Update failed\n");
|
||||
@@ -77,11 +81,13 @@ static int32_t reconnectWiFi()
|
||||
}
|
||||
#endif
|
||||
|
||||
return 43200 * 1000; // every 12 hours
|
||||
if (config.network.wifi_enabled && !WiFi.isConnected()) {
|
||||
return 1000; // check once per second
|
||||
} else {
|
||||
return 300000; // every 5 minutes
|
||||
}
|
||||
}
|
||||
|
||||
static Periodic *wifiReconnect;
|
||||
|
||||
bool isWifiAvailable()
|
||||
{
|
||||
|
||||
@@ -95,20 +101,10 @@ bool isWifiAvailable()
|
||||
// Disable WiFi
|
||||
void deinitWifi()
|
||||
{
|
||||
/*
|
||||
Note from Jm (jm@casler.org - Sept 16, 2020):
|
||||
|
||||
A bug in the ESP32 SDK was introduced in Oct 2019 that keeps the WiFi radio from
|
||||
turning back on after it's shut off. See:
|
||||
https://github.com/espressif/arduino-esp32/issues/3522
|
||||
|
||||
Until then, WiFi should only be allowed when there's no power
|
||||
saving on the 2.4g transceiver.
|
||||
*/
|
||||
|
||||
DEBUG_MSG("WiFi deinit\n");
|
||||
|
||||
if (isWifiAvailable()) {
|
||||
WiFi.disconnect(true);
|
||||
WiFi.mode(WIFI_MODE_NULL);
|
||||
DEBUG_MSG("WiFi Turned Off\n");
|
||||
// WiFi.printDiag(Serial);
|
||||
@@ -119,7 +115,7 @@ static void onNetworkConnected()
|
||||
{
|
||||
if (!APStartupComplete) {
|
||||
// Start web server
|
||||
DEBUG_MSG("... Starting network services\n");
|
||||
DEBUG_MSG("Starting network services\n");
|
||||
|
||||
// start mdns
|
||||
if (!MDNS.begin("Meshtastic")) {
|
||||
@@ -158,6 +154,8 @@ bool initWifi()
|
||||
|
||||
createSSLCert();
|
||||
|
||||
esp_wifi_set_storage(WIFI_STORAGE_RAM); // Disable flash storage for WiFi credentials
|
||||
|
||||
if (!*wifiPsw) // Treat empty password as no password
|
||||
wifiPsw = NULL;
|
||||
|
||||
@@ -169,7 +167,7 @@ bool initWifi()
|
||||
WiFi.mode(WIFI_MODE_STA);
|
||||
WiFi.setHostname(ourHost);
|
||||
WiFi.onEvent(WiFiEvent);
|
||||
WiFi.setAutoReconnect(true);
|
||||
WiFi.setAutoReconnect(false);
|
||||
WiFi.setSleep(false);
|
||||
if (config.network.eth_mode == Config_NetworkConfig_EthMode_STATIC && config.network.ipv4_config.ip != 0) {
|
||||
WiFi.config(config.network.ipv4_config.ip,
|
||||
@@ -184,7 +182,7 @@ bool initWifi()
|
||||
|
||||
WiFi.onEvent(
|
||||
[](WiFiEvent_t event, WiFiEventInfo_t info) {
|
||||
Serial.print("\nWiFi lost connection. Reason: ");
|
||||
Serial.print("WiFi lost connection. Reason: ");
|
||||
Serial.println(info.wifi_sta_disconnected.reason);
|
||||
|
||||
/*
|
||||
@@ -211,88 +209,137 @@ bool initWifi()
|
||||
// Called by the Espressif SDK to
|
||||
static void WiFiEvent(WiFiEvent_t event)
|
||||
{
|
||||
DEBUG_MSG("************ [WiFi-event] event: %d ************\n", event);
|
||||
DEBUG_MSG("WiFi-Event %d: ", event);
|
||||
|
||||
switch (event) {
|
||||
case SYSTEM_EVENT_WIFI_READY:
|
||||
case ARDUINO_EVENT_WIFI_READY:
|
||||
DEBUG_MSG("WiFi interface ready\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_SCAN_DONE:
|
||||
case ARDUINO_EVENT_WIFI_SCAN_DONE:
|
||||
DEBUG_MSG("Completed scan for access points\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_START:
|
||||
case ARDUINO_EVENT_WIFI_STA_START:
|
||||
DEBUG_MSG("WiFi station started\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_STOP:
|
||||
case ARDUINO_EVENT_WIFI_STA_STOP:
|
||||
DEBUG_MSG("WiFi station stopped\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_CONNECTED:
|
||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
||||
DEBUG_MSG("Connected to access point\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_DISCONNECTED:
|
||||
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
|
||||
DEBUG_MSG("Disconnected from WiFi access point\n");
|
||||
WiFi.disconnect(false, true);
|
||||
needReconnect = true;
|
||||
wifiReconnect->setIntervalFromNow(1000);
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_AUTHMODE_CHANGE:
|
||||
case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE:
|
||||
DEBUG_MSG("Authentication mode of access point has changed\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_GOT_IP:
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||
DEBUG_MSG("Obtained IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
onNetworkConnected();
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_LOST_IP:
|
||||
DEBUG_MSG("Lost IP address and IP address is reset to 0\n");
|
||||
needReconnect = true;
|
||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
|
||||
DEBUG_MSG("Obtained IP6 address: ");
|
||||
Serial.println(WiFi.localIPv6());
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_WPS_ER_SUCCESS:
|
||||
case ARDUINO_EVENT_WIFI_STA_LOST_IP:
|
||||
DEBUG_MSG("Lost IP address and IP address is reset to 0\n");
|
||||
WiFi.disconnect(false, true);
|
||||
needReconnect = true;
|
||||
wifiReconnect->setIntervalFromNow(1000);
|
||||
break;
|
||||
case ARDUINO_EVENT_WPS_ER_SUCCESS:
|
||||
DEBUG_MSG("WiFi Protected Setup (WPS): succeeded in enrollee mode\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_WPS_ER_FAILED:
|
||||
case ARDUINO_EVENT_WPS_ER_FAILED:
|
||||
DEBUG_MSG("WiFi Protected Setup (WPS): failed in enrollee mode\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT:
|
||||
case ARDUINO_EVENT_WPS_ER_TIMEOUT:
|
||||
DEBUG_MSG("WiFi Protected Setup (WPS): timeout in enrollee mode\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_STA_WPS_ER_PIN:
|
||||
case ARDUINO_EVENT_WPS_ER_PIN:
|
||||
DEBUG_MSG("WiFi Protected Setup (WPS): pin code in enrollee mode\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_AP_START:
|
||||
DEBUG_MSG("WiFi access point started\n");
|
||||
onNetworkConnected();
|
||||
case ARDUINO_EVENT_WPS_ER_PBC_OVERLAP:
|
||||
DEBUG_MSG("WiFi Protected Setup (WPS): push button overlap in enrollee mode\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_AP_STOP:
|
||||
case ARDUINO_EVENT_WIFI_AP_START:
|
||||
DEBUG_MSG("WiFi access point started\n");
|
||||
break;
|
||||
case ARDUINO_EVENT_WIFI_AP_STOP:
|
||||
DEBUG_MSG("WiFi access point stopped\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_AP_STACONNECTED:
|
||||
case ARDUINO_EVENT_WIFI_AP_STACONNECTED:
|
||||
DEBUG_MSG("Client connected\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_AP_STADISCONNECTED:
|
||||
case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED:
|
||||
DEBUG_MSG("Client disconnected\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_AP_STAIPASSIGNED:
|
||||
case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED:
|
||||
DEBUG_MSG("Assigned IP address to client\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_AP_PROBEREQRECVED:
|
||||
case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED:
|
||||
DEBUG_MSG("Received probe request\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_GOT_IP6:
|
||||
case ARDUINO_EVENT_WIFI_AP_GOT_IP6:
|
||||
DEBUG_MSG("IPv6 is preferred\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_ETH_START:
|
||||
case ARDUINO_EVENT_WIFI_FTM_REPORT:
|
||||
DEBUG_MSG("Fast Transition Management report\n");
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_START:
|
||||
DEBUG_MSG("Ethernet started\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_ETH_STOP:
|
||||
case ARDUINO_EVENT_ETH_STOP:
|
||||
DEBUG_MSG("Ethernet stopped\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_ETH_CONNECTED:
|
||||
case ARDUINO_EVENT_ETH_CONNECTED:
|
||||
DEBUG_MSG("Ethernet connected\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_ETH_DISCONNECTED:
|
||||
case ARDUINO_EVENT_ETH_DISCONNECTED:
|
||||
DEBUG_MSG("Ethernet disconnected\n");
|
||||
break;
|
||||
case SYSTEM_EVENT_ETH_GOT_IP:
|
||||
DEBUG_MSG("Obtained IP address (SYSTEM_EVENT_ETH_GOT_IP)\n");
|
||||
case ARDUINO_EVENT_ETH_GOT_IP:
|
||||
DEBUG_MSG("Obtained IP address (ARDUINO_EVENT_ETH_GOT_IP)\n");
|
||||
break;
|
||||
case ARDUINO_EVENT_ETH_GOT_IP6:
|
||||
DEBUG_MSG("Obtained IP6 address (ARDUINO_EVENT_ETH_GOT_IP6)\n");
|
||||
break;
|
||||
case ARDUINO_EVENT_SC_SCAN_DONE:
|
||||
DEBUG_MSG("SmartConfig: Scan done\n");
|
||||
break;
|
||||
case ARDUINO_EVENT_SC_FOUND_CHANNEL:
|
||||
DEBUG_MSG("SmartConfig: Found channel\n");
|
||||
break;
|
||||
case ARDUINO_EVENT_SC_GOT_SSID_PSWD:
|
||||
DEBUG_MSG("SmartConfig: Got SSID and password\n");
|
||||
break;
|
||||
case ARDUINO_EVENT_SC_SEND_ACK_DONE:
|
||||
DEBUG_MSG("SmartConfig: Send ACK done\n");
|
||||
break;
|
||||
case ARDUINO_EVENT_PROV_INIT:
|
||||
DEBUG_MSG("Provisioning: Init\n");
|
||||
break;
|
||||
case ARDUINO_EVENT_PROV_DEINIT:
|
||||
DEBUG_MSG("Provisioning: Stopped\n");
|
||||
break;
|
||||
case ARDUINO_EVENT_PROV_START:
|
||||
DEBUG_MSG("Provisioning: Started\n");
|
||||
break;
|
||||
case ARDUINO_EVENT_PROV_END:
|
||||
DEBUG_MSG("Provisioning: End\n");
|
||||
break;
|
||||
case ARDUINO_EVENT_PROV_CRED_RECV:
|
||||
DEBUG_MSG("Provisioning: Credentials received\n");
|
||||
break;
|
||||
case ARDUINO_EVENT_PROV_CRED_FAIL:
|
||||
DEBUG_MSG("Provisioning: Credentials failed\n");
|
||||
break;
|
||||
case ARDUINO_EVENT_PROV_CRED_SUCCESS:
|
||||
DEBUG_MSG("Provisioning: Credentials success\n");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "configuration.h"
|
||||
#include "concurrency/Periodic.h"
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
|
||||
@@ -8,6 +9,9 @@
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
extern bool needReconnect;
|
||||
extern concurrency::Periodic *wifiReconnect;
|
||||
|
||||
/// @return true if wifi is now in use
|
||||
bool initWifi();
|
||||
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
#include "unistd.h"
|
||||
#endif
|
||||
|
||||
#define DEFAULT_REBOOT_SECONDS 5
|
||||
|
||||
AdminModule *adminModule;
|
||||
bool hasOpenEditTransaction;
|
||||
|
||||
/// A special reserved string to indicate strings we can not share with external nodes. We will use this 'reserved' word instead.
|
||||
/// Also, to make setting work correctly, if someone tries to set a string to this reserved value we assume they don't really want
|
||||
@@ -109,12 +112,15 @@ bool AdminModule::handleReceivedProtobuf(const MeshPacket &mp, AdminMessage *r)
|
||||
#ifdef ARCH_ESP32
|
||||
if (BleOta::getOtaAppVersion().isEmpty()) {
|
||||
DEBUG_MSG("No OTA firmware available, scheduling regular reboot in %d seconds\n", s);
|
||||
screen->startRebootScreen();
|
||||
}else{
|
||||
screen->startFirmwareUpdateScreen();
|
||||
BleOta::switchToOtaApp();
|
||||
DEBUG_MSG("Rebooting to OTA in %d seconds\n", s);
|
||||
}
|
||||
#else
|
||||
DEBUG_MSG("Not on ESP32, scheduling regular reboot in %d seconds\n", s);
|
||||
screen->startRebootScreen();
|
||||
#endif
|
||||
rebootAtMsec = (s < 0) ? 0 : (millis() + s * 1000);
|
||||
break;
|
||||
@@ -133,13 +139,23 @@ bool AdminModule::handleReceivedProtobuf(const MeshPacket &mp, AdminMessage *r)
|
||||
case AdminMessage_factory_reset_tag: {
|
||||
DEBUG_MSG("Initiating factory reset\n");
|
||||
nodeDB.factoryReset();
|
||||
reboot(5);
|
||||
reboot(DEFAULT_REBOOT_SECONDS);
|
||||
break;
|
||||
}
|
||||
case AdminMessage_nodedb_reset_tag: {
|
||||
} case AdminMessage_nodedb_reset_tag: {
|
||||
DEBUG_MSG("Initiating node-db reset\n");
|
||||
nodeDB.resetNodes();
|
||||
reboot(5);
|
||||
reboot(DEFAULT_REBOOT_SECONDS);
|
||||
break;
|
||||
}
|
||||
case AdminMessage_begin_edit_settings_tag: {
|
||||
DEBUG_MSG("Beginning transaction for editing settings\n");
|
||||
hasOpenEditTransaction = true;
|
||||
break;
|
||||
}
|
||||
case AdminMessage_commit_edit_settings_tag: {
|
||||
DEBUG_MSG("Committing transaction for edited settings\n");
|
||||
hasOpenEditTransaction = false;
|
||||
saveChanges(SEGMENT_CONFIG | SEGMENT_MODULECONFIG | SEGMENT_DEVICESTATE | SEGMENT_CHANNELS);
|
||||
break;
|
||||
}
|
||||
#ifdef ARCH_PORTDUINO
|
||||
@@ -163,6 +179,12 @@ bool AdminModule::handleReceivedProtobuf(const MeshPacket &mp, AdminMessage *r)
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// If asked for a response and it is not yet set, generate an 'ACK' response
|
||||
if (mp.decoded.want_response && !myReply) {
|
||||
myReply = allocErrorResponse(Routing_Error_NONE, &mp);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
@@ -173,6 +195,7 @@ bool AdminModule::handleReceivedProtobuf(const MeshPacket &mp, AdminMessage *r)
|
||||
void AdminModule::handleSetOwner(const User &o)
|
||||
{
|
||||
int changed = 0;
|
||||
bool licensed_changed = false;
|
||||
|
||||
if (*o.long_name) {
|
||||
changed |= strcmp(owner.long_name, o.long_name);
|
||||
@@ -188,13 +211,14 @@ void AdminModule::handleSetOwner(const User &o)
|
||||
}
|
||||
if (owner.is_licensed != o.is_licensed) {
|
||||
changed = 1;
|
||||
licensed_changed = true;
|
||||
owner.is_licensed = o.is_licensed;
|
||||
config.lora.override_duty_cycle = owner.is_licensed; // override duty cycle for licensed operators
|
||||
}
|
||||
|
||||
if (changed) { // If nothing really changed, don't broadcast on the network or write to flash
|
||||
service.reloadOwner();
|
||||
DEBUG_MSG("Rebooting due to owner changes\n");
|
||||
reboot(5);
|
||||
service.reloadOwner(!hasOpenEditTransaction);
|
||||
licensed_changed ? saveChanges(SEGMENT_CONFIG | SEGMENT_DEVICESTATE) : saveChanges(SEGMENT_DEVICESTATE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +244,7 @@ void AdminModule::handleSetConfig(const Config &c)
|
||||
config.has_position = true;
|
||||
config.position = c.payload_variant.position;
|
||||
// Save nodedb as well in case we got a fixed position packet
|
||||
nodeDB.saveToDisk(SEGMENT_DEVICESTATE);
|
||||
saveChanges(SEGMENT_DEVICESTATE, false);
|
||||
break;
|
||||
case Config_power_tag:
|
||||
DEBUG_MSG("Setting config: Power\n");
|
||||
@@ -252,9 +276,8 @@ void AdminModule::handleSetConfig(const Config &c)
|
||||
config.bluetooth = c.payload_variant.bluetooth;
|
||||
break;
|
||||
}
|
||||
|
||||
service.reloadConfig(SEGMENT_CONFIG);
|
||||
reboot(5);
|
||||
|
||||
saveChanges(SEGMENT_CONFIG);
|
||||
}
|
||||
|
||||
void AdminModule::handleSetModuleConfig(const ModuleConfig &c)
|
||||
@@ -302,15 +325,14 @@ void AdminModule::handleSetModuleConfig(const ModuleConfig &c)
|
||||
break;
|
||||
}
|
||||
|
||||
service.reloadConfig(SEGMENT_MODULECONFIG);
|
||||
reboot(5);
|
||||
saveChanges(SEGMENT_MODULECONFIG);
|
||||
}
|
||||
|
||||
void AdminModule::handleSetChannel(const Channel &cc)
|
||||
{
|
||||
channels.setChannel(cc);
|
||||
channels.onConfigChanged(); // tell the radios about this change
|
||||
nodeDB.saveChannelsToDisk();
|
||||
saveChanges(SEGMENT_CHANNELS, false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -479,6 +501,20 @@ void AdminModule::reboot(int32_t seconds)
|
||||
rebootAtMsec = (seconds < 0) ? 0 : (millis() + seconds * 1000);
|
||||
}
|
||||
|
||||
void AdminModule::saveChanges(int saveWhat, bool shouldReboot)
|
||||
{
|
||||
if (!hasOpenEditTransaction) {
|
||||
DEBUG_MSG("Saving changes to disk\n");
|
||||
service.reloadConfig(saveWhat); // Calls saveToDisk among other things
|
||||
} else {
|
||||
DEBUG_MSG("Delaying save of changes to disk until the open transaction is committed\n");
|
||||
}
|
||||
if (shouldReboot)
|
||||
{
|
||||
reboot(DEFAULT_REBOOT_SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
AdminModule::AdminModule() : ProtobufModule("Admin", PortNum_ADMIN_APP, AdminMessage_fields)
|
||||
{
|
||||
// restrict to the admin channel for rx
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include "ProtobufModule.h"
|
||||
|
||||
/**
|
||||
* Routing module for router control messages
|
||||
* Admin module for admin messages
|
||||
*/
|
||||
class AdminModule : public ProtobufModule<AdminMessage>
|
||||
{
|
||||
@@ -20,6 +20,9 @@ class AdminModule : public ProtobufModule<AdminMessage>
|
||||
virtual bool handleReceivedProtobuf(const MeshPacket &mp, AdminMessage *p) override;
|
||||
|
||||
private:
|
||||
bool hasOpenEditTransaction = false;
|
||||
|
||||
void saveChanges(int saveWhat, bool shouldReboot = true);
|
||||
/**
|
||||
* Getters
|
||||
*/
|
||||
|
||||
@@ -176,7 +176,7 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event)
|
||||
DEBUG_MSG("Canned message event Matrix key pressed\n");
|
||||
// this will send the text immediately on matrix press
|
||||
this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_SELECT;
|
||||
this->payload = event->kbchar;
|
||||
this->payload = MATRIXKEY;
|
||||
this->currentMessageIndex = event->kbchar -1;
|
||||
this->lastTouchMillis = millis();
|
||||
validEvent = true;
|
||||
@@ -246,7 +246,12 @@ int32_t CannedMessageModule::runOnce()
|
||||
}
|
||||
} else {
|
||||
if ((this->messagesCount > this->currentMessageIndex) && (strlen(this->messages[this->currentMessageIndex]) > 0)) {
|
||||
sendText(NODENUM_BROADCAST, this->messages[this->currentMessageIndex], true);
|
||||
if(strcmp (this->messages[this->currentMessageIndex], "~") == 0) {
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
return INT32_MAX;
|
||||
} else {
|
||||
sendText(NODENUM_BROADCAST, this->messages[this->currentMessageIndex], true);
|
||||
}
|
||||
this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE;
|
||||
} else {
|
||||
DEBUG_MSG("Reset message is empty.\n");
|
||||
@@ -446,11 +451,15 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
|
||||
if (this->destSelect) {
|
||||
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
|
||||
display->setColor(BLACK);
|
||||
display->drawStringf(1 + x, 0 + y, buffer, "To: %s", cannedMessageModule->getNodeName(this->dest));
|
||||
}
|
||||
display->drawStringf(0 + x, 0 + y, buffer, "To: %s", cannedMessageModule->getNodeName(this->dest));
|
||||
// used chars right aligned
|
||||
sprintf(buffer, "%d left", Constants_DATA_PAYLOAD_LEN - this->freetext.length());
|
||||
display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer);
|
||||
if (this->destSelect) {
|
||||
display->drawString(x + display->getWidth() - display->getStringWidth(buffer) - 1, y + 0, buffer);
|
||||
}
|
||||
display->setColor(WHITE);
|
||||
display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), cannedMessageModule->drawWithCursor(cannedMessageModule->freetext, cannedMessageModule->cursor));
|
||||
} else {
|
||||
|
||||
@@ -76,7 +76,7 @@ int32_t ExternalNotificationModule::runOnce()
|
||||
// moduleConfig.external_notification.output_ms = 1000;
|
||||
// moduleConfig.external_notification.output = 13;
|
||||
|
||||
if (externalCurrentState) {
|
||||
if (externalCurrentState && !moduleConfig.external_notification.use_pwm) {
|
||||
|
||||
// If the output is turned on, turn it back off after the given period of time.
|
||||
if (externalTurnedOn + (moduleConfig.external_notification.output_ms
|
||||
@@ -84,13 +84,13 @@ int32_t ExternalNotificationModule::runOnce()
|
||||
: EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
|
||||
millis()) {
|
||||
DEBUG_MSG("Turning off external notification\n");
|
||||
if (output != PIN_BUZZER) {
|
||||
setExternalOff();
|
||||
}
|
||||
setExternalOff();
|
||||
}
|
||||
}
|
||||
|
||||
return (25);
|
||||
if (moduleConfig.external_notification.use_pwm)
|
||||
return INT32_MAX; // we don't need this thread here...
|
||||
else
|
||||
return 25;
|
||||
}
|
||||
|
||||
void ExternalNotificationModule::setExternalOn()
|
||||
@@ -129,11 +129,6 @@ ExternalNotificationModule::ExternalNotificationModule()
|
||||
// moduleConfig.external_notification.output_ms = 1000;
|
||||
// moduleConfig.external_notification.output = 13;
|
||||
|
||||
if (moduleConfig.external_notification.alert_message) {
|
||||
// restrict to the gpio channel for rx
|
||||
boundChannel = Channels::gpioChannel;
|
||||
}
|
||||
|
||||
if (moduleConfig.external_notification.enabled) {
|
||||
|
||||
DEBUG_MSG("Initializing External Notification Module\n");
|
||||
@@ -142,14 +137,19 @@ ExternalNotificationModule::ExternalNotificationModule()
|
||||
? moduleConfig.external_notification.output
|
||||
: EXT_NOTIFICATION_MODULE_OUTPUT;
|
||||
|
||||
if (output != PIN_BUZZER) {
|
||||
if (!moduleConfig.external_notification.use_pwm) {
|
||||
// Set the direction of a pin
|
||||
DEBUG_MSG("Using Pin %i in digital mode\n", output);
|
||||
pinMode(output, OUTPUT);
|
||||
// Turn off the pin
|
||||
setExternalOff();
|
||||
} else{
|
||||
DEBUG_MSG("Using Pin %i in PWM mode\n", output);
|
||||
} else {
|
||||
config.device.buzzer_gpio = config.device.buzzer_gpio
|
||||
? config.device.buzzer_gpio
|
||||
: PIN_BUZZER;
|
||||
|
||||
// in PWM Mode we force the buzzer pin if it is set
|
||||
DEBUG_MSG("Using Pin %i in PWM mode\n", config.device.buzzer_gpio);
|
||||
}
|
||||
} else {
|
||||
DEBUG_MSG("External Notification Module Disabled\n");
|
||||
@@ -170,7 +170,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const MeshPacket &mp)
|
||||
DEBUG_MSG("externalNotificationModule - Notification Bell\n");
|
||||
for (int i = 0; i < p.payload.size; i++) {
|
||||
if (p.payload.bytes[i] == ASCII_BELL) {
|
||||
if (output != PIN_BUZZER) {
|
||||
if (!moduleConfig.external_notification.use_pwm) {
|
||||
setExternalOn();
|
||||
} else {
|
||||
playBeep();
|
||||
@@ -181,7 +181,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const MeshPacket &mp)
|
||||
|
||||
if (moduleConfig.external_notification.alert_message) {
|
||||
DEBUG_MSG("externalNotificationModule - Notification Module\n");
|
||||
if (output != PIN_BUZZER) {
|
||||
if (!moduleConfig.external_notification.use_pwm) {
|
||||
setExternalOn();
|
||||
} else {
|
||||
playBeep();
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "modules/ReplyModule.h"
|
||||
#include "modules/RoutingModule.h"
|
||||
#include "modules/TextMessageModule.h"
|
||||
#include "modules/TraceRouteModule.h"
|
||||
#include "modules/WaypointModule.h"
|
||||
#if HAS_TELEMETRY
|
||||
#include "modules/Telemetry/DeviceTelemetry.h"
|
||||
@@ -19,10 +20,8 @@
|
||||
#ifdef ARCH_ESP32
|
||||
#include "modules/esp32/RangeTestModule.h"
|
||||
#include "modules/esp32/StoreForwardModule.h"
|
||||
#ifdef USE_SX1280
|
||||
#include "modules/esp32/AudioModule.h"
|
||||
#endif
|
||||
#endif
|
||||
#if defined(ARCH_ESP32) || defined(ARCH_NRF52)
|
||||
#include "modules/ExternalNotificationModule.h"
|
||||
#if !defined(TTGO_T_ECHO)
|
||||
@@ -42,6 +41,7 @@ void setupModules()
|
||||
positionModule = new PositionModule();
|
||||
waypointModule = new WaypointModule();
|
||||
textMessageModule = new TextMessageModule();
|
||||
traceRouteModule = new TraceRouteModule();
|
||||
|
||||
// Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance
|
||||
// to a global variable.
|
||||
@@ -68,9 +68,7 @@ void setupModules()
|
||||
#endif
|
||||
#ifdef ARCH_ESP32
|
||||
// Only run on an esp32 based device.
|
||||
#ifdef USE_SX1280
|
||||
new AudioModule();
|
||||
#endif
|
||||
audioModule = new AudioModule();
|
||||
new ExternalNotificationModule();
|
||||
|
||||
storeForwardModule = new StoreForwardModule();
|
||||
|
||||
@@ -82,7 +82,7 @@ SerialModuleRadio::SerialModuleRadio() : MeshModule("SerialModuleRadio")
|
||||
|
||||
int32_t SerialModule::runOnce()
|
||||
{
|
||||
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO)
|
||||
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) && !defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
/*
|
||||
Uncomment the preferences below if you want to use the module
|
||||
without having to configure it from the PythonAPI or WebUI.
|
||||
@@ -214,7 +214,6 @@ int32_t SerialModule::runOnce()
|
||||
|
||||
MeshPacket *SerialModuleRadio::allocReply()
|
||||
{
|
||||
|
||||
auto reply = allocDataPacket(); // Allocate a packet for sending
|
||||
|
||||
return reply;
|
||||
@@ -222,8 +221,12 @@ MeshPacket *SerialModuleRadio::allocReply()
|
||||
|
||||
void SerialModuleRadio::sendPayload(NodeNum dest, bool wantReplies)
|
||||
{
|
||||
Channel *ch = (boundChannel != NULL) ? &channels.getByName(boundChannel) : NULL;
|
||||
MeshPacket *p = allocReply();
|
||||
p->to = dest;
|
||||
if (ch != NULL) {
|
||||
p->channel = ch->index;
|
||||
}
|
||||
p->decoded.want_response = wantReplies;
|
||||
|
||||
p->want_ack = ACK;
|
||||
@@ -236,7 +239,7 @@ void SerialModuleRadio::sendPayload(NodeNum dest, bool wantReplies)
|
||||
|
||||
ProcessMessage SerialModuleRadio::handleReceived(const MeshPacket &mp)
|
||||
{
|
||||
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO)
|
||||
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) && !defined(CONFIG_IDF_TARGET_ESP32S2)
|
||||
if (moduleConfig.serial.enabled) {
|
||||
|
||||
auto &p = mp.decoded;
|
||||
@@ -266,7 +269,12 @@ ProcessMessage SerialModuleRadio::handleReceived(const MeshPacket &mp)
|
||||
if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_DEFAULT ||
|
||||
moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_SIMPLE) {
|
||||
Serial2.printf("%s", p.payload.bytes);
|
||||
|
||||
} else if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG) {
|
||||
NodeInfo *node = nodeDB.getNode(getFrom(&mp));
|
||||
String sender = (node && node->has_user) ? node->user.short_name : "???";
|
||||
Serial2.println();
|
||||
Serial2.printf("%s: %s", sender, p.payload.bytes);
|
||||
Serial2.println();
|
||||
} else if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_PROTO) {
|
||||
// TODO this needs to be implemented
|
||||
} else if (moduleConfig.serial.mode == ModuleConfig_SerialConfig_Serial_Mode_NMEA) {
|
||||
|
||||
86
src/modules/TraceRouteModule.cpp
Normal file
86
src/modules/TraceRouteModule.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include "TraceRouteModule.h"
|
||||
#include "MeshService.h"
|
||||
#include "FloodingRouter.h"
|
||||
|
||||
TraceRouteModule *traceRouteModule;
|
||||
|
||||
|
||||
bool TraceRouteModule::handleReceivedProtobuf(const MeshPacket &mp, RouteDiscovery *r)
|
||||
{
|
||||
// Only handle a response
|
||||
if (mp.decoded.request_id) {
|
||||
printRoute(r, mp.to, mp.from);
|
||||
}
|
||||
|
||||
return false; // let it be handled by RoutingModule
|
||||
}
|
||||
|
||||
|
||||
void TraceRouteModule::updateRoute(MeshPacket* p)
|
||||
{
|
||||
auto &incoming = p->decoded;
|
||||
// Only append an ID for the request (one way)
|
||||
if (!incoming.request_id) {
|
||||
RouteDiscovery scratch;
|
||||
RouteDiscovery *updated = NULL;
|
||||
memset(&scratch, 0, sizeof(scratch));
|
||||
pb_decode_from_bytes(incoming.payload.bytes, incoming.payload.size, RouteDiscovery_fields, &scratch);
|
||||
updated = &scratch;
|
||||
|
||||
appendMyID(updated);
|
||||
printRoute(updated, p->from, NODENUM_BROADCAST);
|
||||
|
||||
// Set updated route to the payload of the to be flooded packet
|
||||
p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), RouteDiscovery_fields, updated);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TraceRouteModule::appendMyID(RouteDiscovery* updated)
|
||||
{
|
||||
// Length of route array can normally not be exceeded due to the max. hop_limit of 7
|
||||
if (updated->route_count < sizeof(updated->route)/sizeof(updated->route[0])) {
|
||||
updated->route[updated->route_count] = myNodeInfo.my_node_num;
|
||||
updated->route_count += 1;
|
||||
} else {
|
||||
DEBUG_MSG("WARNING: Route exceeded maximum hop limit, are you bridging networks?\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TraceRouteModule::printRoute(RouteDiscovery* r, uint32_t origin, uint32_t dest)
|
||||
{
|
||||
DEBUG_MSG("Route traced:\n");
|
||||
DEBUG_MSG("0x%x --> ", origin);
|
||||
for (uint8_t i=0; i<r->route_count; i++) {
|
||||
DEBUG_MSG("0x%x --> ", r->route[i]);
|
||||
}
|
||||
if (dest != NODENUM_BROADCAST) DEBUG_MSG("0x%x\n", dest); else DEBUG_MSG("...\n");
|
||||
}
|
||||
|
||||
|
||||
MeshPacket* TraceRouteModule::allocReply()
|
||||
{
|
||||
assert(currentRequest);
|
||||
|
||||
// Copy the payload of the current request
|
||||
auto req = *currentRequest;
|
||||
auto &p = req.decoded;
|
||||
RouteDiscovery scratch;
|
||||
RouteDiscovery *updated = NULL;
|
||||
memset(&scratch, 0, sizeof(scratch));
|
||||
pb_decode_from_bytes(p.payload.bytes, p.payload.size, RouteDiscovery_fields, &scratch);
|
||||
updated = &scratch;
|
||||
|
||||
printRoute(updated, req.from, req.to);
|
||||
|
||||
// Create a MeshPacket with this payload and set it as the reply
|
||||
MeshPacket* reply = allocDataProtobuf(*updated);
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
|
||||
TraceRouteModule::TraceRouteModule() : ProtobufModule("traceroute", PortNum_TRACEROUTE_APP, RouteDiscovery_fields) {
|
||||
ourPortNum = PortNum_TRACEROUTE_APP;
|
||||
}
|
||||
36
src/modules/TraceRouteModule.h
Normal file
36
src/modules/TraceRouteModule.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include "ProtobufModule.h"
|
||||
|
||||
|
||||
/**
|
||||
* A module that traces the route to a certain destination node
|
||||
*/
|
||||
class TraceRouteModule : public ProtobufModule<RouteDiscovery>
|
||||
{
|
||||
public:
|
||||
TraceRouteModule();
|
||||
|
||||
// Let FloodingRouter call updateRoute upon rebroadcasting a TraceRoute request
|
||||
friend class FloodingRouter;
|
||||
|
||||
protected:
|
||||
bool handleReceivedProtobuf(const MeshPacket &mp, RouteDiscovery *r) override;
|
||||
|
||||
virtual MeshPacket *allocReply() override;
|
||||
|
||||
/* Call before rebroadcasting a RouteDiscovery payload in order to update
|
||||
the route array containing the IDs of nodes this packet went through */
|
||||
void updateRoute(MeshPacket* p);
|
||||
|
||||
private:
|
||||
// Call to add your ID to the route array of a RouteDiscovery message
|
||||
void appendMyID(RouteDiscovery *r);
|
||||
|
||||
/* Call to print the route array of a RouteDiscovery message.
|
||||
Set origin to where the request came from.
|
||||
Set dest to the ID of its destination, or NODENUM_BROADCAST if it has not yet arrived there. */
|
||||
void printRoute(RouteDiscovery* r, uint32_t origin, uint32_t dest);
|
||||
|
||||
};
|
||||
|
||||
extern TraceRouteModule *traceRouteModule;
|
||||
@@ -1,4 +1,6 @@
|
||||
|
||||
#include "configuration.h"
|
||||
#if defined(ARCH_ESP32)
|
||||
#include "AudioModule.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
@@ -8,6 +10,10 @@
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef OLED_RU
|
||||
#include "graphics/fonts/OLEDDisplayFontsRU.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
AudioModule
|
||||
A interface to send raw codec2 audio data over the mesh network. Based on the example code from the ESP32_codec2 project.
|
||||
@@ -19,201 +25,269 @@
|
||||
|
||||
Basic Usage:
|
||||
1) Enable the module by setting audio.codec2_enabled to 1.
|
||||
2) Set the pins (audio.mic_pin / audio.amp_pin) for your preferred microphone and amplifier GPIO pins.
|
||||
On tbeam, recommend to use:
|
||||
audio.mic_chan 6 (GPIO 34)
|
||||
audio.amp_pin 14
|
||||
audio.ptt_pin 39
|
||||
3) Set audio.timeout to the amount of time to wait before we consider
|
||||
your voice stream as "done".
|
||||
4) Set audio.bitrate to the desired codec2 rate (CODEC2_3200, CODEC2_2400, CODEC2_1600, CODEC2_1400, CODEC2_1300, CODEC2_1200, CODEC2_700, CODEC2_700B)
|
||||
2) Set the pins for the I2S interface. Recommended on TLora is I2S_WS 13/I2S_SD 15/I2S_SIN 2/I2S_SCK 14
|
||||
3) Set audio.bitrate to the desired codec2 rate (CODEC2_3200, CODEC2_2400, CODEC2_1600, CODEC2_1400, CODEC2_1300, CODEC2_1200, CODEC2_700, CODEC2_700B)
|
||||
|
||||
KNOWN PROBLEMS
|
||||
* Until the module is initilized by the startup sequence, the amp_pin pin is in a floating
|
||||
state. This may produce a bit of "noise".
|
||||
* Will not work on NRF and the Linux device targets.
|
||||
* Half Duplex
|
||||
* Will not work on NRF and the Linux device targets (yet?).
|
||||
*/
|
||||
|
||||
#define AMIC 6
|
||||
#define AAMP 14
|
||||
#define PTT_PIN 39
|
||||
|
||||
#define AUDIO_MODULE_RX_BUFFER 128
|
||||
#define AUDIO_MODULE_DATA_MAX Constants_DATA_PAYLOAD_LEN
|
||||
#define AUDIO_MODULE_MODE 7 // 700B
|
||||
#define AUDIO_MODULE_ACK 1
|
||||
|
||||
#if defined(ARCH_ESP32) && defined(USE_SX1280)
|
||||
|
||||
AudioModule *audioModule;
|
||||
|
||||
ButterworthFilter hp_filter(240, 8000, ButterworthFilter::ButterworthFilter::Highpass, 1);
|
||||
|
||||
//int16_t 1KHz sine test tone
|
||||
int16_t Sine1KHz[8] = { -21210 , -30000, -21210, 0 , 21210 , 30000 , 21210, 0 };
|
||||
int Sine1KHz_index = 0;
|
||||
TaskHandle_t codec2HandlerTask;
|
||||
AudioModule *audioModule;
|
||||
|
||||
uint8_t rx_raw_audio_value = 127;
|
||||
#ifdef ARCH_ESP32
|
||||
// ESP32 doesn't use that flag
|
||||
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR()
|
||||
#else
|
||||
#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x)
|
||||
#endif
|
||||
|
||||
AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), concurrency::OSThread("AudioModule") {
|
||||
audio_fifo.init();
|
||||
}
|
||||
#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS)
|
||||
// The screen is bigger so use bigger fonts
|
||||
#define FONT_SMALL ArialMT_Plain_16
|
||||
#define FONT_MEDIUM ArialMT_Plain_24
|
||||
#define FONT_LARGE ArialMT_Plain_24
|
||||
#else
|
||||
#ifdef OLED_RU
|
||||
#define FONT_SMALL ArialMT_Plain_10_RU
|
||||
#else
|
||||
#define FONT_SMALL ArialMT_Plain_10
|
||||
#endif
|
||||
#define FONT_MEDIUM ArialMT_Plain_16
|
||||
#define FONT_LARGE ArialMT_Plain_24
|
||||
#endif
|
||||
|
||||
void AudioModule::run_codec2()
|
||||
#define fontHeight(font) ((font)[1] + 1) // height is position 1
|
||||
|
||||
#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
|
||||
#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
|
||||
#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE)
|
||||
|
||||
void run_codec2(void* parameter)
|
||||
{
|
||||
if (state == State::tx)
|
||||
{
|
||||
for (int i = 0; i < ADC_BUFFER_SIZE; i++)
|
||||
speech[i] = (int16_t)hp_filter.Update((float)speech[i]);
|
||||
// 4 bytes of header in each frame hex c0 de c2 plus the bitrate
|
||||
memcpy(audioModule->tx_encode_frame,&audioModule->tx_header,sizeof(audioModule->tx_header));
|
||||
|
||||
codec2_encode(codec2_state, tx_encode_frame + tx_encode_frame_index, speech);
|
||||
DEBUG_MSG("Starting codec2 task\n");
|
||||
|
||||
//increment the pointer where the encoded frame must be saved
|
||||
tx_encode_frame_index += 8;
|
||||
while (true) {
|
||||
uint32_t tcount = ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(10000));
|
||||
|
||||
//If it is the 5th time then we have a ready trasnmission frame
|
||||
if (tx_encode_frame_index == ENCODE_FRAME_SIZE)
|
||||
{
|
||||
tx_encode_frame_index = 0;
|
||||
//Transmit it
|
||||
sendPayload();
|
||||
if (tcount != 0) {
|
||||
if (audioModule->radio_state == RadioState::tx) {
|
||||
for (int i = 0; i < audioModule->adc_buffer_size; i++)
|
||||
audioModule->speech[i] = (int16_t)hp_filter.Update((float)audioModule->speech[i]);
|
||||
|
||||
codec2_encode(audioModule->codec2, audioModule->tx_encode_frame + audioModule->tx_encode_frame_index, audioModule->speech);
|
||||
audioModule->tx_encode_frame_index += audioModule->encode_codec_size;
|
||||
|
||||
if (audioModule->tx_encode_frame_index == (audioModule->encode_frame_size + sizeof(audioModule->tx_header)))
|
||||
{
|
||||
DEBUG_MSG("Sending %d codec2 bytes\n", audioModule->encode_frame_size);
|
||||
audioModule->sendPayload();
|
||||
audioModule->tx_encode_frame_index = sizeof(audioModule->tx_header);
|
||||
}
|
||||
}
|
||||
if (audioModule->radio_state == RadioState::rx) {
|
||||
size_t bytesOut = 0;
|
||||
if (memcmp(audioModule->rx_encode_frame, &audioModule->tx_header, sizeof(audioModule->tx_header)) == 0) {
|
||||
for (int i = 4; i < audioModule->rx_encode_frame_index; i += audioModule->encode_codec_size)
|
||||
{
|
||||
codec2_decode(audioModule->codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i);
|
||||
i2s_write(I2S_PORT, &audioModule->output_buffer, audioModule->adc_buffer_size, &bytesOut, pdMS_TO_TICKS(500));
|
||||
}
|
||||
} else {
|
||||
// if the buffer header does not match our own codec, make a temp decoding setup.
|
||||
CODEC2* tmp_codec2 = codec2_create(audioModule->rx_encode_frame[3]);
|
||||
codec2_set_lpc_post_filter(tmp_codec2, 1, 0, 0.8, 0.2);
|
||||
int tmp_encode_codec_size = (codec2_bits_per_frame(tmp_codec2) + 7) / 8;
|
||||
int tmp_adc_buffer_size = codec2_samples_per_frame(tmp_codec2);
|
||||
for (int i = 4; i < audioModule->rx_encode_frame_index; i += tmp_encode_codec_size)
|
||||
{
|
||||
codec2_decode(tmp_codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i);
|
||||
i2s_write(I2S_PORT, &audioModule->output_buffer, tmp_adc_buffer_size, &bytesOut, pdMS_TO_TICKS(500));
|
||||
}
|
||||
codec2_destroy(tmp_codec2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (state == State::rx) //Receiving
|
||||
{
|
||||
//Make a cycle to get each codec2 frame from the received frame
|
||||
for (int i = 0; i < ENCODE_FRAME_SIZE; i += 8)
|
||||
{
|
||||
//Decode the codec2 frame
|
||||
codec2_decode(codec2_state, output_buffer, rx_encode_frame + i);
|
||||
|
||||
// Add to the audio buffer the 320 samples resulting of the decode of the codec2 frame.
|
||||
for (int g = 0; g < ADC_BUFFER_SIZE; g++)
|
||||
audio_fifo.put(output_buffer[g]);
|
||||
}
|
||||
}
|
||||
|
||||
AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), concurrency::OSThread("AudioModule")
|
||||
{
|
||||
// moduleConfig.audio.codec2_enabled = true;
|
||||
// moduleConfig.audio.i2s_ws = 13;
|
||||
// moduleConfig.audio.i2s_sd = 15;
|
||||
// moduleConfig.audio.i2s_din = 22;
|
||||
// moduleConfig.audio.i2s_sck = 14;
|
||||
// moduleConfig.audio.ptt_pin = 39;
|
||||
|
||||
if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) {
|
||||
DEBUG_MSG("Setting up codec2 in mode %u", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1);
|
||||
codec2 = codec2_create((moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1);
|
||||
memcpy(tx_header.magic,c2_magic,sizeof(c2_magic));
|
||||
tx_header.mode = (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1;
|
||||
codec2_set_lpc_post_filter(codec2, 1, 0, 0.8, 0.2);
|
||||
encode_codec_size = (codec2_bits_per_frame(codec2) + 7) / 8;
|
||||
encode_frame_num = (Constants_DATA_PAYLOAD_LEN - sizeof(tx_header)) / encode_codec_size;
|
||||
encode_frame_size = encode_frame_num * encode_codec_size; // max 233 bytes + 4 header bytes
|
||||
adc_buffer_size = codec2_samples_per_frame(codec2);
|
||||
DEBUG_MSG(" using %d frames of %d bytes for a total payload length of %d bytes\n", encode_frame_num, encode_codec_size, encode_frame_size);
|
||||
xTaskCreate(&run_codec2, "codec2_task", 30000, NULL, 5, &codec2HandlerTask);
|
||||
} else {
|
||||
DEBUG_MSG("Codec2 disabled (AudioModule %d, Region %s, permitted %d)\n", moduleConfig.audio.codec2_enabled, myRegion->name, myRegion->audioPermitted);
|
||||
}
|
||||
state = State::standby;
|
||||
}
|
||||
|
||||
void AudioModule::handleInterrupt()
|
||||
void AudioModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
audioModule->onTimer();
|
||||
}
|
||||
displayedNodeNum = 0; // Not currently showing a node pane
|
||||
|
||||
void AudioModule::onTimer()
|
||||
{
|
||||
if (state == State::tx) {
|
||||
adc_buffer[adc_buffer_index++] = (16 * adc1_get_raw(mic_chan)) - 32768;
|
||||
char buffer[50];
|
||||
|
||||
//If you want to test with a 1KHz tone, comment the line above and descomment the three lines below
|
||||
|
||||
// adc_buffer[adc_buffer_index++] = Sine1KHz[Sine1KHz_index++];
|
||||
// if (Sine1KHz_index >= 8)
|
||||
// Sine1KHz_index = 0;
|
||||
|
||||
if (adc_buffer_index == ADC_BUFFER_SIZE) {
|
||||
adc_buffer_index = 0;
|
||||
memcpy((void*)speech, (void*)adc_buffer, 2 * ADC_BUFFER_SIZE);
|
||||
audioModule->setIntervalFromNow(0); // process buffer immediately
|
||||
}
|
||||
} else if (state == State::rx) {
|
||||
|
||||
int16_t v;
|
||||
|
||||
//Get a value from audio_fifo and convert it to 0 - 255 to play it in the ADC
|
||||
//If none value is available the DAC will play the last one that was read, that's
|
||||
//why the rx_raw_audio_value variable is a global one.
|
||||
if (audio_fifo.get(&v))
|
||||
rx_raw_audio_value = (uint8_t)((v + 32768) / 256);
|
||||
|
||||
//Play
|
||||
dacWrite(moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP, rx_raw_audio_value);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL);
|
||||
display->setColor(BLACK);
|
||||
display->drawStringf(0 + x, 0 + y, buffer, "Codec2 Mode %d Audio", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1);
|
||||
display->setColor(WHITE);
|
||||
display->setFont(FONT_LARGE);
|
||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||
switch (radio_state) {
|
||||
case RadioState::tx:
|
||||
display->drawString(display->getWidth() / 2 + x, (display->getHeight() - FONT_HEIGHT_SMALL) / 2 + y, "PTT");
|
||||
break;
|
||||
default:
|
||||
display->drawString(display->getWidth() / 2 + x, (display->getHeight() - FONT_HEIGHT_SMALL) / 2 + y, "Receive");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t AudioModule::runOnce()
|
||||
{
|
||||
if (moduleConfig.audio.codec2_enabled) {
|
||||
|
||||
if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) {
|
||||
esp_err_t res;
|
||||
if (firstTime) {
|
||||
// Set up I2S Processor configuration. This will produce 16bit samples at 8 kHz instead of 12 from the ADC
|
||||
DEBUG_MSG("Initializing I2S SD: %d DIN: %d WS: %d SCK: %d\n", moduleConfig.audio.i2s_sd, moduleConfig.audio.i2s_din, moduleConfig.audio.i2s_ws, moduleConfig.audio.i2s_sck);
|
||||
i2s_config_t i2s_config = {
|
||||
.mode = (i2s_mode_t)(I2S_MODE_MASTER | (moduleConfig.audio.i2s_sd ? I2S_MODE_RX : 0) | (moduleConfig.audio.i2s_din ? I2S_MODE_TX : 0)),
|
||||
.sample_rate = 8000,
|
||||
.bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
|
||||
.channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
|
||||
.communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S),
|
||||
.intr_alloc_flags = 0,
|
||||
.dma_buf_count = 8,
|
||||
.dma_buf_len = adc_buffer_size, // 320 * 2 bytes
|
||||
.use_apll = false,
|
||||
.tx_desc_auto_clear = true,
|
||||
.fixed_mclk = 0
|
||||
};
|
||||
res = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
|
||||
if(res != ESP_OK)
|
||||
DEBUG_MSG("Failed to install I2S driver: %d\n", res);
|
||||
|
||||
DEBUG_MSG("Initializing ADC on Channel %u\n", moduleConfig.audio.mic_chan ? moduleConfig.audio.mic_chan : AMIC);
|
||||
const i2s_pin_config_t pin_config = {
|
||||
.bck_io_num = moduleConfig.audio.i2s_sck,
|
||||
.ws_io_num = moduleConfig.audio.i2s_ws,
|
||||
.data_out_num = moduleConfig.audio.i2s_din ? moduleConfig.audio.i2s_din : I2S_PIN_NO_CHANGE,
|
||||
.data_in_num = moduleConfig.audio.i2s_sd ? moduleConfig.audio.i2s_sd : I2S_PIN_NO_CHANGE
|
||||
};
|
||||
res = i2s_set_pin(I2S_PORT, &pin_config);
|
||||
if(res != ESP_OK)
|
||||
DEBUG_MSG("Failed to set I2S pin config: %d\n", res);
|
||||
|
||||
mic_chan = moduleConfig.audio.mic_chan ? (adc1_channel_t)(int)moduleConfig.audio.mic_chan : (adc1_channel_t)AMIC;
|
||||
adc1_config_width(ADC_WIDTH_12Bit);
|
||||
adc1_config_channel_atten(mic_chan, ADC_ATTEN_DB_6);
|
||||
|
||||
// Start a timer at 8kHz to sample the ADC and play the audio on the DAC.
|
||||
uint32_t cpufreq = getCpuFrequencyMhz();
|
||||
switch (cpufreq){
|
||||
case 160:
|
||||
adcTimer = timerBegin(3, 1000, true); // 160 MHz / 1000 = 160KHz
|
||||
break;
|
||||
case 240:
|
||||
adcTimer = timerBegin(3, 1500, true); // 240 MHz / 1500 = 160KHz
|
||||
break;
|
||||
case 320:
|
||||
adcTimer = timerBegin(3, 2000, true); // 320 MHz / 2000 = 160KHz
|
||||
break;
|
||||
case 80:
|
||||
default:
|
||||
adcTimer = timerBegin(3, 500, true); // 80 MHz / 500 = 160KHz
|
||||
break;
|
||||
}
|
||||
timerAttachInterrupt(adcTimer, &AudioModule::handleInterrupt, true);
|
||||
timerAlarmWrite(adcTimer, 20, true); // Interrupts when counter == 20, 8.000 times a second
|
||||
timerAlarmEnable(adcTimer);
|
||||
|
||||
DEBUG_MSG("Initializing DAC on Pin %u\n", moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP);
|
||||
DEBUG_MSG("Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN);
|
||||
res = i2s_start(I2S_PORT);
|
||||
if(res != ESP_OK)
|
||||
DEBUG_MSG("Failed to start I2S: %d\n", res);
|
||||
|
||||
radio_state = RadioState::rx;
|
||||
|
||||
// Configure PTT input
|
||||
pinMode(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN, INPUT_PULLUP);
|
||||
DEBUG_MSG("Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN);
|
||||
pinMode(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN, INPUT);
|
||||
|
||||
state = State::rx;
|
||||
|
||||
DEBUG_MSG("Setting up codec2 in mode %u\n", moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE);
|
||||
|
||||
codec2_state = codec2_create(moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE);
|
||||
codec2_set_lpc_post_filter(codec2_state, 1, 0, 0.8, 0.2);
|
||||
|
||||
firstTime = 0;
|
||||
firstTime = false;
|
||||
} else {
|
||||
// Check if we have a PTT press
|
||||
if (digitalRead(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN) == LOW) {
|
||||
// PTT pressed, recording
|
||||
state = State::tx;
|
||||
UIFrameEvent e = {false, true};
|
||||
// Check if PTT is pressed. TODO hook that into Onebutton/Interrupt drive.
|
||||
if (digitalRead(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN) == HIGH) {
|
||||
if (radio_state == RadioState::rx) {
|
||||
DEBUG_MSG("PTT pressed, switching to TX\n");
|
||||
radio_state = RadioState::tx;
|
||||
e.frameChanged = true;
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
} else {
|
||||
if (radio_state == RadioState::tx) {
|
||||
DEBUG_MSG("PTT released, switching to RX\n");
|
||||
if (tx_encode_frame_index > sizeof(tx_header)) {
|
||||
// Send the incomplete frame
|
||||
DEBUG_MSG("Sending %d codec2 bytes (incomplete)\n", tx_encode_frame_index);
|
||||
sendPayload();
|
||||
}
|
||||
tx_encode_frame_index = sizeof(tx_header);
|
||||
radio_state = RadioState::rx;
|
||||
e.frameChanged = true;
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
}
|
||||
if (state != State::standby) {
|
||||
run_codec2();
|
||||
if (radio_state == RadioState::tx) {
|
||||
// Get I2S data from the microphone and place in data buffer
|
||||
size_t bytesIn = 0;
|
||||
res = i2s_read(I2S_PORT, adc_buffer + adc_buffer_index, adc_buffer_size - adc_buffer_index, &bytesIn, pdMS_TO_TICKS(40)); // wait 40ms for audio to arrive.
|
||||
|
||||
if (res == ESP_OK) {
|
||||
adc_buffer_index += bytesIn;
|
||||
if (adc_buffer_index == adc_buffer_size) {
|
||||
adc_buffer_index = 0;
|
||||
memcpy((void*)speech, (void*)adc_buffer, 2 * adc_buffer_size);
|
||||
// Notify run_codec2 task that the buffer is ready.
|
||||
radio_state = RadioState::tx;
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
vTaskNotifyGiveFromISR(codec2HandlerTask, &xHigherPriorityTaskWoken);
|
||||
if (xHigherPriorityTaskWoken == pdTRUE)
|
||||
YIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 100;
|
||||
} else {
|
||||
DEBUG_MSG("Audio Module Disabled\n");
|
||||
|
||||
return INT32_MAX;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MeshPacket *AudioModule::allocReply()
|
||||
{
|
||||
|
||||
auto reply = allocDataPacket(); // Allocate a packet for sending
|
||||
|
||||
auto reply = allocDataPacket();
|
||||
return reply;
|
||||
}
|
||||
|
||||
bool AudioModule::shouldDraw()
|
||||
{
|
||||
if (!moduleConfig.audio.codec2_enabled) {
|
||||
return false;
|
||||
}
|
||||
return (radio_state == RadioState::tx);
|
||||
}
|
||||
|
||||
void AudioModule::sendPayload(NodeNum dest, bool wantReplies)
|
||||
{
|
||||
MeshPacket *p = allocReply();
|
||||
p->to = dest;
|
||||
p->decoded.want_response = wantReplies;
|
||||
|
||||
p->want_ack = AUDIO_MODULE_ACK;
|
||||
p->want_ack = false; // Audio is shoot&forget. No need to wait for ACKs.
|
||||
p->priority = MeshPacket_Priority_MAX; // Audio is important, because realtime
|
||||
|
||||
p->decoded.payload.size = ENCODE_FRAME_SIZE;
|
||||
p->decoded.payload.size = tx_encode_frame_index;
|
||||
memcpy(p->decoded.payload.bytes, tx_encode_frame, p->decoded.payload.size);
|
||||
|
||||
service.sendToMesh(p);
|
||||
@@ -221,17 +295,17 @@ void AudioModule::sendPayload(NodeNum dest, bool wantReplies)
|
||||
|
||||
ProcessMessage AudioModule::handleReceived(const MeshPacket &mp)
|
||||
{
|
||||
if (moduleConfig.audio.codec2_enabled) {
|
||||
if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) {
|
||||
auto &p = mp.decoded;
|
||||
if (getFrom(&mp) != nodeDB.getNodeNum()) {
|
||||
if (p.payload.size == ENCODE_FRAME_SIZE) {
|
||||
memcpy(rx_encode_frame, p.payload.bytes, p.payload.size);
|
||||
state = State::rx;
|
||||
audioModule->setIntervalFromNow(0);
|
||||
run_codec2();
|
||||
} else {
|
||||
DEBUG_MSG("Invalid payload size %u != %u\n", p.payload.size, ENCODE_FRAME_SIZE);
|
||||
}
|
||||
memcpy(rx_encode_frame, p.payload.bytes, p.payload.size);
|
||||
radio_state = RadioState::rx;
|
||||
rx_encode_frame_index = p.payload.size;
|
||||
// Notify run_codec2 task that the buffer is ready.
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
vTaskNotifyGiveFromISR(codec2HandlerTask, &xHigherPriorityTaskWoken);
|
||||
if (xHigherPriorityTaskWoken == pdTRUE)
|
||||
YIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,68 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include "SinglePortModule.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "concurrency/NotifiedWorkerThread.h"
|
||||
#include "configuration.h"
|
||||
#if defined(ARCH_ESP32)
|
||||
#include "NodeDB.h"
|
||||
#include <Arduino.h>
|
||||
#include <driver/adc.h>
|
||||
#include <driver/i2s.h>
|
||||
#include <functional>
|
||||
#if defined(ARCH_ESP32) && defined(USE_SX1280)
|
||||
#include <codec2.h>
|
||||
#include <ButterworthFilter.h>
|
||||
#include <FastAudioFIFO.h>
|
||||
#endif
|
||||
#include <OLEDDisplay.h>
|
||||
#include <OLEDDisplayUi.h>
|
||||
|
||||
#define ADC_BUFFER_SIZE 320 // 40ms of voice in 8KHz sampling frequency
|
||||
#define ENCODE_FRAME_SIZE 40 // 5 codec2 frames of 8 bytes each
|
||||
enum RadioState { standby, rx, tx };
|
||||
|
||||
class AudioModule : public SinglePortModule, private concurrency::OSThread
|
||||
const char c2_magic[3] = {0xc0, 0xde, 0xc2}; // Magic number for codec2 header
|
||||
|
||||
struct c2_header {
|
||||
char magic[3];
|
||||
char mode;
|
||||
};
|
||||
|
||||
#define ADC_BUFFER_SIZE_MAX 320
|
||||
#define PTT_PIN 39
|
||||
|
||||
#define I2S_PORT I2S_NUM_0
|
||||
|
||||
#define AUDIO_MODULE_RX_BUFFER 128
|
||||
#define AUDIO_MODULE_MODE ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700
|
||||
|
||||
class AudioModule : public SinglePortModule, public Observable<const UIFrameEvent *>, private concurrency::OSThread
|
||||
{
|
||||
#if defined(ARCH_ESP32) && defined(USE_SX1280)
|
||||
bool firstTime = 1;
|
||||
hw_timer_t* adcTimer = NULL;
|
||||
uint16_t adc_buffer[ADC_BUFFER_SIZE] = {};
|
||||
int16_t speech[ADC_BUFFER_SIZE] = {};
|
||||
int16_t output_buffer[ADC_BUFFER_SIZE] = {};
|
||||
unsigned char rx_encode_frame[ENCODE_FRAME_SIZE] = {};
|
||||
unsigned char tx_encode_frame[ENCODE_FRAME_SIZE] = {};
|
||||
int tx_encode_frame_index = 0;
|
||||
FastAudioFIFO audio_fifo;
|
||||
uint16_t adc_buffer_index = 0;
|
||||
adc1_channel_t mic_chan = (adc1_channel_t)0;
|
||||
struct CODEC2* codec2_state = NULL;
|
||||
|
||||
enum State
|
||||
{
|
||||
standby, rx, tx
|
||||
};
|
||||
volatile State state = State::tx;
|
||||
|
||||
public:
|
||||
unsigned char rx_encode_frame[Constants_DATA_PAYLOAD_LEN] = {};
|
||||
unsigned char tx_encode_frame[Constants_DATA_PAYLOAD_LEN] = {};
|
||||
c2_header tx_header = {};
|
||||
int16_t speech[ADC_BUFFER_SIZE_MAX] = {};
|
||||
int16_t output_buffer[ADC_BUFFER_SIZE_MAX] = {};
|
||||
uint16_t adc_buffer[ADC_BUFFER_SIZE_MAX] = {};
|
||||
int adc_buffer_size = 0;
|
||||
uint16_t adc_buffer_index = 0;
|
||||
int tx_encode_frame_index = sizeof(c2_header); // leave room for header
|
||||
int rx_encode_frame_index = 0;
|
||||
int encode_codec_size = 0;
|
||||
int encode_frame_size = 0;
|
||||
volatile RadioState radio_state = RadioState::rx;
|
||||
|
||||
struct CODEC2* codec2 = NULL;
|
||||
// int16_t sample;
|
||||
|
||||
AudioModule();
|
||||
|
||||
bool shouldDraw();
|
||||
|
||||
/**
|
||||
* Send our payload into the mesh
|
||||
*/
|
||||
void sendPayload(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
|
||||
|
||||
protected:
|
||||
int encode_frame_num = 0;
|
||||
bool firstTime = true;
|
||||
|
||||
virtual int32_t runOnce() override;
|
||||
|
||||
static void handleInterrupt();
|
||||
|
||||
void onTimer();
|
||||
|
||||
void run_codec2();
|
||||
|
||||
virtual MeshPacket *allocReply() override;
|
||||
|
||||
virtual bool wantUIFrame() override { return this->shouldDraw(); }
|
||||
virtual Observable<const UIFrameEvent *>* getUIFrameObservable() override { return this; }
|
||||
#if !HAS_SCREEN
|
||||
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||
#else
|
||||
virtual void drawFrame(
|
||||
OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override;
|
||||
#endif
|
||||
|
||||
/** Called to handle a particular incoming message
|
||||
* @return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it
|
||||
*/
|
||||
virtual ProcessMessage handleReceived(const MeshPacket &mp) override;
|
||||
#endif
|
||||
};
|
||||
|
||||
extern AudioModule *audioModule;
|
||||
|
||||
#endif
|
||||
@@ -155,8 +155,8 @@ ProcessMessage RangeTestModuleRadio::handleReceived(const MeshPacket &mp)
|
||||
DEBUG_MSG("mp.from %d\n", mp.from);
|
||||
DEBUG_MSG("mp.rx_snr %f\n", mp.rx_snr);
|
||||
DEBUG_MSG("mp.hop_limit %d\n", mp.hop_limit);
|
||||
DEBUG_MSG("mp.decoded.position.latitude_i %d\n", mp.decoded.position.latitude_i); // Depricated
|
||||
DEBUG_MSG("mp.decoded.position.longitude_i %d\n", mp.decoded.position.longitude_i); // Depricated
|
||||
// DEBUG_MSG("mp.decoded.position.latitude_i %d\n", mp.decoded.position.latitude_i); // Depricated
|
||||
// DEBUG_MSG("mp.decoded.position.longitude_i %d\n", mp.decoded.position.longitude_i); // Depricated
|
||||
DEBUG_MSG("---- Node Information of Received Packet (mp.from):\n");
|
||||
DEBUG_MSG("n->user.long_name %s\n", n->user.long_name);
|
||||
DEBUG_MSG("n->user.short_name %s\n", n->user.short_name);
|
||||
|
||||
@@ -16,52 +16,50 @@ StoreForwardModule *storeForwardModule;
|
||||
|
||||
int32_t StoreForwardModule::runOnce()
|
||||
{
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
if (moduleConfig.store_forward.enabled && is_server) {
|
||||
// Send out the message queue.
|
||||
if (this->busy) {
|
||||
// Only send packets if the channel is less than 25% utilized.
|
||||
if (airTime->channelUtilizationPercent() < polite_channel_util_percent) {
|
||||
|
||||
if (moduleConfig.store_forward.enabled) {
|
||||
// DEBUG_MSG("--- --- --- In busy loop 1 %d\n", this->packetHistoryTXQueue_index);
|
||||
storeForwardModule->sendPayload(this->busyTo, this->packetHistoryTXQueue_index);
|
||||
|
||||
if (config.device.role == Config_DeviceConfig_Role_ROUTER) {
|
||||
|
||||
// Send out the message queue.
|
||||
if (this->busy) {
|
||||
|
||||
// Only send packets if the channel is less than 25% utilized.
|
||||
if (airTime->channelUtilizationPercent() < polite_channel_util_percent) {
|
||||
|
||||
// DEBUG_MSG("--- --- --- In busy loop 1 %d\n", this->packetHistoryTXQueue_index);
|
||||
storeForwardModule->sendPayload(this->busyTo, this->packetHistoryTXQueue_index);
|
||||
|
||||
if (this->packetHistoryTXQueue_index == packetHistoryTXQueue_size) {
|
||||
strcpy(this->routerMessage, "** S&F - Done");
|
||||
storeForwardModule->sendMessage(this->busyTo, this->routerMessage);
|
||||
|
||||
// DEBUG_MSG("--- --- --- In busy loop - Done \n");
|
||||
this->packetHistoryTXQueue_index = 0;
|
||||
this->busy = false;
|
||||
} else {
|
||||
this->packetHistoryTXQueue_index++;
|
||||
}
|
||||
if (this->packetHistoryTXQueue_index == packetHistoryTXQueue_size) {
|
||||
strcpy(this->routerMessage, "** S&F - Done");
|
||||
storeForwardModule->sendMessage(this->busyTo, this->routerMessage);
|
||||
|
||||
// DEBUG_MSG("--- --- --- In busy loop - Done \n");
|
||||
this->packetHistoryTXQueue_index = 0;
|
||||
this->busy = false;
|
||||
} else {
|
||||
DEBUG_MSG("Channel utilization is too high. Skipping this opportunity to send and will retry later.\n");
|
||||
this->packetHistoryTXQueue_index++;
|
||||
}
|
||||
|
||||
} else {
|
||||
DEBUG_MSG("Channel utilization is too high. Retrying later.\n");
|
||||
}
|
||||
DEBUG_MSG("SF myNodeInfo.bitrate = %f bytes / sec\n", myNodeInfo.bitrate);
|
||||
DEBUG_MSG("SF bitrate = %f bytes / sec\n", myNodeInfo.bitrate);
|
||||
|
||||
return (this->packetTimeMax);
|
||||
} else {
|
||||
DEBUG_MSG("Store & Forward Module - Disabled (is_router = false)\n");
|
||||
} else if (millis() - lastHeartbeat > 300000) {
|
||||
lastHeartbeat = millis();
|
||||
DEBUG_MSG("Sending heartbeat\n");
|
||||
|
||||
StoreAndForward sf;
|
||||
sf.rr = StoreAndForward_RequestResponse_ROUTER_HEARTBEAT;
|
||||
sf.has_heartbeat = true;
|
||||
sf.heartbeat.period = 300;
|
||||
sf.heartbeat.secondary = 0; // TODO we always have one primary router for now
|
||||
|
||||
return (INT32_MAX);
|
||||
MeshPacket *p = allocDataProtobuf(sf);
|
||||
p->to = NODENUM_BROADCAST;
|
||||
p->decoded.want_response = false;
|
||||
p->priority = MeshPacket_Priority_MIN;
|
||||
service.sendToMesh(p);
|
||||
}
|
||||
|
||||
} else {
|
||||
DEBUG_MSG("Store & Forward Module - Disabled\n");
|
||||
|
||||
return (INT32_MAX);
|
||||
return (this->packetTimeMax);
|
||||
}
|
||||
|
||||
#endif
|
||||
return (INT32_MAX);
|
||||
}
|
||||
@@ -76,12 +74,7 @@ void StoreForwardModule::populatePSRAM()
|
||||
https://learn.upesy.com/en/programmation/psram.html#psram-tab
|
||||
*/
|
||||
|
||||
DEBUG_MSG("Before PSRAM initilization:\n");
|
||||
|
||||
DEBUG_MSG(" Total heap: %d\n", ESP.getHeapSize());
|
||||
DEBUG_MSG(" Free heap: %d\n", ESP.getFreeHeap());
|
||||
DEBUG_MSG(" Total PSRAM: %d\n", ESP.getPsramSize());
|
||||
DEBUG_MSG(" Free PSRAM: %d\n", ESP.getFreePsram());
|
||||
DEBUG_MSG("Before PSRAM initilization: heap %d/%d PSRAM %d/%d\n", ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getFreePsram(), ESP.getPsramSize());
|
||||
|
||||
this->packetHistoryTXQueue =
|
||||
static_cast<PacketHistoryStruct *>(ps_calloc(this->historyReturnMax, sizeof(PacketHistoryStruct)));
|
||||
@@ -93,19 +86,12 @@ void StoreForwardModule::populatePSRAM()
|
||||
|
||||
this->packetHistory = static_cast<PacketHistoryStruct *>(ps_calloc(numberOfPackets, sizeof(PacketHistoryStruct)));
|
||||
|
||||
DEBUG_MSG("After PSRAM initilization:\n");
|
||||
|
||||
DEBUG_MSG(" Total heap: %d\n", ESP.getHeapSize());
|
||||
DEBUG_MSG(" Free heap: %d\n", ESP.getFreeHeap());
|
||||
DEBUG_MSG(" Total PSRAM: %d\n", ESP.getPsramSize());
|
||||
DEBUG_MSG(" Free PSRAM: %d\n", ESP.getFreePsram());
|
||||
DEBUG_MSG("Store and Forward Stats:\n");
|
||||
DEBUG_MSG(" numberOfPackets for packetHistory - %u\n", numberOfPackets);
|
||||
DEBUG_MSG("After PSRAM initilization: heap %d/%d PSRAM %d/%d\n", ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getFreePsram(), ESP.getPsramSize());
|
||||
DEBUG_MSG("numberOfPackets for packetHistory - %u\n", numberOfPackets);
|
||||
}
|
||||
|
||||
void StoreForwardModule::historyReport()
|
||||
{
|
||||
DEBUG_MSG("Iterating through the message history...\n");
|
||||
DEBUG_MSG("Message history contains %u records\n", this->packetHistoryCurrent);
|
||||
}
|
||||
|
||||
@@ -246,8 +232,8 @@ ProcessMessage StoreForwardModule::handleReceived(const MeshPacket &mp)
|
||||
|
||||
DEBUG_MSG("--- S&F Received something\n");
|
||||
|
||||
// The router node should not be sending messages as a client.
|
||||
if (getFrom(&mp) != nodeDB.getNodeNum()) {
|
||||
// The router node should not be sending messages as a client. Unless he is a ROUTER_CLIENT
|
||||
if ((getFrom(&mp) != nodeDB.getNodeNum()) || (config.device.role == Config_DeviceConfig_Role_ROUTER_CLIENT)) {
|
||||
|
||||
if (mp.decoded.portnum == PortNum_TEXT_MESSAGE_APP) {
|
||||
DEBUG_MSG("Packet came from - PortNum_TEXT_MESSAGE_APP\n");
|
||||
@@ -264,6 +250,7 @@ ProcessMessage StoreForwardModule::handleReceived(const MeshPacket &mp)
|
||||
} else {
|
||||
storeForwardModule->historySend(1000 * 60, getFrom(&mp));
|
||||
}
|
||||
|
||||
} else if ((p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && (p.payload.bytes[2] == 'm') &&
|
||||
(p.payload.bytes[3] == 0x00)) {
|
||||
strlcpy(this->routerMessage,
|
||||
@@ -278,14 +265,12 @@ ProcessMessage StoreForwardModule::handleReceived(const MeshPacket &mp)
|
||||
}
|
||||
|
||||
} else if (mp.decoded.portnum == PortNum_STORE_FORWARD_APP) {
|
||||
DEBUG_MSG("Packet came from an PortNum_STORE_FORWARD_APP port %u\n", mp.decoded.portnum);
|
||||
|
||||
} else {
|
||||
DEBUG_MSG("Packet came from an unknown port %u\n", mp.decoded.portnum);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
DEBUG_MSG("Store & Forward Module - Disabled\n");
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -293,92 +278,107 @@ ProcessMessage StoreForwardModule::handleReceived(const MeshPacket &mp)
|
||||
return ProcessMessage::CONTINUE; // Let others look at this message also if they want
|
||||
}
|
||||
|
||||
ProcessMessage StoreForwardModule::handleReceivedProtobuf(const MeshPacket &mp, StoreAndForward *p)
|
||||
bool StoreForwardModule::handleReceivedProtobuf(const MeshPacket &mp, StoreAndForward *p)
|
||||
{
|
||||
if (!moduleConfig.store_forward.enabled) {
|
||||
// If this module is not enabled in any capacity, don't handle the packet, and allow other modules to consume
|
||||
return ProcessMessage::CONTINUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mp.decoded.portnum == PortNum_TEXT_MESSAGE_APP) {
|
||||
DEBUG_MSG("Packet came from an PortNum_TEXT_MESSAGE_APP port %u\n", mp.decoded.portnum);
|
||||
return ProcessMessage::CONTINUE;
|
||||
} else if (mp.decoded.portnum == PortNum_STORE_FORWARD_APP) {
|
||||
DEBUG_MSG("Packet came from an PortNum_STORE_FORWARD_APP port %u\n", mp.decoded.portnum);
|
||||
|
||||
if (mp.decoded.portnum != PortNum_STORE_FORWARD_APP) {
|
||||
DEBUG_MSG("Packet came from port %u\n", mp.decoded.portnum);
|
||||
return false;
|
||||
} else {
|
||||
DEBUG_MSG("Packet came from an UNKNOWN port %u\n", mp.decoded.portnum);
|
||||
return ProcessMessage::CONTINUE;
|
||||
}
|
||||
DEBUG_MSG("Packet came from PortNum_STORE_FORWARD_APP port %u\n", mp.decoded.portnum);
|
||||
|
||||
|
||||
switch (p->rr) {
|
||||
case StoreAndForward_RequestResponse_CLIENT_ERROR:
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_ERROR\n");
|
||||
break;
|
||||
case StoreAndForward_RequestResponse_CLIENT_ERROR:
|
||||
if(is_server) {
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_ERROR\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case StoreAndForward_RequestResponse_CLIENT_HISTORY:
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_HISTORY\n");
|
||||
case StoreAndForward_RequestResponse_CLIENT_HISTORY:
|
||||
if(is_server) {
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_HISTORY\n");
|
||||
// Send the last 60 minutes of messages.
|
||||
if (this->busy) {
|
||||
strcpy(this->routerMessage, "** S&F - Busy. Try again shortly.");
|
||||
storeForwardModule->sendMessage(getFrom(&mp), this->routerMessage);
|
||||
} else {
|
||||
storeForwardModule->historySend(1000 * 60, getFrom(&mp));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Send the last 60 minutes of messages.
|
||||
if (this->busy) {
|
||||
strcpy(this->routerMessage, "** S&F - Busy. Try again shortly.");
|
||||
storeForwardModule->sendMessage(getFrom(&mp), this->routerMessage);
|
||||
} else {
|
||||
storeForwardModule->historySend(1000 * 60, getFrom(&mp));
|
||||
case StoreAndForward_RequestResponse_CLIENT_PING:
|
||||
if(is_server) {
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_PING\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case StoreAndForward_RequestResponse_CLIENT_PONG:
|
||||
if(is_server) {
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_PONG\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case StoreAndForward_RequestResponse_CLIENT_STATS:
|
||||
if(is_server) {
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_STATS\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case StoreAndForward_RequestResponse_ROUTER_BUSY:
|
||||
if(is_client) {
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_BUSY\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case StoreAndForward_RequestResponse_ROUTER_ERROR:
|
||||
if(is_client) {
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_ERROR\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case StoreAndForward_RequestResponse_ROUTER_HEARTBEAT:
|
||||
if(is_client) {
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_HEARTBEAT\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case StoreAndForward_RequestResponse_ROUTER_PING:
|
||||
if(is_client) {
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_PING\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case StoreAndForward_RequestResponse_ROUTER_PONG:
|
||||
if(is_client) {
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_PONG\n");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0); // unexpected state - FIXME, make an error code and reboot
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case StoreAndForward_RequestResponse_CLIENT_PING:
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_PING\n");
|
||||
break;
|
||||
|
||||
case StoreAndForward_RequestResponse_CLIENT_PONG:
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_PONG\n");
|
||||
break;
|
||||
|
||||
case StoreAndForward_RequestResponse_CLIENT_STATS:
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_STATS\n");
|
||||
break;
|
||||
|
||||
case StoreAndForward_RequestResponse_ROUTER_BUSY:
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_BUSY\n");
|
||||
break;
|
||||
|
||||
case StoreAndForward_RequestResponse_ROUTER_ERROR:
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_ERROR\n");
|
||||
break;
|
||||
|
||||
case StoreAndForward_RequestResponse_ROUTER_HEARTBEAT:
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_HEARTBEAT\n");
|
||||
break;
|
||||
|
||||
case StoreAndForward_RequestResponse_ROUTER_PING:
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_PING\n");
|
||||
break;
|
||||
|
||||
case StoreAndForward_RequestResponse_ROUTER_PONG:
|
||||
// Do nothing
|
||||
DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_PONG\n");
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0); // unexpected state - FIXME, make an error code and reboot
|
||||
}
|
||||
|
||||
return ProcessMessage::STOP; // There's no need for others to look at this message.
|
||||
return true; // There's no need for others to look at this message.
|
||||
}
|
||||
|
||||
StoreForwardModule::StoreForwardModule()
|
||||
: SinglePortModule("StoreForwardModule", PortNum_TEXT_MESSAGE_APP), concurrency::OSThread("StoreForwardModule")
|
||||
: concurrency::OSThread("StoreForwardModule"), ProtobufModule("StoreForward", PortNum_STORE_FORWARD_APP, &StoreAndForward_msg)
|
||||
{
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
@@ -397,9 +397,9 @@ StoreForwardModule::StoreForwardModule()
|
||||
if (moduleConfig.store_forward.enabled) {
|
||||
|
||||
// Router
|
||||
if (config.device.role == Config_DeviceConfig_Role_ROUTER) {
|
||||
DEBUG_MSG("Initializing Store & Forward Module - Enabled as Router\n");
|
||||
if (ESP.getPsramSize()) {
|
||||
if ((config.device.role == Config_DeviceConfig_Role_ROUTER) || (config.device.role == Config_DeviceConfig_Role_ROUTER_CLIENT)) {
|
||||
DEBUG_MSG("Initializing Store & Forward Module in Router mode\n");
|
||||
if (ESP.getPsramSize() > 0) {
|
||||
if (ESP.getFreePsram() >= 1024 * 1024) {
|
||||
|
||||
// Do the startup here
|
||||
@@ -416,26 +416,27 @@ StoreForwardModule::StoreForwardModule()
|
||||
if (moduleConfig.store_forward.records)
|
||||
this->records = moduleConfig.store_forward.records;
|
||||
|
||||
// Maximum number of records to store in memory
|
||||
// send heartbeat advertising?
|
||||
if (moduleConfig.store_forward.heartbeat)
|
||||
this->heartbeat = moduleConfig.store_forward.heartbeat;
|
||||
|
||||
// Popupate PSRAM with our data structures.
|
||||
this->populatePSRAM();
|
||||
|
||||
is_server = true;
|
||||
} else {
|
||||
DEBUG_MSG("Device has less than 1M of PSRAM free. Aborting startup.\n");
|
||||
DEBUG_MSG("Store & Forward Module - Aborting Startup.\n");
|
||||
DEBUG_MSG("Device has less than 1M of PSRAM free.\n");
|
||||
DEBUG_MSG("Store & Forward Module - disabling server.\n");
|
||||
}
|
||||
|
||||
} else {
|
||||
DEBUG_MSG("Device doesn't have PSRAM.\n");
|
||||
DEBUG_MSG("Store & Forward Module - Aborting Startup.\n");
|
||||
DEBUG_MSG("Store & Forward Module - disabling server.\n");
|
||||
}
|
||||
|
||||
// Client
|
||||
} else {
|
||||
DEBUG_MSG("Initializing Store & Forward Module - Enabled as Client\n");
|
||||
}
|
||||
if ((config.device.role == Config_DeviceConfig_Role_CLIENT) || (config.device.role == Config_DeviceConfig_Role_ROUTER_CLIENT)) {
|
||||
is_client = true;
|
||||
DEBUG_MSG("Initializing Store & Forward Module in Client mode\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "SinglePortModule.h"
|
||||
#include "ProtobufModule.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "mesh/generated/storeforward.pb.h"
|
||||
|
||||
@@ -18,9 +18,8 @@ struct PacketHistoryStruct {
|
||||
pb_size_t payload_size;
|
||||
};
|
||||
|
||||
class StoreForwardModule : public SinglePortModule, private concurrency::OSThread
|
||||
class StoreForwardModule : private concurrency::OSThread, public ProtobufModule<StoreAndForward>
|
||||
{
|
||||
// bool firstTime = 1;
|
||||
bool busy = 0;
|
||||
uint32_t busyTo = 0;
|
||||
char routerMessage[Constants_DATA_PAYLOAD_LEN] = {0};
|
||||
@@ -34,7 +33,12 @@ class StoreForwardModule : public SinglePortModule, private concurrency::OSThrea
|
||||
uint32_t packetHistoryTXQueue_size = 0;
|
||||
uint32_t packetHistoryTXQueue_index = 0;
|
||||
|
||||
uint32_t packetTimeMax = 2000;
|
||||
uint32_t packetTimeMax = 5000;
|
||||
|
||||
unsigned long lastHeartbeat = 0;
|
||||
|
||||
bool is_client = false;
|
||||
bool is_server = false;
|
||||
|
||||
public:
|
||||
StoreForwardModule();
|
||||
@@ -78,7 +82,7 @@ class StoreForwardModule : public SinglePortModule, private concurrency::OSThrea
|
||||
it
|
||||
*/
|
||||
virtual ProcessMessage handleReceived(const MeshPacket &mp) override;
|
||||
virtual ProcessMessage handleReceivedProtobuf(const MeshPacket &mp, StoreAndForward *p);
|
||||
virtual bool handleReceivedProtobuf(const MeshPacket &mp, StoreAndForward *p);
|
||||
|
||||
};
|
||||
|
||||
|
||||
241
src/mqtt/JSON.cpp
Normal file
241
src/mqtt/JSON.cpp
Normal file
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* File JSON.cpp part of the SimpleJSON Library - http://mjpa.in/json
|
||||
*
|
||||
* Copyright (C) 2010 Mike Anchor
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "JSON.h"
|
||||
|
||||
/**
|
||||
* Blocks off the public constructor
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
*/
|
||||
JSON::JSON()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a complete JSON encoded string
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param char* data The JSON text
|
||||
*
|
||||
* @return JSONValue* Returns a JSON Value representing the root, or NULL on error
|
||||
*/
|
||||
JSONValue *JSON::Parse(const char *data)
|
||||
{
|
||||
// Skip any preceding whitespace, end of data = no JSON = fail
|
||||
if (!SkipWhitespace(&data))
|
||||
return NULL;
|
||||
|
||||
// We need the start of a value here now...
|
||||
JSONValue *value = JSONValue::Parse(&data);
|
||||
if (value == NULL)
|
||||
return NULL;
|
||||
|
||||
// Can be white space now and should be at the end of the string then...
|
||||
if (SkipWhitespace(&data))
|
||||
{
|
||||
delete value;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// We're now at the end of the string
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns the passed in JSONValue into a JSON encode string
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param JSONValue* value The root value
|
||||
*
|
||||
* @return std::string Returns a JSON encoded string representation of the given value
|
||||
*/
|
||||
std::string JSON::Stringify(const JSONValue *value)
|
||||
{
|
||||
if (value != NULL)
|
||||
return value->Stringify();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips over any whitespace characters (space, tab, \r or \n) defined by the JSON spec
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param char** data Pointer to a char* that contains the JSON text
|
||||
*
|
||||
* @return bool Returns true if there is more data, or false if the end of the text was reached
|
||||
*/
|
||||
bool JSON::SkipWhitespace(const char **data)
|
||||
{
|
||||
while (**data != 0 && (**data == ' ' || **data == '\t' || **data == '\r' || **data == '\n'))
|
||||
(*data)++;
|
||||
|
||||
return **data != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a JSON String as defined by the spec - "<some chars>"
|
||||
* Any escaped characters are swapped out for their unescaped values
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param char** data Pointer to a char* that contains the JSON text
|
||||
* @param std::string& str Reference to a std::string to receive the extracted string
|
||||
*
|
||||
* @return bool Returns true on success, false on failure
|
||||
*/
|
||||
bool JSON::ExtractString(const char **data, std::string &str)
|
||||
{
|
||||
str = "";
|
||||
|
||||
while (**data != 0)
|
||||
{
|
||||
// Save the char so we can change it if need be
|
||||
char next_char = **data;
|
||||
|
||||
// Escaping something?
|
||||
if (next_char == '\\')
|
||||
{
|
||||
// Move over the escape char
|
||||
(*data)++;
|
||||
|
||||
// Deal with the escaped char
|
||||
switch (**data)
|
||||
{
|
||||
case '"': next_char = '"'; break;
|
||||
case '\\': next_char = '\\'; break;
|
||||
case '/': next_char = '/'; break;
|
||||
case 'b': next_char = '\b'; break;
|
||||
case 'f': next_char = '\f'; break;
|
||||
case 'n': next_char = '\n'; break;
|
||||
case 'r': next_char = '\r'; break;
|
||||
case 't': next_char = '\t'; break;
|
||||
case 'u':
|
||||
{
|
||||
// We need 5 chars (4 hex + the 'u') or its not valid
|
||||
if (!simplejson_csnlen(*data, 5))
|
||||
return false;
|
||||
|
||||
// Deal with the chars
|
||||
next_char = 0;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
// Do it first to move off the 'u' and leave us on the
|
||||
// final hex digit as we move on by one later on
|
||||
(*data)++;
|
||||
|
||||
next_char <<= 4;
|
||||
|
||||
// Parse the hex digit
|
||||
if (**data >= '0' && **data <= '9')
|
||||
next_char |= (**data - '0');
|
||||
else if (**data >= 'A' && **data <= 'F')
|
||||
next_char |= (10 + (**data - 'A'));
|
||||
else if (**data >= 'a' && **data <= 'f')
|
||||
next_char |= (10 + (**data - 'a'));
|
||||
else
|
||||
{
|
||||
// Invalid hex digit = invalid JSON
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// By the spec, only the above cases are allowed
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// End of the string?
|
||||
else if (next_char == '"')
|
||||
{
|
||||
(*data)++;
|
||||
str.reserve(); // Remove unused capacity
|
||||
return true;
|
||||
}
|
||||
|
||||
// Disallowed char?
|
||||
else if (next_char < ' ' && next_char != '\t')
|
||||
{
|
||||
// SPEC Violation: Allow tabs due to real world cases
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add the next char
|
||||
str += next_char;
|
||||
|
||||
// Move on
|
||||
(*data)++;
|
||||
}
|
||||
|
||||
// If we're here, the string ended incorrectly
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses some text as though it is an integer
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param char** data Pointer to a char* that contains the JSON text
|
||||
*
|
||||
* @return double Returns the double value of the number found
|
||||
*/
|
||||
double JSON::ParseInt(const char **data)
|
||||
{
|
||||
double integer = 0;
|
||||
while (**data != 0 && **data >= '0' && **data <= '9')
|
||||
integer = integer * 10 + (*(*data)++ - '0');
|
||||
|
||||
return integer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses some text as though it is a decimal
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param char** data Pointer to a char* that contains the JSON text
|
||||
*
|
||||
* @return double Returns the double value of the decimal found
|
||||
*/
|
||||
double JSON::ParseDecimal(const char **data)
|
||||
{
|
||||
double decimal = 0.0;
|
||||
double factor = 0.1;
|
||||
while (**data != 0 && **data >= '0' && **data <= '9')
|
||||
{
|
||||
int digit = (*(*data)++ - '0');
|
||||
decimal = decimal + digit * factor;
|
||||
factor *= 0.1;
|
||||
}
|
||||
return decimal;
|
||||
}
|
||||
70
src/mqtt/JSON.h
Normal file
70
src/mqtt/JSON.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* File JSON.h part of the SimpleJSON Library - http://mjpa.in/json
|
||||
*
|
||||
* Copyright (C) 2010 Mike Anchor
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef _JSON_H_
|
||||
#define _JSON_H_
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <cstring>
|
||||
|
||||
// Simple function to check a string 's' has at least 'n' characters
|
||||
static inline bool simplejson_csnlen(const char *s, size_t n) {
|
||||
if (s == 0)
|
||||
return false;
|
||||
|
||||
const char *save = s;
|
||||
while (n-- > 0)
|
||||
{
|
||||
if (*(save++) == 0) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Custom types
|
||||
class JSONValue;
|
||||
typedef std::vector<JSONValue*> JSONArray;
|
||||
typedef std::map<std::string, JSONValue*> JSONObject;
|
||||
|
||||
#include "JSONValue.h"
|
||||
|
||||
class JSON
|
||||
{
|
||||
friend class JSONValue;
|
||||
|
||||
public:
|
||||
static JSONValue* Parse(const char *data);
|
||||
static std::string Stringify(const JSONValue *value);
|
||||
protected:
|
||||
static bool SkipWhitespace(const char **data);
|
||||
static bool ExtractString(const char **data, std::string &str);
|
||||
static double ParseInt(const char **data);
|
||||
static double ParseDecimal(const char **data);
|
||||
private:
|
||||
JSON();
|
||||
};
|
||||
|
||||
#endif
|
||||
940
src/mqtt/JSONValue.cpp
Normal file
940
src/mqtt/JSONValue.cpp
Normal file
@@ -0,0 +1,940 @@
|
||||
/*
|
||||
* File JSONValue.cpp part of the SimpleJSON Library - http://mjpa.in/json
|
||||
*
|
||||
* Copyright (C) 2010 Mike Anchor
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <math.h>
|
||||
|
||||
#include "JSONValue.h"
|
||||
|
||||
// Macros to free an array/object
|
||||
#define FREE_ARRAY(x) { JSONArray::iterator iter; for (iter = x.begin(); iter != x.end(); iter++) { delete *iter; } }
|
||||
#define FREE_OBJECT(x) { JSONObject::iterator iter; for (iter = x.begin(); iter != x.end(); iter++) { delete (*iter).second; } }
|
||||
|
||||
/**
|
||||
* Parses a JSON encoded value to a JSONValue object
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param char** data Pointer to a char* that contains the data
|
||||
*
|
||||
* @return JSONValue* Returns a pointer to a JSONValue object on success, NULL on error
|
||||
*/
|
||||
JSONValue *JSONValue::Parse(const char **data)
|
||||
{
|
||||
// Is it a string?
|
||||
if (**data == '"')
|
||||
{
|
||||
std::string str;
|
||||
if (!JSON::ExtractString(&(++(*data)), str))
|
||||
return NULL;
|
||||
else
|
||||
return new JSONValue(str);
|
||||
}
|
||||
|
||||
// Is it a boolean?
|
||||
else if ((simplejson_csnlen(*data, 4) && strncasecmp(*data, "true", 4) == 0) || (simplejson_csnlen(*data, 5) && strncasecmp(*data, "false", 5) == 0))
|
||||
{
|
||||
bool value = strncasecmp(*data, "true", 4) == 0;
|
||||
(*data) += value ? 4 : 5;
|
||||
return new JSONValue(value);
|
||||
}
|
||||
|
||||
// Is it a null?
|
||||
else if (simplejson_csnlen(*data, 4) && strncasecmp(*data, "null", 4) == 0)
|
||||
{
|
||||
(*data) += 4;
|
||||
return new JSONValue();
|
||||
}
|
||||
|
||||
// Is it a number?
|
||||
else if (**data == '-' || (**data >= '0' && **data <= '9'))
|
||||
{
|
||||
// Negative?
|
||||
bool neg = **data == '-';
|
||||
if (neg) (*data)++;
|
||||
|
||||
double number = 0.0;
|
||||
|
||||
// Parse the whole part of the number - only if it wasn't 0
|
||||
if (**data == '0')
|
||||
(*data)++;
|
||||
else if (**data >= '1' && **data <= '9')
|
||||
number = JSON::ParseInt(data);
|
||||
else
|
||||
return NULL;
|
||||
|
||||
// Could be a decimal now...
|
||||
if (**data == '.')
|
||||
{
|
||||
(*data)++;
|
||||
|
||||
// Not get any digits?
|
||||
if (!(**data >= '0' && **data <= '9'))
|
||||
return NULL;
|
||||
|
||||
// Find the decimal and sort the decimal place out
|
||||
// Use ParseDecimal as ParseInt won't work with decimals less than 0.1
|
||||
// thanks to Javier Abadia for the report & fix
|
||||
double decimal = JSON::ParseDecimal(data);
|
||||
|
||||
// Save the number
|
||||
number += decimal;
|
||||
}
|
||||
|
||||
// Could be an exponent now...
|
||||
if (**data == 'E' || **data == 'e')
|
||||
{
|
||||
(*data)++;
|
||||
|
||||
// Check signage of expo
|
||||
bool neg_expo = false;
|
||||
if (**data == '-' || **data == '+')
|
||||
{
|
||||
neg_expo = **data == '-';
|
||||
(*data)++;
|
||||
}
|
||||
|
||||
// Not get any digits?
|
||||
if (!(**data >= '0' && **data <= '9'))
|
||||
return NULL;
|
||||
|
||||
// Sort the expo out
|
||||
double expo = JSON::ParseInt(data);
|
||||
for (double i = 0.0; i < expo; i++)
|
||||
number = neg_expo ? (number / 10.0) : (number * 10.0);
|
||||
}
|
||||
|
||||
// Was it neg?
|
||||
if (neg) number *= -1;
|
||||
|
||||
return new JSONValue(number);
|
||||
}
|
||||
|
||||
// An object?
|
||||
else if (**data == '{')
|
||||
{
|
||||
JSONObject object;
|
||||
|
||||
(*data)++;
|
||||
|
||||
while (**data != 0)
|
||||
{
|
||||
// Whitespace at the start?
|
||||
if (!JSON::SkipWhitespace(data))
|
||||
{
|
||||
FREE_OBJECT(object);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Special case - empty object
|
||||
if (object.size() == 0 && **data == '}')
|
||||
{
|
||||
(*data)++;
|
||||
return new JSONValue(object);
|
||||
}
|
||||
|
||||
// We want a string now...
|
||||
std::string name;
|
||||
if (!JSON::ExtractString(&(++(*data)), name))
|
||||
{
|
||||
FREE_OBJECT(object);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// More whitespace?
|
||||
if (!JSON::SkipWhitespace(data))
|
||||
{
|
||||
FREE_OBJECT(object);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Need a : now
|
||||
if (*((*data)++) != ':')
|
||||
{
|
||||
FREE_OBJECT(object);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// More whitespace?
|
||||
if (!JSON::SkipWhitespace(data))
|
||||
{
|
||||
FREE_OBJECT(object);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// The value is here
|
||||
JSONValue *value = Parse(data);
|
||||
if (value == NULL)
|
||||
{
|
||||
FREE_OBJECT(object);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Add the name:value
|
||||
if (object.find(name) != object.end())
|
||||
delete object[name];
|
||||
object[name] = value;
|
||||
|
||||
// More whitespace?
|
||||
if (!JSON::SkipWhitespace(data))
|
||||
{
|
||||
FREE_OBJECT(object);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// End of object?
|
||||
if (**data == '}')
|
||||
{
|
||||
(*data)++;
|
||||
return new JSONValue(object);
|
||||
}
|
||||
|
||||
// Want a , now
|
||||
if (**data != ',')
|
||||
{
|
||||
FREE_OBJECT(object);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
(*data)++;
|
||||
}
|
||||
|
||||
// Only here if we ran out of data
|
||||
FREE_OBJECT(object);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// An array?
|
||||
else if (**data == '[')
|
||||
{
|
||||
JSONArray array;
|
||||
|
||||
(*data)++;
|
||||
|
||||
while (**data != 0)
|
||||
{
|
||||
// Whitespace at the start?
|
||||
if (!JSON::SkipWhitespace(data))
|
||||
{
|
||||
FREE_ARRAY(array);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Special case - empty array
|
||||
if (array.size() == 0 && **data == ']')
|
||||
{
|
||||
(*data)++;
|
||||
return new JSONValue(array);
|
||||
}
|
||||
|
||||
// Get the value
|
||||
JSONValue *value = Parse(data);
|
||||
if (value == NULL)
|
||||
{
|
||||
FREE_ARRAY(array);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Add the value
|
||||
array.push_back(value);
|
||||
|
||||
// More whitespace?
|
||||
if (!JSON::SkipWhitespace(data))
|
||||
{
|
||||
FREE_ARRAY(array);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// End of array?
|
||||
if (**data == ']')
|
||||
{
|
||||
(*data)++;
|
||||
return new JSONValue(array);
|
||||
}
|
||||
|
||||
// Want a , now
|
||||
if (**data != ',')
|
||||
{
|
||||
FREE_ARRAY(array);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
(*data)++;
|
||||
}
|
||||
|
||||
// Only here if we ran out of data
|
||||
FREE_ARRAY(array);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Ran out of possibilites, it's bad!
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic constructor for creating a JSON Value of type NULL
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
JSONValue::JSONValue(/*NULL*/)
|
||||
{
|
||||
type = JSONType_Null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic constructor for creating a JSON Value of type String
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param char* m_char_value The string to use as the value
|
||||
*/
|
||||
JSONValue::JSONValue(const char *m_char_value)
|
||||
{
|
||||
type = JSONType_String;
|
||||
string_value = new std::string(std::string(m_char_value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic constructor for creating a JSON Value of type String
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param std::string m_string_value The string to use as the value
|
||||
*/
|
||||
JSONValue::JSONValue(const std::string &m_string_value)
|
||||
{
|
||||
type = JSONType_String;
|
||||
string_value = new std::string(m_string_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic constructor for creating a JSON Value of type Bool
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param bool m_bool_value The bool to use as the value
|
||||
*/
|
||||
JSONValue::JSONValue(bool m_bool_value)
|
||||
{
|
||||
type = JSONType_Bool;
|
||||
bool_value = m_bool_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic constructor for creating a JSON Value of type Number
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param double m_number_value The number to use as the value
|
||||
*/
|
||||
JSONValue::JSONValue(double m_number_value)
|
||||
{
|
||||
type = JSONType_Number;
|
||||
number_value = m_number_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic constructor for creating a JSON Value of type Number
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param int m_integer_value The number to use as the value
|
||||
*/
|
||||
JSONValue::JSONValue(int m_integer_value)
|
||||
{
|
||||
type = JSONType_Number;
|
||||
number_value = (double) m_integer_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic constructor for creating a JSON Value of type Array
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param JSONArray m_array_value The JSONArray to use as the value
|
||||
*/
|
||||
JSONValue::JSONValue(const JSONArray &m_array_value)
|
||||
{
|
||||
type = JSONType_Array;
|
||||
array_value = new JSONArray(m_array_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic constructor for creating a JSON Value of type Object
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param JSONObject m_object_value The JSONObject to use as the value
|
||||
*/
|
||||
JSONValue::JSONValue(const JSONObject &m_object_value)
|
||||
{
|
||||
type = JSONType_Object;
|
||||
object_value = new JSONObject(m_object_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor to perform a deep copy of array / object values
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param JSONValue m_source The source JSONValue that is being copied
|
||||
*/
|
||||
JSONValue::JSONValue(const JSONValue &m_source)
|
||||
{
|
||||
type = m_source.type;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case JSONType_String:
|
||||
string_value = new std::string(*m_source.string_value);
|
||||
break;
|
||||
|
||||
case JSONType_Bool:
|
||||
bool_value = m_source.bool_value;
|
||||
break;
|
||||
|
||||
case JSONType_Number:
|
||||
number_value = m_source.number_value;
|
||||
break;
|
||||
|
||||
case JSONType_Array:
|
||||
{
|
||||
JSONArray source_array = *m_source.array_value;
|
||||
JSONArray::iterator iter;
|
||||
array_value = new JSONArray();
|
||||
for (iter = source_array.begin(); iter != source_array.end(); iter++)
|
||||
array_value->push_back(new JSONValue(**iter));
|
||||
break;
|
||||
}
|
||||
|
||||
case JSONType_Object:
|
||||
{
|
||||
JSONObject source_object = *m_source.object_value;
|
||||
object_value = new JSONObject();
|
||||
JSONObject::iterator iter;
|
||||
for (iter = source_object.begin(); iter != source_object.end(); iter++)
|
||||
{
|
||||
std::string name = (*iter).first;
|
||||
(*object_value)[name] = new JSONValue(*((*iter).second));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case JSONType_Null:
|
||||
// Nothing to do.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The destructor for the JSON Value object
|
||||
* Handles deleting the objects in the array or the object value
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
JSONValue::~JSONValue()
|
||||
{
|
||||
if (type == JSONType_Array)
|
||||
{
|
||||
JSONArray::iterator iter;
|
||||
for (iter = array_value->begin(); iter != array_value->end(); iter++)
|
||||
delete *iter;
|
||||
delete array_value;
|
||||
}
|
||||
else if (type == JSONType_Object)
|
||||
{
|
||||
JSONObject::iterator iter;
|
||||
for (iter = object_value->begin(); iter != object_value->end(); iter++)
|
||||
{
|
||||
delete (*iter).second;
|
||||
}
|
||||
delete object_value;
|
||||
}
|
||||
else if (type == JSONType_String)
|
||||
{
|
||||
delete string_value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value is a NULL
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return bool Returns true if it is a NULL value, false otherwise
|
||||
*/
|
||||
bool JSONValue::IsNull() const
|
||||
{
|
||||
return type == JSONType_Null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value is a String
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return bool Returns true if it is a String value, false otherwise
|
||||
*/
|
||||
bool JSONValue::IsString() const
|
||||
{
|
||||
return type == JSONType_String;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value is a Bool
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return bool Returns true if it is a Bool value, false otherwise
|
||||
*/
|
||||
bool JSONValue::IsBool() const
|
||||
{
|
||||
return type == JSONType_Bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value is a Number
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return bool Returns true if it is a Number value, false otherwise
|
||||
*/
|
||||
bool JSONValue::IsNumber() const
|
||||
{
|
||||
return type == JSONType_Number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value is an Array
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return bool Returns true if it is an Array value, false otherwise
|
||||
*/
|
||||
bool JSONValue::IsArray() const
|
||||
{
|
||||
return type == JSONType_Array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value is an Object
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return bool Returns true if it is an Object value, false otherwise
|
||||
*/
|
||||
bool JSONValue::IsObject() const
|
||||
{
|
||||
return type == JSONType_Object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the String value of this JSONValue
|
||||
* Use IsString() before using this method.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return std::string Returns the string value
|
||||
*/
|
||||
const std::string &JSONValue::AsString() const
|
||||
{
|
||||
return (*string_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Bool value of this JSONValue
|
||||
* Use IsBool() before using this method.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return bool Returns the bool value
|
||||
*/
|
||||
bool JSONValue::AsBool() const
|
||||
{
|
||||
return bool_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Number value of this JSONValue
|
||||
* Use IsNumber() before using this method.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return double Returns the number value
|
||||
*/
|
||||
double JSONValue::AsNumber() const
|
||||
{
|
||||
return number_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Array value of this JSONValue
|
||||
* Use IsArray() before using this method.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return JSONArray Returns the array value
|
||||
*/
|
||||
const JSONArray &JSONValue::AsArray() const
|
||||
{
|
||||
return (*array_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Object value of this JSONValue
|
||||
* Use IsObject() before using this method.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return JSONObject Returns the object value
|
||||
*/
|
||||
const JSONObject &JSONValue::AsObject() const
|
||||
{
|
||||
return (*object_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the number of children of this JSONValue.
|
||||
* This number will be 0 or the actual number of children
|
||||
* if IsArray() or IsObject().
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return The number of children.
|
||||
*/
|
||||
std::size_t JSONValue::CountChildren() const
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case JSONType_Array:
|
||||
return array_value->size();
|
||||
case JSONType_Object:
|
||||
return object_value->size();
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this JSONValue has a child at the given index.
|
||||
* Use IsArray() before using this method.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return bool Returns true if the array has a value at the given index.
|
||||
*/
|
||||
bool JSONValue::HasChild(std::size_t index) const
|
||||
{
|
||||
if (type == JSONType_Array)
|
||||
{
|
||||
return index < array_value->size();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the child of this JSONValue at the given index.
|
||||
* Use IsArray() before using this method.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return JSONValue* Returns JSONValue at the given index or NULL
|
||||
* if it doesn't exist.
|
||||
*/
|
||||
JSONValue *JSONValue::Child(std::size_t index)
|
||||
{
|
||||
if (index < array_value->size())
|
||||
{
|
||||
return (*array_value)[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this JSONValue has a child at the given key.
|
||||
* Use IsObject() before using this method.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return bool Returns true if the object has a value at the given key.
|
||||
*/
|
||||
bool JSONValue::HasChild(const char* name) const
|
||||
{
|
||||
if (type == JSONType_Object)
|
||||
{
|
||||
return object_value->find(name) != object_value->end();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the child of this JSONValue at the given key.
|
||||
* Use IsObject() before using this method.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return JSONValue* Returns JSONValue for the given key in the object
|
||||
* or NULL if it doesn't exist.
|
||||
*/
|
||||
JSONValue* JSONValue::Child(const char* name)
|
||||
{
|
||||
JSONObject::const_iterator it = object_value->find(name);
|
||||
if (it != object_value->end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the keys of the JSON Object or an empty vector
|
||||
* if this value is not an object.
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @return std::vector<std::string> A vector containing the keys.
|
||||
*/
|
||||
std::vector<std::string> JSONValue::ObjectKeys() const
|
||||
{
|
||||
std::vector<std::string> keys;
|
||||
|
||||
if (type == JSONType_Object)
|
||||
{
|
||||
JSONObject::const_iterator iter = object_value->begin();
|
||||
while (iter != object_value->end())
|
||||
{
|
||||
keys.push_back(iter->first);
|
||||
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON encoded string for the value with all necessary characters escaped
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param bool prettyprint Enable prettyprint
|
||||
*
|
||||
* @return std::string Returns the JSON string
|
||||
*/
|
||||
std::string JSONValue::Stringify(bool const prettyprint) const
|
||||
{
|
||||
size_t const indentDepth = prettyprint ? 1 : 0;
|
||||
return StringifyImpl(indentDepth);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a JSON encoded string for the value with all necessary characters escaped
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param size_t indentDepth The prettyprint indentation depth (0 : no prettyprint)
|
||||
*
|
||||
* @return std::string Returns the JSON string
|
||||
*/
|
||||
std::string JSONValue::StringifyImpl(size_t const indentDepth) const
|
||||
{
|
||||
std::string ret_string;
|
||||
size_t const indentDepth1 = indentDepth ? indentDepth + 1 : 0;
|
||||
std::string const indentStr = Indent(indentDepth);
|
||||
std::string const indentStr1 = Indent(indentDepth1);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case JSONType_Null:
|
||||
ret_string = "null";
|
||||
break;
|
||||
|
||||
case JSONType_String:
|
||||
ret_string = StringifyString(*string_value);
|
||||
break;
|
||||
|
||||
case JSONType_Bool:
|
||||
ret_string = bool_value ? "true" : "false";
|
||||
break;
|
||||
|
||||
case JSONType_Number:
|
||||
{
|
||||
if (isinf(number_value) || isnan(number_value))
|
||||
ret_string = "null";
|
||||
else
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss.precision(15);
|
||||
ss << number_value;
|
||||
ret_string = ss.str();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case JSONType_Array:
|
||||
{
|
||||
ret_string = indentDepth ? "[\n" + indentStr1 : "[";
|
||||
JSONArray::const_iterator iter = array_value->begin();
|
||||
while (iter != array_value->end())
|
||||
{
|
||||
ret_string += (*iter)->StringifyImpl(indentDepth1);
|
||||
|
||||
// Not at the end - add a separator
|
||||
if (++iter != array_value->end())
|
||||
ret_string += ",";
|
||||
}
|
||||
ret_string += indentDepth ? "\n" + indentStr + "]" : "]";
|
||||
break;
|
||||
}
|
||||
|
||||
case JSONType_Object:
|
||||
{
|
||||
ret_string = indentDepth ? "{\n" + indentStr1 : "{";
|
||||
JSONObject::const_iterator iter = object_value->begin();
|
||||
while (iter != object_value->end())
|
||||
{
|
||||
ret_string += StringifyString((*iter).first);
|
||||
ret_string += ":";
|
||||
ret_string += (*iter).second->StringifyImpl(indentDepth1);
|
||||
|
||||
// Not at the end - add a separator
|
||||
if (++iter != object_value->end())
|
||||
ret_string += ",";
|
||||
}
|
||||
ret_string += indentDepth ? "\n" + indentStr + "}" : "}";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON encoded string with all required fields escaped
|
||||
* Works from http://www.ecma-internationl.org/publications/files/ECMA-ST/ECMA-262.pdf
|
||||
* Section 15.12.3.
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param std::string str The string that needs to have the characters escaped
|
||||
*
|
||||
* @return std::string Returns the JSON string
|
||||
*/
|
||||
std::string JSONValue::StringifyString(const std::string &str)
|
||||
{
|
||||
std::string str_out = "\"";
|
||||
|
||||
std::string::const_iterator iter = str.begin();
|
||||
while (iter != str.end())
|
||||
{
|
||||
char chr = *iter;
|
||||
|
||||
if (chr == '"' || chr == '\\' || chr == '/')
|
||||
{
|
||||
str_out += '\\';
|
||||
str_out += chr;
|
||||
}
|
||||
else if (chr == '\b')
|
||||
{
|
||||
str_out += "\\b";
|
||||
}
|
||||
else if (chr == '\f')
|
||||
{
|
||||
str_out += "\\f";
|
||||
}
|
||||
else if (chr == '\n')
|
||||
{
|
||||
str_out += "\\n";
|
||||
}
|
||||
else if (chr == '\r')
|
||||
{
|
||||
str_out += "\\r";
|
||||
}
|
||||
else if (chr == '\t')
|
||||
{
|
||||
str_out += "\\t";
|
||||
}
|
||||
else if (chr < ' ' || chr > 126)
|
||||
{
|
||||
str_out += "\\u";
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
int value = (chr >> 12) & 0xf;
|
||||
if (value >= 0 && value <= 9)
|
||||
str_out += (char)('0' + value);
|
||||
else if (value >= 10 && value <= 15)
|
||||
str_out += (char)('A' + (value - 10));
|
||||
chr <<= 4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
str_out += chr;
|
||||
}
|
||||
|
||||
iter++;
|
||||
}
|
||||
|
||||
str_out += "\"";
|
||||
return str_out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the indentation string for the depth given
|
||||
*
|
||||
* @access private
|
||||
*
|
||||
* @param size_t indent The prettyprint indentation depth (0 : no indentation)
|
||||
*
|
||||
* @return std::string Returns the string
|
||||
*/
|
||||
std::string JSONValue::Indent(size_t depth)
|
||||
{
|
||||
const size_t indent_step = 2;
|
||||
depth ? --depth : 0;
|
||||
std::string indentStr(depth * indent_step, ' ');
|
||||
return indentStr;
|
||||
}
|
||||
95
src/mqtt/JSONValue.h
Normal file
95
src/mqtt/JSONValue.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* File JSONValue.h part of the SimpleJSON Library - http://mjpa.in/json
|
||||
*
|
||||
* Copyright (C) 2010 Mike Anchor
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef _JSONVALUE_H_
|
||||
#define _JSONVALUE_H_
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "JSON.h"
|
||||
|
||||
class JSON;
|
||||
|
||||
enum JSONType { JSONType_Null, JSONType_String, JSONType_Bool, JSONType_Number, JSONType_Array, JSONType_Object };
|
||||
|
||||
class JSONValue
|
||||
{
|
||||
friend class JSON;
|
||||
|
||||
public:
|
||||
JSONValue(/*NULL*/);
|
||||
JSONValue(const char *m_char_value);
|
||||
JSONValue(const std::string &m_string_value);
|
||||
JSONValue(bool m_bool_value);
|
||||
JSONValue(double m_number_value);
|
||||
JSONValue(int m_integer_value);
|
||||
JSONValue(const JSONArray &m_array_value);
|
||||
JSONValue(const JSONObject &m_object_value);
|
||||
JSONValue(const JSONValue &m_source);
|
||||
~JSONValue();
|
||||
|
||||
bool IsNull() const;
|
||||
bool IsString() const;
|
||||
bool IsBool() const;
|
||||
bool IsNumber() const;
|
||||
bool IsArray() const;
|
||||
bool IsObject() const;
|
||||
|
||||
const std::string &AsString() const;
|
||||
bool AsBool() const;
|
||||
double AsNumber() const;
|
||||
const JSONArray &AsArray() const;
|
||||
const JSONObject &AsObject() const;
|
||||
|
||||
std::size_t CountChildren() const;
|
||||
bool HasChild(std::size_t index) const;
|
||||
JSONValue *Child(std::size_t index);
|
||||
bool HasChild(const char* name) const;
|
||||
JSONValue *Child(const char* name);
|
||||
std::vector<std::string> ObjectKeys() const;
|
||||
|
||||
std::string Stringify(bool const prettyprint = false) const;
|
||||
protected:
|
||||
static JSONValue *Parse(const char **data);
|
||||
|
||||
private:
|
||||
static std::string StringifyString(const std::string &str);
|
||||
std::string StringifyImpl(size_t const indentDepth) const;
|
||||
static std::string Indent(size_t depth);
|
||||
|
||||
JSONType type;
|
||||
|
||||
union
|
||||
{
|
||||
bool bool_value;
|
||||
double number_value;
|
||||
std::string *string_value;
|
||||
JSONArray *array_value;
|
||||
JSONObject *object_value;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user