diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml
index 2b6ffce0a..b027a36cc 100644
--- a/.github/ISSUE_TEMPLATE/feature.yml
+++ b/.github/ISSUE_TEMPLATE/feature.yml
@@ -16,6 +16,9 @@ body:
options:
- NRF52
- ESP32
+ - RP2040
+ - Linux Native
+ - other
validations:
required: true
- type: textarea
diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml
index 7b97e1753..b5b4cb6f3 100644
--- a/.github/actions/setup-base/action.yml
+++ b/.github/actions/setup-base/action.yml
@@ -5,37 +5,25 @@ runs:
using: "composite"
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
submodules: "recursive"
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- - name: Install cppcheck
+ - name: Install dependencies
shell: bash
run: |
- sudo apt-get install -y cppcheck
-
- - name: Install libbluetooth
- shell: bash
- run: |
- sudo apt-get install -y libbluetooth-dev
- - name: Install libgpiod
- shell: bash
- run: |
- sudo apt-get install -y libgpiod-dev
- - name: Install libyaml-cpp
- shell: bash
- run: |
- sudo apt-get install -y libyaml-cpp-dev
+ sudo apt-get -y update
+ sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev
- name: Setup Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Cache python libs
- uses: actions/cache@v3
+ uses: actions/cache@v4
id: cache-pip # needed in if test
with:
path: ~/.cache/pip
diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml
index 31f0dd5a0..4cbb4c7a4 100644
--- a/.github/workflows/build_esp32.yml
+++ b/.github/workflows/build_esp32.yml
@@ -11,13 +11,13 @@ jobs:
build-esp32:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Build base
id: base
uses: ./.github/actions/setup-base
- name: Pull web ui
- uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
+ uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/web
file: build.tar
@@ -41,7 +41,7 @@ jobs:
run: bin/build-esp32.sh ${{ inputs.board }}
- name: Pull OTA Firmware
- uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
+ uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/firmware-ota
file: firmware.bin
@@ -54,9 +54,10 @@ jobs:
id: version
- name: Store binaries as an artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
+ overwrite: true
path: |
release/*.bin
release/*.elf
diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml
index a30cf33f1..07727d711 100644
--- a/.github/workflows/build_esp32_c3.yml
+++ b/.github/workflows/build_esp32_c3.yml
@@ -13,13 +13,13 @@ jobs:
build-esp32-c3:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Build base
id: base
uses: ./.github/actions/setup-base
- name: Pull web ui
- uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
+ uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/web
file: build.tar
@@ -41,7 +41,7 @@ jobs:
run: bin/build-esp32.sh ${{ inputs.board }}
- name: Pull OTA Firmware
- uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
+ uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/firmware-ota
file: firmware-c3.bin
@@ -54,9 +54,10 @@ jobs:
id: version
- name: Store binaries as an artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
+ overwrite: true
path: |
release/*.bin
release/*.elf
diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml
index f603a6a31..10773833e 100644
--- a/.github/workflows/build_esp32_s3.yml
+++ b/.github/workflows/build_esp32_s3.yml
@@ -11,13 +11,13 @@ jobs:
build-esp32-s3:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Build base
id: base
uses: ./.github/actions/setup-base
- name: Pull web ui
- uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
+ uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/web
file: build.tar
@@ -39,7 +39,7 @@ jobs:
run: bin/build-esp32.sh ${{ inputs.board }}
- name: Pull OTA Firmware
- uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
+ uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/firmware-ota
file: firmware-s3.bin
@@ -52,9 +52,10 @@ jobs:
id: version
- name: Store binaries as an artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
+ overwrite: true
path: |
release/*.bin
release/*.elf
diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml
new file mode 100644
index 000000000..8fe8e6c31
--- /dev/null
+++ b/.github/workflows/build_native.yml
@@ -0,0 +1,85 @@
+name: Build Native
+
+on: workflow_call
+
+permissions:
+ contents: write
+ packages: write
+
+jobs:
+ build-native:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Install libbluetooth
+ shell: bash
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev
+
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ ref: ${{github.event.pull_request.head.ref}}
+ repository: ${{github.event.pull_request.head.repo.full_name}}
+
+ - 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
+
+ - name: Build Native
+ run: bin/build-native.sh
+
+ - 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@v4
+ with:
+ name: firmware-native-${{ steps.version.outputs.version }}.zip
+ overwrite: true
+ path: |
+ release/meshtasticd_linux_x86_64
+ bin/config-dist.yaml
+
+ - name: Docker login
+ if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
+ uses: docker/login-action@v3
+ continue-on-error: true # FIXME: Failing docker login auth
+ with:
+ username: meshtastic
+ password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
+
+ - name: Docker setup
+ if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
+ continue-on-error: true # FIXME: Failing docker login auth
+ uses: docker/setup-buildx-action@v3
+
+ - name: Docker build and push tagged versions
+ if: ${{ github.event_name == 'workflow_dispatch' }}
+ continue-on-error: true # FIXME: Failing docker login auth
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./Dockerfile
+ push: true
+ tags: meshtastic/device-simulator:${{ steps.version.outputs.version }}
+
+ - name: Docker build and push
+ if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
+ continue-on-error: true # FIXME: Failing docker login auth
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./Dockerfile
+ push: true
+ tags: meshtastic/device-simulator:latest
diff --git a/.github/workflows/build_nrf52.yml b/.github/workflows/build_nrf52.yml
index 33ee4d00c..eb1779963 100644
--- a/.github/workflows/build_nrf52.yml
+++ b/.github/workflows/build_nrf52.yml
@@ -11,7 +11,7 @@ jobs:
build-nrf52:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Build base
id: base
uses: ./.github/actions/setup-base
@@ -24,9 +24,10 @@ jobs:
id: version
- name: Store binaries as an artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
+ overwrite: true
path: |
release/*.uf2
release/*.elf
diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml
index 7a25892bc..697d08727 100644
--- a/.github/workflows/build_raspbian.yml
+++ b/.github/workflows/build_raspbian.yml
@@ -10,8 +10,13 @@ jobs:
build-raspbian:
runs-on: [self-hosted, linux, ARM64]
steps:
+ - name: Install libbluetooth
+ shell: bash
+ run: |
+ apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev
+
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
@@ -37,9 +42,10 @@ jobs:
id: version
- name: Store binaries as an artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: firmware-raspbian-${{ steps.version.outputs.version }}.zip
+ overwrite: true
path: |
release/meshtasticd_linux_aarch64
bin/config-dist.yaml
diff --git a/.github/workflows/build_raspbian_armv7l.yml b/.github/workflows/build_raspbian_armv7l.yml
new file mode 100644
index 000000000..ee5eb66eb
--- /dev/null
+++ b/.github/workflows/build_raspbian_armv7l.yml
@@ -0,0 +1,51 @@
+name: Build Raspbian Arm
+
+on: workflow_call
+
+permissions:
+ contents: write
+ packages: write
+
+jobs:
+ build-raspbian-armv7l:
+ runs-on: [self-hosted, linux, ARM]
+ steps:
+ - name: Install libbluetooth
+ shell: bash
+ run: |
+ apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev
+
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ ref: ${{github.event.pull_request.head.ref}}
+ repository: ${{github.event.pull_request.head.repo.full_name}}
+
+ - 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
+
+ - name: Build Raspbian
+ run: bin/build-native.sh
+
+ - 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@v4
+ with:
+ name: firmware-raspbian-armv7l-${{ steps.version.outputs.version }}.zip
+ overwrite: true
+ path: |
+ release/meshtasticd_linux_armv7l
+ bin/config-dist.yaml
diff --git a/.github/workflows/build_rpi2040.yml b/.github/workflows/build_rpi2040.yml
index 76ca2c20e..6e258fe2a 100644
--- a/.github/workflows/build_rpi2040.yml
+++ b/.github/workflows/build_rpi2040.yml
@@ -11,7 +11,7 @@ jobs:
build-rpi2040:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Build base
id: base
uses: ./.github/actions/setup-base
@@ -24,9 +24,10 @@ jobs:
id: version
- name: Store binaries as an artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
+ overwrite: true
path: |
release/*.uf2
release/*.elf
diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index 9ca0764b5..25a0fbad2 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -8,7 +8,7 @@ on:
branches: [master, develop]
paths-ignore:
- "**.md"
- - "version.properties"
+ - version.properties
# Note: This is different from "pull_request". Need to specify ref when doing checkouts.
pull_request_target:
@@ -20,209 +20,104 @@ on:
workflow_dispatch:
jobs:
- check:
+ setup:
strategy:
fail-fast: false
matrix:
- include:
- - board: rak11200
- - board: tlora-v2-1-1_6
- - board: tbeam
- - board: heltec-v2_1
- - board: meshtastic-diy-v1
- - board: rak4631
- - board: t-echo
- - board: station-g2
- - board: m5stack-coreink
- - board: tbeam-s3-core
- - board: tlora-t3s3-v1
- - board: t-watch-s3
- - board: t-deck
- #- board: rak11310
+ arch: [esp32, esp32s3, esp32c3, nrf52840, rp2040, check]
+ runs-on: ubuntu-latest
+ steps:
+ - id: checkout
+ uses: actions/checkout@v4
+ name: Checkout base
+ - id: jsonStep
+ run: |
+ TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
+ echo "$TARGETS"
+ echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
+ outputs:
+ esp32: ${{ steps.jsonStep.outputs.esp32 }}
+ esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
+ esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
+ nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
+ rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
+ check: ${{ steps.jsonStep.outputs.check }}
+
+ check:
+ needs: setup
+ strategy:
+ fail-fast: false
+ matrix: ${{ fromJson(needs.setup.outputs.check) }}
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
- name: Build base
id: base
uses: ./.github/actions/setup-base
-
- - name: Trunk Check
- if: ${{ github.event_name != 'workflow_dispatch' }}
- uses: trunk-io/trunk-action@782e83f803ca6e369f035d64c6ba2768174ba61b
-
- name: Check ${{ matrix.board }}
run: bin/check-all.sh ${{ matrix.board }}
build-esp32:
+ needs: setup
strategy:
fail-fast: false
- matrix:
- include:
- - board: rak11200
- - board: tlora-v2
- - board: tlora-v1
- - board: tlora_v1_3
- - board: tlora-v2-1-1_6
- - board: tlora-v2-1-1_6-tcxo
- - board: tlora-v2-1-1_8
- - board: tbeam
- - board: heltec-v2_0
- - board: heltec-v2_1
- - board: tbeam0_7
- - board: meshtastic-diy-v1
- - board: hydra
- - board: meshtastic-dr-dev
- - board: nano-g1
- - board: station-g1
- - board: m5stack-core
- - board: m5stack-coreink
- - board: nano-g1-explorer
- - board: chatter2
+ matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
uses: ./.github/workflows/build_esp32.yml
with:
board: ${{ matrix.board }}
build-esp32-s3:
+ needs: setup
strategy:
fail-fast: false
- matrix:
- include:
- - board: heltec-v3
- - board: heltec-wsl-v3
- - board: heltec-wireless-tracker
- - board: heltec-wireless-tracker-V1-0
- - board: heltec-wireless-paper-v1_0
- - board: heltec-wireless-paper #v1.1
- - board: tbeam-s3-core
- - board: tlora-t3s3-v1
- - board: t-watch-s3
- - board: t-deck
- - board: picomputer-s3
- - board: station-g2
- - board: unphone
+ matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
uses: ./.github/workflows/build_esp32_s3.yml
with:
board: ${{ matrix.board }}
build-esp32-c3:
+ needs: setup
strategy:
fail-fast: false
- matrix:
- include:
- - board: heltec-ht62-esp32c3-sx1262
+ matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
uses: ./.github/workflows/build_esp32_c3.yml
with:
board: ${{ matrix.board }}
build-nrf52:
+ needs: setup
strategy:
fail-fast: false
- matrix:
- include:
- - board: rak4631
- - board: rak4631_eink
- - board: monteops_hw1
- - board: t-echo
- - board: canaryone
- - board: pca10059_diy_eink
- - board: feather_diy
- - board: nano-g2-ultra
+ matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
uses: ./.github/workflows/build_nrf52.yml
with:
board: ${{ matrix.board }}
build-rpi2040:
+ needs: setup
strategy:
fail-fast: false
- matrix:
- include:
- - board: pico
- - board: picow
- - board: rak11310
- - board: senselora_rp2040
- - board: rp2040-lora
+ matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
uses: ./.github/workflows/build_rpi2040.yml
with:
board: ${{ matrix.board }}
- build-raspbian:
- strategy:
- fail-fast: false
- max-parallel: 1
- uses: ./.github/workflows/build_raspbian.yml
-
package-raspbian:
uses: ./.github/workflows/package_raspbian.yml
- build-native:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - name: Build base
- id: base
- uses: ./.github/actions/setup-base
+ package-raspbian-armv7l:
+ uses: ./.github/workflows/package_raspbian_armv7l.yml
- # We now run integration test before other build steps (to quickly see runtime failures)
- #- name: Build for native
- # run: platformio run -e native
- #- name: Integration test
- # run: |
- #.pio/build/native/program
- #& sleep 20 # 5 seconds was not enough
- #echo "Simulator started, launching python test..."
- #python3 -c 'from meshtastic.test import testSimulator; testSimulator()'
-
- - name: Build Native
- run: bin/build-native.sh
-
- - 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-native-${{ steps.version.outputs.version }}.zip
- path: |
- release/device-*.sh
- release/device-*.bat
-
- - name: Docker login
- if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
- uses: docker/login-action@v2
- with:
- username: meshtastic
- password: ${{ secrets.DOCKER_TOKEN }}
-
- - name: Docker setup
- if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
- uses: docker/setup-buildx-action@v2
-
- - name: Docker build and push tagged versions
- if: ${{ github.event_name == 'workflow_dispatch' }}
- uses: docker/build-push-action@v3
- with:
- context: .
- file: ./Dockerfile
- push: true
- tags: meshtastic/device-simulator:${{ steps.version.outputs.version }}
-
- - name: Docker build and push
- if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
- uses: docker/build-push-action@v3
- with:
- context: .
- file: ./Dockerfile
- push: true
- tags: meshtastic/device-simulator:latest
+ package-native:
+ uses: ./.github/workflows/package_amd64.yml
after-checks:
runs-on: ubuntu-latest
needs: [check]
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
@@ -238,21 +133,22 @@ jobs:
build-esp32-s3,
build-esp32-c3,
build-nrf52,
- build-raspbian,
- build-native,
build-rpi2040,
package-raspbian,
+ package-raspbian-armv7l,
+ package-native
]
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
with:
path: ./
+ merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
@@ -262,25 +158,30 @@ jobs:
id: version
- name: Move files up
- run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./*esp32c3*/bleota-c3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase_v2.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat ./firmware-raspbian-*/release/meshtasticd_linux_aarch64 ./firmware-raspbian-*/bin/config-dist.yaml
+ run: mv -b -t ./ ./release/meshtasticd_linux_* ./bin/config-dist.yaml ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: firmware-${{ steps.version.outputs.version }}
+ overwrite: true
path: |
- ./*.bin
- ./*.uf2
+ ./firmware-*.bin
+ ./firmware-*.uf2
./firmware-*-ota.zip
./device-*.sh
./device-*.bat
- ./meshtasticd_linux_arm64
+ ./meshtasticd_linux_*
./config-dist.yaml
+ ./littlefs-*.bin
+ ./bleota*bin
+ ./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 90
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
with:
name: firmware-${{ steps.version.outputs.version }}
+ merge-multiple: true
path: ./output
# For diagnostics
@@ -296,9 +197,10 @@ jobs:
run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output
- name: Repackage in single elfs zip
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: debug-elfs-${{ steps.version.outputs.version }}.zip
+ overwrite: true
path: ./*.elf
retention-days: 30
@@ -320,10 +222,10 @@ jobs:
needs: [gather-artifacts, after-checks]
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v5
with:
python-version: 3.x
@@ -331,14 +233,17 @@ jobs:
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
with:
name: firmware-${{ steps.version.outputs.version }}
+ merge-multiple: true
path: ./output
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
with:
- name: artifact-deb
+ pattern: meshtasticd_${{ steps.version.outputs.version }}_*.deb
+ merge-multiple: true
+ path: ./output
- name: Display structure of downloaded files
run: ls -R
@@ -349,11 +254,12 @@ jobs:
chmod +x ./output/device-update.sh
- name: Zip firmware
- run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output
+ run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output -x *.deb
- - uses: actions/download-artifact@v3
+ - uses: actions/download-artifact@v4
with:
name: debug-elfs-${{ steps.version.outputs.version }}.zip
+ merge-multiple: true
path: ./elfs
- name: Zip Elfs
@@ -396,22 +302,42 @@ jobs:
asset_name: debug-elfs-${{ steps.version.outputs.version }}.zip
asset_content_type: application/zip
- - name: Add raspbian .deb
+ - name: Add raspbian aarch64 .deb
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ github.token }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./meshtasticd_${{ steps.version.outputs.version }}_arm64.deb
+ asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_arm64.deb
asset_name: meshtasticd_${{ steps.version.outputs.version }}_arm64.deb
asset_content_type: application/vnd.debian.binary-package
+ - name: Add raspbian armv7l .deb
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_armhf.deb
+ asset_name: meshtasticd_${{ steps.version.outputs.version }}_armhf.deb
+ asset_content_type: application/vnd.debian.binary-package
+
+ - name: Add raspbian amd64 .deb
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ with:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_amd64.deb
+ asset_name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb
+ asset_content_type: application/vnd.debian.binary-package
+
- name: Bump version.properties
run: >-
bin/bump_version.py
- name: Create version.properties pull request
- uses: peter-evans/create-pull-request@v3
+ uses: peter-evans/create-pull-request@v6
with:
add-paths: |
version.properties
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index da59bc0fd..e249823a7 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Trunk Check
uses: trunk-io/trunk-action@782e83f803ca6e369f035d64c6ba2768174ba61b
diff --git a/.github/workflows/package_amd64.yml b/.github/workflows/package_amd64.yml
new file mode 100644
index 000000000..ae7bf3242
--- /dev/null
+++ b/.github/workflows/package_amd64.yml
@@ -0,0 +1,78 @@
+name: Package Native
+
+on:
+ workflow_call:
+ workflow_dispatch:
+
+permissions:
+ contents: write
+ packages: write
+
+jobs:
+ build-native:
+ uses: ./.github/workflows/build_native.yml
+
+ package-native:
+ runs-on: ubuntu-latest
+ needs: build-native
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ ref: ${{github.event.pull_request.head.ref}}
+ repository: ${{github.event.pull_request.head.repo.full_name}}
+
+ - 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: Get release version string
+ run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+ id: version
+
+ - name: Download artifacts
+ uses: actions/download-artifact@v4
+ with:
+ name: firmware-native-${{ steps.version.outputs.version }}.zip
+ merge-multiple: true
+
+ - name: Display structure of downloaded files
+ run: ls -R
+
+ - name: build .debpkg
+ run: |
+ mkdir -p .debpkg/DEBIAN
+ mkdir -p .debpkg/usr/share/doc/meshtasticd/web
+ mkdir -p .debpkg/usr/sbin
+ mkdir -p .debpkg/etc/meshtasticd
+ mkdir -p .debpkg/usr/lib/systemd/system/
+ tar -xf build.tar -C .debpkg/usr/share/doc/meshtasticd/web
+ gunzip .debpkg/usr/share/doc/meshtasticd/web/*.gz
+ cp release/meshtasticd_linux_x86_64 .debpkg/usr/sbin/meshtasticd
+ cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
+ chmod +x .debpkg/usr/sbin/meshtasticd
+ cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
+ echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
+ chmod +x .debpkg/DEBIAN/conffiles
+
+ - uses: jiro4989/build-deb-action@v3
+ with:
+ package: meshtasticd
+ package_root: .debpkg
+ maintainer: Jonathan Bennett
+ version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.*
+ arch: amd64
+ depends: libyaml-cpp0.7, openssl, libulfius2.7
+ desc: Native Linux Meshtastic binary.
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb
+ overwrite: true
+ path: |
+ ./*.deb
diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml
index dd4133dab..5471332c5 100644
--- a/.github/workflows/package_raspbian.yml
+++ b/.github/workflows/package_raspbian.yml
@@ -17,14 +17,14 @@ jobs:
needs: build-raspbian
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Pull web ui
- uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
+ uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/web
file: build.tar
@@ -36,15 +36,17 @@ jobs:
id: version
- name: Download artifacts
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
name: firmware-raspbian-${{ steps.version.outputs.version }}.zip
+ merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
- name: build .debpkg
run: |
+ mkdir -p .debpkg/DEBIAN
mkdir -p .debpkg/usr/share/doc/meshtasticd/web
mkdir -p .debpkg/usr/sbin
mkdir -p .debpkg/etc/meshtasticd
@@ -55,6 +57,8 @@ jobs:
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
chmod +x .debpkg/usr/sbin/meshtasticd
cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
+ echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
+ chmod +x .debpkg/DEBIAN/conffiles
- uses: jiro4989/build-deb-action@v3
with:
@@ -66,8 +70,9 @@ jobs:
depends: libyaml-cpp0.7, openssl, libulfius2.7
desc: Native Linux Meshtastic binary.
- - uses: actions/upload-artifact@v3
+ - uses: actions/upload-artifact@v4
with:
- name: artifact-deb
+ name: meshtasticd_${{ steps.version.outputs.version }}_arm64.deb
+ overwrite: true
path: |
./*.deb
diff --git a/.github/workflows/package_raspbian_armv7l.yml b/.github/workflows/package_raspbian_armv7l.yml
new file mode 100644
index 000000000..5b9c9aa71
--- /dev/null
+++ b/.github/workflows/package_raspbian_armv7l.yml
@@ -0,0 +1,78 @@
+name: Package Raspbian
+
+on:
+ workflow_call:
+ workflow_dispatch:
+
+permissions:
+ contents: write
+ packages: write
+
+jobs:
+ build-raspbian_armv7l:
+ uses: ./.github/workflows/build_raspbian_armv7l.yml
+
+ package-raspbian_armv7l:
+ runs-on: ubuntu-latest
+ needs: build-raspbian_armv7l
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ ref: ${{github.event.pull_request.head.ref}}
+ repository: ${{github.event.pull_request.head.repo.full_name}}
+
+ - 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: Get release version string
+ run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+ id: version
+
+ - name: Download artifacts
+ uses: actions/download-artifact@v4
+ with:
+ name: firmware-raspbian-armv7l-${{ steps.version.outputs.version }}.zip
+ merge-multiple: true
+
+ - name: Display structure of downloaded files
+ run: ls -R
+
+ - name: build .debpkg
+ run: |
+ mkdir -p .debpkg/DEBIAN
+ mkdir -p .debpkg/usr/share/doc/meshtasticd/web
+ mkdir -p .debpkg/usr/sbin
+ mkdir -p .debpkg/etc/meshtasticd
+ mkdir -p .debpkg/usr/lib/systemd/system/
+ tar -xf build.tar -C .debpkg/usr/share/doc/meshtasticd/web
+ gunzip .debpkg/usr/share/doc/meshtasticd/web/*.gz
+ cp release/meshtasticd_linux_armv7l .debpkg/usr/sbin/meshtasticd
+ cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
+ chmod +x .debpkg/usr/sbin/meshtasticd
+ cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
+ echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
+ chmod +x .debpkg/DEBIAN/conffiles
+
+ - uses: jiro4989/build-deb-action@v3
+ with:
+ package: meshtasticd
+ package_root: .debpkg
+ maintainer: Jonathan Bennett
+ version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.*
+ arch: armhf
+ depends: libyaml-cpp0.7, openssl, libulfius2.7
+ desc: Native Linux Meshtastic binary.
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: meshtasticd_${{ steps.version.outputs.version }}_armhf.deb
+ overwrite: true
+ path: |
+ ./*.deb
diff --git a/.github/workflows/sec_sast_flawfinder.yml b/.github/workflows/sec_sast_flawfinder.yml
index 2c7e751af..99cc72190 100644
--- a/.github/workflows/sec_sast_flawfinder.yml
+++ b/.github/workflows/sec_sast_flawfinder.yml
@@ -16,7 +16,7 @@ jobs:
steps:
# step 1
- name: clone application source code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
# step 2
- name: flawfinder_scan
@@ -27,14 +27,15 @@ jobs:
# step 3
- name: save report as pipeline artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: flawfinder_report.sarif
+ overwrite: true
path: flawfinder_report.sarif
# step 4
- name: publish code scanning alerts
- uses: github/codeql-action/upload-sarif@v2
+ uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: flawfinder_report.sarif
category: flawfinder
diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml
index cdd2c3c37..2a0361f5e 100644
--- a/.github/workflows/sec_sast_semgrep_cron.yml
+++ b/.github/workflows/sec_sast_semgrep_cron.yml
@@ -17,7 +17,7 @@ jobs:
steps:
# step 1
- name: clone application source code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
# step 2
- name: full scan
@@ -29,14 +29,15 @@ jobs:
# step 3
- name: save report as pipeline artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: report.sarif
+ overwrite: true
path: report.sarif
# step 4
- name: publish code scanning alerts
- uses: github/codeql-action/upload-sarif@v2
+ uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: report.sarif
category: semgrep
diff --git a/.github/workflows/sec_sast_semgrep_pull.yml b/.github/workflows/sec_sast_semgrep_pull.yml
index 1697ffb1b..b6c288494 100644
--- a/.github/workflows/sec_sast_semgrep_pull.yml
+++ b/.github/workflows/sec_sast_semgrep_pull.yml
@@ -11,7 +11,7 @@ jobs:
steps:
# step 1
- name: clone application source code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
fetch-depth: 0
diff --git a/.github/workflows/trunk-check.yml b/.github/workflows/trunk-check.yml
index e35b91cb9..6ed905bc8 100644
--- a/.github/workflows/trunk-check.yml
+++ b/.github/workflows/trunk-check.yml
@@ -16,7 +16,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Trunk Check
uses: trunk-io/trunk-action@v1
diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml
index 30f9b3578..4402a280e 100644
--- a/.github/workflows/update_protobufs.yml
+++ b/.github/workflows/update_protobufs.yml
@@ -7,7 +7,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
submodules: true
@@ -26,7 +26,7 @@ jobs:
./bin/regen-protos.sh
- name: Create pull request
- uses: peter-evans/create-pull-request@v3
+ uses: peter-evans/create-pull-request@v6
with:
add-paths: |
protobufs
diff --git a/.trunk/configs/.bandit b/.trunk/configs/.bandit
new file mode 100644
index 000000000..d286ded89
--- /dev/null
+++ b/.trunk/configs/.bandit
@@ -0,0 +1,2 @@
+[bandit]
+skips = B101
\ No newline at end of file
diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index 0826b71d9..8a2f18ad5 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -1,32 +1,32 @@
version: 0.1
cli:
- version: 1.20.1
+ version: 1.22.1
plugins:
sources:
- id: trunk
- ref: v1.4.4
+ ref: v1.5.0
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- - trufflehog@3.68.5
+ - trufflehog@3.76.3
- yamllint@1.35.1
- - bandit@1.7.7
- - checkov@3.2.32
+ - bandit@1.7.8
+ - checkov@3.2.95
- terrascan@1.19.1
- - trivy@0.49.1
+ - trivy@0.51.1
#- trufflehog@3.63.2-rc0
- taplo@0.8.1
- - ruff@0.3.1
+ - ruff@0.4.4
- isort@5.13.2
- - markdownlint@0.39.0
- - oxipng@9.0.0
- - svgo@3.2.0
- - actionlint@1.6.27
+ - markdownlint@0.40.0
+ - oxipng@9.1.1
+ - svgo@3.3.2
+ - actionlint@1.7.0
- flake8@7.0.0
- hadolint@2.12.0
- shfmt@3.6.0
- - shellcheck@0.9.0
- - black@24.2.0
+ - shellcheck@0.10.0
+ - black@24.4.2
- git-diff-check
- gitleaks@8.18.2
- clang-format@16.0.3
diff --git a/.vscode/settings.json b/.vscode/settings.json
index e86d31c7d..07e198f0a 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -3,5 +3,6 @@
"editor.defaultFormatter": "trunk.io",
"trunk.enableWindows": true,
"files.insertFinalNewline": false,
- "files.trimFinalNewlines": false
+ "files.trimFinalNewlines": false,
+ "cmake.configureOnOpen": false
}
diff --git a/Dockerfile b/Dockerfile
index fee6c62d4..08cb3925d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -48,6 +48,7 @@ USER mesh
WORKDIR /home/mesh
COPY --from=builder /tmp/firmware/release/meshtasticd /home/mesh/
+RUN mkdir data
VOLUME /home/mesh/data
CMD [ "sh", "-cx", "./meshtasticd -d /home/mesh/data --hwid=${HWID:-$RANDOM}" ]
diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini
index 39935b849..f3eb0cbc0 100644
--- a/arch/esp32/esp32.ini
+++ b/arch/esp32/esp32.ini
@@ -1,7 +1,8 @@
; Common settings for ESP targes, mixin with extends = esp32_base
[esp32_base]
extends = arduino_base
-platform = platformio/espressif32@6.3.2 # This is a temporary fix to the S3-based devices bluetooth issues until we can determine what within ESP-IDF changed and can develop a suitable patch.
+custom_esp32_kind = esp32
+platform = platformio/espressif32@6.7.0
build_src_filter =
${arduino_base.build_src_filter} - - - - -
@@ -15,8 +16,10 @@ 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_unflags = -fno-lto
build_flags =
${arduino_base.build_flags}
+ -flto
-Wall
-Wextra
-Isrc/platform/esp32
diff --git a/arch/esp32/esp32c3.ini b/arch/esp32/esp32c3.ini
index 619fdb28a..2ba3036d0 100644
--- a/arch/esp32/esp32c3.ini
+++ b/arch/esp32/esp32c3.ini
@@ -1,5 +1,6 @@
[esp32c3_base]
extends = esp32_base
+custom_esp32_kind = esp32c3
monitor_speed = 115200
monitor_filters = esp32_c3_exception_decoder
diff --git a/arch/esp32/esp32s2.ini b/arch/esp32/esp32s2.ini
index df66de2ed..40fdc461a 100644
--- a/arch/esp32/esp32s2.ini
+++ b/arch/esp32/esp32s2.ini
@@ -1,5 +1,6 @@
[esp32s2_base]
extends = esp32_base
+custom_esp32_kind = esp32s2
build_src_filter =
${esp32_base.build_src_filter} - - -
diff --git a/arch/esp32/esp32s3.ini b/arch/esp32/esp32s3.ini
index 6a1bdd3fd..1cd0e2033 100644
--- a/arch/esp32/esp32s3.ini
+++ b/arch/esp32/esp32s3.ini
@@ -1,5 +1,6 @@
[esp32s3_base]
extends = esp32_base
+custom_esp32_kind = esp32s3
monitor_speed = 115200
diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini
index 2505fe315..1a371e920 100644
--- a/arch/nrf52/nrf52.ini
+++ b/arch/nrf52/nrf52.ini
@@ -1,14 +1,15 @@
[nrf52_base]
; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files
-platform = platformio/nordicnrf52@^10.4.0
+platform = platformio/nordicnrf52@^10.5.0
extends = arduino_base
-build_type = debug ; I'm debugging with ICE a lot now
+build_type = debug
build_flags =
${arduino_base.build_flags}
-DSERIAL_BUFFER_SIZE=1024
-Wno-unused-variable
-Isrc/platform/nrf52
+ -DLFS_NO_ASSERT ; Disable LFS assertions , see https://github.com/meshtastic/firmware/pull/3818
build_src_filter =
${arduino_base.build_src_filter} - - - - - - - - - -
diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini
index 53f06c9f3..482b1f9c5 100644
--- a/arch/portduino/portduino.ini
+++ b/arch/portduino/portduino.ini
@@ -1,6 +1,6 @@
; 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#6fb39b6f94ece9c042141edb4afb91aca94dcaab
+platform = https://github.com/meshtastic/platform-native.git#ad8112adf82ce1f5b917092cf32be07a077801a0
framework = arduino
build_src_filter =
@@ -24,7 +24,7 @@ lib_deps =
${env.lib_deps}
${networking_base.lib_deps}
rweather/Crypto@^0.4.0
- lovyan03/LovyanGFX@^1.1.12
+ https://github.com/lovyan03/LovyanGFX.git#5a39989aa2c9492572255b22f033843ec8900233
build_flags =
${arduino_base.build_flags}
diff --git a/bin/build-native.sh b/bin/build-native.sh
index 9d31d091a..e8ed61bcf 100755
--- a/bin/build-native.sh
+++ b/bin/build-native.sh
@@ -2,6 +2,17 @@
set -e
+platformioFailed() {
+ [[ $VIRTUAL_ENV != "" ]] && exit 1 # don't hint at virtualenv if it's already in use
+ echo -e "\nThere were issues running platformio and you are not using a virtual environment." \
+ "\nYou may try setting up virtualenv and downloading the latest platformio from pip:" \
+ "\n\tvirtualenv venv" \
+ "\n\tsource venv/bin/activate" \
+ "\n\tpip install platformio" \
+ "\n\t./bin/build-native.sh # retry building"
+ exit 1
+}
+
VERSION=$(bin/buildinfo.py long)
SHORT_VERSION=$(bin/buildinfo.py short)
@@ -13,8 +24,8 @@ mkdir -p $OUTDIR/
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
-platformio pkg update --environment native
-pio run --environment native
+platformio pkg update --environment native || platformioFailed
+pio run --environment native || platformioFailed
cp .pio/build/native/program "$OUTDIR/meshtasticd_linux_$(uname -m)"
cp bin/device-install.* $OUTDIR
cp bin/device-update.* $OUTDIR
diff --git a/bin/buildinfo.py b/bin/buildinfo.py
index 6b123c9cf..5aecad1bd 100755
--- a/bin/buildinfo.py
+++ b/bin/buildinfo.py
@@ -1,9 +1,8 @@
#!/usr/bin/env python3
-import configparser
import sys
+
from readprops import readProps
-
-verObj = readProps('version.properties')
+verObj = readProps("version.properties")
propName = sys.argv[1]
print(f"{verObj[propName]}")
diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml
index f729f1ac7..333d6eadc 100644
--- a/bin/config-dist.yaml
+++ b/bin/config-dist.yaml
@@ -38,11 +38,22 @@ Lora:
# Busy: 20
# Reset: 18
+# Module: sx1268 # SX1268-based modules, tested with Ebyte E22 400M33S
+# CS: 21
+# IRQ: 16
+# Busy: 20
+# Reset: 18
+# TXen: 6
+# RXen: 12
+# DIO3_TCXO_VOLTAGE: true
+
# DIO3_TCXO_VOLTAGE: true # the Waveshare Core1262 and others are known to need this setting
# TXen: x # TX and RX enable pins
# RXen: x
+# ch341_quirk: true # Uncomment this to use the chunked SPI transfer that seems to fix the ch341
+
### Set gpio chip to use in /dev/. Defaults to 0.
### Notably the Raspberry Pi 5 puts the GPIO header on gpiochip4
# gpiochip: 4
@@ -96,17 +107,21 @@ Display:
# Panel: ILI9341
# CS: 8
# DC: 25
-# Backlight: 2
-# Width: 320
-# Height: 240
+# Width: 240
+# Height: 320
+# Rotate: true
Touchscreen:
### Note, at least for now, the touchscreen must have a CS pin defined, even if you let Linux manage the CS switching.
-# Module: STMPE610
+# Module: STMPE610 # Option 1 for Adafruit PiTFT 2.8
# CS: 7
# IRQ: 24
+# Module: FT5x06 # Option 2 for Adafruit PiTFT 2.8
+# IRQ: 24
+# I2CAddr: 0x38
+
# Module: XPT2046 # Waveshare 2.8inch
# CS: 7
# IRQ: 17
diff --git a/bin/generate_ci_matrix.py b/bin/generate_ci_matrix.py
index 2501e83c1..46398dd59 100755
--- a/bin/generate_ci_matrix.py
+++ b/bin/generate_ci_matrix.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
-"""Generate the CI matrix"""
+"""Generate the CI matrix."""
import configparser
import json
@@ -34,5 +34,10 @@ for subdir, dirs, files in os.walk(rootdir):
outlist.append(section)
else:
outlist.append(section)
+ if "board_check" in config[config[c].name]:
+ if (config[config[c].name]["board_check"] == "true") & (
+ "check" in options
+ ):
+ outlist.append(section)
print(json.dumps(outlist))
diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py
index e03a35e3a..3202a1e7a 100644
--- a/bin/platformio-custom.py
+++ b/bin/platformio-custom.py
@@ -1,13 +1,14 @@
-import subprocess
-import configparser
-import traceback
+# trunk-ignore-all(ruff/F821)
+# trunk-ignore-all(flake8/F821): For SConstruct imports
import sys
from os.path import join
+
from readprops import readProps
Import("env")
platform = env.PioPlatform()
+
def esp32_create_combined_bin(source, target, env):
# this sub is borrowed from ESPEasy build toolchain. It's licensed under GPL V3
# https://github.com/letscontrolit/ESPEasy/blob/mega/tools/pio/post_esp32.py
@@ -20,8 +21,8 @@ def esp32_create_combined_bin(source, target, env):
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
chip = env.get("BOARD_MCU")
flash_size = env.BoardConfig().get("upload.flash_size")
- flash_freq = env.BoardConfig().get("build.f_flash", '40m')
- flash_freq = flash_freq.replace('000000L', 'm')
+ flash_freq = env.BoardConfig().get("build.f_flash", "40m")
+ flash_freq = flash_freq.replace("000000L", "m")
flash_mode = env.BoardConfig().get("build.flash_mode", "dio")
memory_type = env.BoardConfig().get("build.arduino.memory_type", "qio_qspi")
if flash_mode == "qio" or flash_mode == "qout":
@@ -51,23 +52,42 @@ def esp32_create_combined_bin(source, target, env):
print(f" - {hex(app_offset)} | {firmware_name}")
cmd += [hex(app_offset), firmware_name]
- print('Using esptool.py arguments: %s' % ' '.join(cmd))
+ print("Using esptool.py arguments: %s" % " ".join(cmd))
esptool.main(cmd)
-if (platform.name == "espressif32"):
+
+if platform.name == "espressif32":
sys.path.append(join(platform.get_package_dir("tool-esptoolpy")))
import esptool
- env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)
+
+ env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)
+
+ esp32_kind = env.GetProjectOption("custom_esp32_kind")
+ if esp32_kind == "esp32":
+ # Free up some IRAM by removing auxiliary SPI flash chip drivers.
+ # Wrapped stub symbols are defined in src/platform/esp32/iram-quirk.c.
+ env.Append(
+ LINKFLAGS=[
+ "-Wl,--wrap=esp_flash_chip_gd",
+ "-Wl,--wrap=esp_flash_chip_issi",
+ "-Wl,--wrap=esp_flash_chip_winbond",
+ ]
+ )
+ else:
+ # For newer ESP32 targets, using newlib nano works better.
+ env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"])
Import("projenv")
prefsLoc = projenv["PROJECT_DIR"] + "/version.properties"
verObj = readProps(prefsLoc)
-print("Using meshtastic platformio-custom.py, firmware version " + verObj['long'])
+print("Using meshtastic platformio-custom.py, firmware version " + verObj["long"])
# General options that are passed to the C and C++ compilers
-projenv.Append(CCFLAGS=[
- "-DAPP_VERSION=" + verObj['long'],
- "-DAPP_VERSION_SHORT=" + verObj['short']
-])
+projenv.Append(
+ CCFLAGS=[
+ "-DAPP_VERSION=" + verObj["long"],
+ "-DAPP_VERSION_SHORT=" + verObj["short"],
+ ]
+)
diff --git a/boards/promicro-nrf52840.json b/boards/promicro-nrf52840.json
new file mode 100644
index 000000000..99ae3f01e
--- /dev/null
+++ b/boards/promicro-nrf52840.json
@@ -0,0 +1,52 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "nrf52840_s140_v6.ld"
+ },
+ "core": "nRF5",
+ "cpu": "cortex-m4",
+ "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
+ "f_cpu": "64000000L",
+ "hwids": [
+ ["0x239A", "0x00B3"],
+ ["0x239A", "0x8029"],
+ ["0x239A", "0x0029"],
+ ["0x239A", "0x002A"],
+ ["0x239A", "0x802A"]
+ ],
+ "usb_product": "ProMicro compatible nRF52840",
+ "mcu": "nrf52840",
+ "variant": "promicro_diy",
+ "bsp": {
+ "name": "adafruit"
+ },
+ "softdevice": {
+ "sd_flags": "-DS140",
+ "sd_name": "s140",
+ "sd_version": "6.1.1",
+ "sd_fwid": "0x00B6"
+ },
+ "bootloader": {
+ "settings_addr": "0xFF000"
+ }
+ },
+ "connectivity": ["bluetooth"],
+ "debug": {
+ "jlink_device": "nRF52840_xxAA",
+ "svd_path": "nrf52840.svd"
+ },
+ "frameworks": ["arduino"],
+ "name": "ProMicro compatible nRF52840",
+ "upload": {
+ "maximum_ram_size": 248832,
+ "maximum_size": 815104,
+ "speed": 115200,
+ "protocol": "nrfutil",
+ "protocols": ["nrfutil", "jlink", "nrfjprog", "stlink"],
+ "use_1200bps_touch": true,
+ "require_upload_port": true,
+ "wait_for_upload_port": true
+ },
+ "url": "https://www.nologo.tech/product/otherboard/NRF52840.html",
+ "vendor": "Nologo"
+}
diff --git a/boards/tbeam-s3-core.json b/boards/tbeam-s3-core.json
index 4c82a2789..7bda2e5a0 100644
--- a/boards/tbeam-s3-core.json
+++ b/boards/tbeam-s3-core.json
@@ -7,7 +7,8 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DLILYGO_TBEAM_S3_CORE",
- "-DARDUINO_USB_MODE=1",
+ "-DARDUINO_USB_CDC_ON_BOOT=1",
+ "-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
diff --git a/boards/wio-sdk-wm1110.json b/boards/wio-sdk-wm1110.json
new file mode 100644
index 000000000..029c9c085
--- /dev/null
+++ b/boards/wio-sdk-wm1110.json
@@ -0,0 +1,58 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "nrf52840_s140_v6.ld"
+ },
+ "core": "nRF5",
+ "cpu": "cortex-m4",
+ "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA",
+ "f_cpu": "64000000L",
+ "hwids": [
+ ["0x239A", "0x8029"],
+ ["0x239A", "0x0029"],
+ ["0x239A", "0x002A"],
+ ["0x239A", "0x802A"]
+ ],
+ "usb_product": "WIO-BOOT",
+ "mcu": "nrf52840",
+ "variant": "Seeed_WIO_WM1110",
+ "bsp": {
+ "name": "adafruit"
+ },
+ "softdevice": {
+ "sd_flags": "-DS140",
+ "sd_name": "s140",
+ "sd_version": "6.1.1",
+ "sd_fwid": "0x00B6"
+ },
+ "bootloader": {
+ "settings_addr": "0xFF000"
+ }
+ },
+ "connectivity": ["bluetooth"],
+ "debug": {
+ "jlink_device": "nRF52840_xxAA",
+ "svd_path": "nrf52840.svd"
+ },
+ "frameworks": ["arduino"],
+ "name": "Seeed WIO WM1110",
+ "upload": {
+ "maximum_ram_size": 248832,
+ "maximum_size": 815104,
+ "speed": 115200,
+ "protocol": "nrfutil",
+ "protocols": [
+ "jlink",
+ "nrfjprog",
+ "nrfutil",
+ "stlink",
+ "cmsis-dap",
+ "blackmagic"
+ ],
+ "use_1200bps_touch": true,
+ "require_upload_port": true,
+ "wait_for_upload_port": true
+ },
+ "url": "https://www.seeedstudio.com/Wio-WM1110-Dev-Kit-p-5677.html",
+ "vendor": "Seeed Studio"
+}
diff --git a/boards/wio-tracker-wm1110.json b/boards/wio-tracker-wm1110.json
new file mode 100644
index 000000000..029c9c085
--- /dev/null
+++ b/boards/wio-tracker-wm1110.json
@@ -0,0 +1,58 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "nrf52840_s140_v6.ld"
+ },
+ "core": "nRF5",
+ "cpu": "cortex-m4",
+ "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA",
+ "f_cpu": "64000000L",
+ "hwids": [
+ ["0x239A", "0x8029"],
+ ["0x239A", "0x0029"],
+ ["0x239A", "0x002A"],
+ ["0x239A", "0x802A"]
+ ],
+ "usb_product": "WIO-BOOT",
+ "mcu": "nrf52840",
+ "variant": "Seeed_WIO_WM1110",
+ "bsp": {
+ "name": "adafruit"
+ },
+ "softdevice": {
+ "sd_flags": "-DS140",
+ "sd_name": "s140",
+ "sd_version": "6.1.1",
+ "sd_fwid": "0x00B6"
+ },
+ "bootloader": {
+ "settings_addr": "0xFF000"
+ }
+ },
+ "connectivity": ["bluetooth"],
+ "debug": {
+ "jlink_device": "nRF52840_xxAA",
+ "svd_path": "nrf52840.svd"
+ },
+ "frameworks": ["arduino"],
+ "name": "Seeed WIO WM1110",
+ "upload": {
+ "maximum_ram_size": 248832,
+ "maximum_size": 815104,
+ "speed": 115200,
+ "protocol": "nrfutil",
+ "protocols": [
+ "jlink",
+ "nrfjprog",
+ "nrfutil",
+ "stlink",
+ "cmsis-dap",
+ "blackmagic"
+ ],
+ "use_1200bps_touch": true,
+ "require_upload_port": true,
+ "wait_for_upload_port": true
+ },
+ "url": "https://www.seeedstudio.com/Wio-WM1110-Dev-Kit-p-5677.html",
+ "vendor": "Seeed Studio"
+}
diff --git a/boards/wiphone.json b/boards/wiphone.json
new file mode 100644
index 000000000..bb01f425f
--- /dev/null
+++ b/boards/wiphone.json
@@ -0,0 +1,34 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "esp32_out.ld",
+ "partitions": "default_16MB.csv"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-DARDUINO_WIPHONE14",
+ "-DBOARD_HAS_PSRAM",
+ "-mfix-esp32-psram-cache-issue",
+ "-mfix-esp32-psram-cache-strategy=memw"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "40000000L",
+ "flash_mode": "dio",
+ "mcu": "esp32",
+ "variant": "wiphone",
+ "board": "WiPhone"
+ },
+ "connectivity": ["wifi", "bluetooth"],
+ "frameworks": ["arduino", "espidf"],
+ "name": "WIPhone Integrated 1.4",
+ "upload": {
+ "flash_size": "16MB",
+ "maximum_ram_size": 532480,
+ "maximum_size": 6553600,
+ "maximum_data_size": 4521984,
+ "require_upload_port": true,
+ "speed": 921600
+ },
+ "url": "https://www.wiphone.io/",
+ "vendor": "HackEDA"
+}
diff --git a/boards/wiscore_rak11200.json b/boards/wiscore_rak11200.json
index 33d16ba77..54ee9b69e 100644
--- a/boards/wiscore_rak11200.json
+++ b/boards/wiscore_rak11200.json
@@ -4,7 +4,7 @@
"ldscript": "esp32_out.ld"
},
"core": "esp32",
- "extra_flags": "-DARDUINO_ESP32_DEV",
+ "extra_flags": ["-DBOARD_HAS_PSRAM", "-DARDUINO_ESP32_DEV"],
"f_cpu": "240000000L",
"f_flash": "40000000L",
"flash_mode": "dio",
diff --git a/platformio.ini b/platformio.ini
index 2f9d28e0f..05c420331 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -29,8 +29,12 @@ default_envs = tbeam
;default_envs = meshtastic-dr-dev
;default_envs = m5stack-coreink
;default_envs = rak4631
+;default_envs = rak2560
;default_envs = rak10701
;default_envs = wio-e5
+;default_envs = radiomaster_900_bandit_nano
+;default_envs = radiomaster_900_bandit_micro
+;default_envs = heltec_capsule_sensor_v3
extra_configs =
arch/*/*.ini
@@ -70,18 +74,19 @@ build_flags = -Wno-missing-field-initializers
-DRADIOLIB_EXCLUDE_FSK4
-DRADIOLIB_EXCLUDE_APRS
-DRADIOLIB_EXCLUDE_LORAWAN
+ -DMESHTASTIC_EXCLUDE_DROPZONE=1
monitor_speed = 115200
lib_deps =
- jgromes/RadioLib@~6.5.0
- https://github.com/meshtastic/esp8266-oled-ssd1306.git#ee628ee6c9588d4c56c9e3da35f0fc9448ad54a8 ; ESP8266_SSD1306
+ jgromes/RadioLib@~6.6.0
+ https://github.com/meshtastic/esp8266-oled-ssd1306.git#2b40affbe7f7dc63b6c00fa88e7e12ed1f8e1719 ; ESP8266_SSD1306
mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce
robtillaart/I2CKeyPad@^0.4.0 ; port extender with keymatrix
https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159
- https://github.com/meshtastic/TinyGPSPlus.git#964f75a72cccd6b53cd74e4add1f7a42c6f7344d
+ https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4
https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0
- nanopb/Nanopb@^0.4.7
+ nanopb/Nanopb@^0.4.8
erriez/ErriezCRC32@^1.0.1
; Used for the code analysis in PIO Home / Inspect
@@ -97,7 +102,6 @@ check_flags =
framework = arduino
lib_deps =
${env.lib_deps}
- mprograms/QMC5883LCompass@^1.2.0
end2endzone/NonBlockingRTTTL@^1.3.0
https://github.com/meshtastic/SparkFun_ATECCX08a_Arduino_Library.git#5cf62b36c6f30bc72a07bdb2c11fc9a22d1e31da
@@ -120,10 +124,7 @@ lib_deps =
adafruit/Adafruit BMP280 Library@^2.6.8
adafruit/Adafruit BMP085 Library@^1.2.4
adafruit/Adafruit BME280 Library@^2.2.2
- https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.5.2400
- boschsensortec/BME68x Sensor Library@^1.1.40407
adafruit/Adafruit MCP9808 Library@^2.0.0
- https://github.com/KodinLanewave/INA3221@^1.0.0
adafruit/Adafruit INA260 Library@^1.5.0
adafruit/Adafruit INA219@^1.2.0
adafruit/Adafruit SHTC3 Library@^1.0.0
@@ -132,5 +133,23 @@ lib_deps =
adafruit/Adafruit PM25 AQI Sensor@^1.0.6
adafruit/Adafruit MPU6050@^2.2.4
adafruit/Adafruit LIS3DH@^1.2.4
- https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17
- adafruit/Adafruit LSM6DS@^4.7.2
\ No newline at end of file
+ adafruit/Adafruit AHTX0@^2.0.5
+ adafruit/Adafruit LSM6DS@^4.7.2
+ adafruit/Adafruit VEML7700 Library@^2.1.6
+ adafruit/Adafruit SHT4x Library@^1.0.4
+ adafruit/Adafruit TSL2591 Library@^1.4.5
+ sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@^1.0.5
+ ClosedCube OPT3001@^1.1.2
+ emotibit/EmotiBit MLX90632@^1.0.8
+ dfrobot/DFRobot_RTU@^1.0.3
+
+
+ https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.7.2502
+ boschsensortec/BME68x Sensor Library@^1.1.40407
+ https://github.com/KodinLanewave/INA3221@^1.0.0
+ lewisxhe/SensorLib@^0.2.0
+ mprograms/QMC5883LCompass@^1.2.0
+
+
+ https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee
+
diff --git a/protobufs b/protobufs
index eade2c6be..1c3029f28 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit eade2c6befb65a9c46c5d28ae1e8e24c37a1a3d0
+Subproject commit 1c3029f2868e5fc49809fd378f6c0c66aee0eaf4
diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h
index fa5acdaae..f45511cca 100644
--- a/src/AccelerometerThread.h
+++ b/src/AccelerometerThread.h
@@ -1,6 +1,10 @@
+#pragma once
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "PowerFSM.h"
#include "concurrency/OSThread.h"
-#include "configuration.h"
#include "main.h"
#include "power.h"
@@ -10,14 +14,15 @@
#include
#include
#include
-
-SensorBMA423 bmaSensor;
-bool BMA_IRQ = false;
+#ifdef RAK_4631
+#include "Fusion/Fusion.h"
+#include
+#endif
#define ACCELEROMETER_CHECK_INTERVAL_MS 100
#define ACCELEROMETER_CLICK_THRESHOLD 40
-int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
+static inline int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
{
Wire.beginTransmission(address);
Wire.write(reg);
@@ -30,7 +35,7 @@ int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
return 0; // Pass
}
-int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
+static inline int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
{
Wire.beginTransmission(address);
Wire.write(reg);
@@ -38,8 +43,6 @@ int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
return (0 != Wire.endTransmission());
}
-namespace concurrency
-{
class AccelerometerThread : public concurrency::OSThread
{
public:
@@ -50,14 +53,122 @@ class AccelerometerThread : public concurrency::OSThread
disable();
return;
}
-
+ acceleremoter_type = type;
+#ifndef RAK_4631
if (!config.display.wake_on_tap_or_motion && !config.device.double_tap_as_button_press) {
LOG_DEBUG("AccelerometerThread disabling due to no interested configurations\n");
disable();
return;
}
+#endif
+ init();
+ }
- acceleremoter_type = type;
+ void start()
+ {
+ init();
+ setIntervalFromNow(0);
+ };
+
+ protected:
+ int32_t runOnce() override
+ {
+ canSleep = true; // Assume we should not keep the board awake
+
+ if (acceleremoter_type == ScanI2C::DeviceType::MPU6050 && mpu.getMotionInterruptStatus()) {
+ wakeScreen();
+ } else if (acceleremoter_type == ScanI2C::DeviceType::LIS3DH && lis.getClick() > 0) {
+ uint8_t click = lis.getClick();
+ if (!config.device.double_tap_as_button_press) {
+ wakeScreen();
+ }
+
+ if (config.device.double_tap_as_button_press && (click & 0x20)) {
+ buttonPress();
+ return 500;
+ }
+ } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.readIrqStatus() != DEV_WIRE_NONE) {
+ if (bmaSensor.isTilt() || bmaSensor.isDoubleTap()) {
+ wakeScreen();
+ return 500;
+ }
+#ifdef RAK_4631
+ } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160) {
+ sBmx160SensorData_t magAccel;
+ sBmx160SensorData_t gAccel;
+
+ /* Get a new sensor event */
+ bmx160.getAllData(&magAccel, NULL, &gAccel);
+
+ // expirimental calibrate routine. Limited to between 10 and 30 seconds after boot
+ if (millis() > 10 * 1000 && millis() < 30 * 1000) {
+ if (magAccel.x > highestX)
+ highestX = magAccel.x;
+ if (magAccel.x < lowestX)
+ lowestX = magAccel.x;
+ if (magAccel.y > highestY)
+ highestY = magAccel.y;
+ if (magAccel.y < lowestY)
+ lowestY = magAccel.y;
+ if (magAccel.z > highestZ)
+ highestZ = magAccel.z;
+ if (magAccel.z < lowestZ)
+ lowestZ = magAccel.z;
+ }
+
+ int highestRealX = highestX - (highestX + lowestX) / 2;
+
+ magAccel.x -= (highestX + lowestX) / 2;
+ magAccel.y -= (highestY + lowestY) / 2;
+ magAccel.z -= (highestZ + lowestZ) / 2;
+ FusionVector ga, ma;
+ ga.axis.x = -gAccel.x; // default location for the BMX160 is on the rear of the board
+ ga.axis.y = -gAccel.y;
+ ga.axis.z = gAccel.z;
+ ma.axis.x = -magAccel.x;
+ ma.axis.y = -magAccel.y;
+ ma.axis.z = magAccel.z * 3;
+
+ // If we're set to one of the inverted positions
+ if (config.display.compass_orientation > meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270) {
+ ma = FusionAxesSwap(ma, FusionAxesAlignmentNXNYPZ);
+ ga = FusionAxesSwap(ga, FusionAxesAlignmentNXNYPZ);
+ }
+
+ float heading = FusionCompassCalculateHeading(FusionConventionNed, ga, ma);
+
+ switch (config.display.compass_orientation) {
+ case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0_INVERTED:
+ case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0:
+ break;
+ case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90:
+ case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90_INVERTED:
+ heading += 90;
+ break;
+ case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180:
+ case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180_INVERTED:
+ heading += 180;
+ break;
+ case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270:
+ case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED:
+ heading += 270;
+ break;
+ }
+
+ screen->setHeading(heading);
+
+#endif
+ } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.shake()) {
+ wakeScreen();
+ return 500;
+ }
+
+ return ACCELEROMETER_CHECK_INTERVAL_MS;
+ }
+
+ private:
+ void init()
+ {
LOG_DEBUG("AccelerometerThread initializing\n");
if (acceleremoter_type == ScanI2C::DeviceType::MPU6050 && mpu.begin(accelerometer_found.address)) {
@@ -109,6 +220,11 @@ class AccelerometerThread : public concurrency::OSThread
bmaSensor.enableTiltIRQ();
// It corresponds to isDoubleClick interrupt
bmaSensor.enableWakeupIRQ();
+#ifdef RAK_4631
+ } else if (acceleremoter_type == ScanI2C::DeviceType::BMX160 && bmx160.begin()) {
+ bmx160.ODR_Config(BMX160_ACCEL_ODR_100HZ, BMX160_GYRO_ODR_100HZ); // set output data rate
+
+#endif
} else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.begin_I2C(accelerometer_found.address)) {
LOG_DEBUG("LSM6DS3 initializing\n");
// Default threshold of 2G, less sensitive options are 4, 8 or 16G
@@ -120,38 +236,6 @@ class AccelerometerThread : public concurrency::OSThread
// Duration is number of occurances needed to trigger, higher threshold is less sensitive
}
}
-
- protected:
- int32_t runOnce() override
- {
- canSleep = true; // Assume we should not keep the board awake
-
- if (acceleremoter_type == ScanI2C::DeviceType::MPU6050 && mpu.getMotionInterruptStatus()) {
- wakeScreen();
- } else if (acceleremoter_type == ScanI2C::DeviceType::LIS3DH && lis.getClick() > 0) {
- uint8_t click = lis.getClick();
- if (!config.device.double_tap_as_button_press) {
- wakeScreen();
- }
-
- if (config.device.double_tap_as_button_press && (click & 0x20)) {
- buttonPress();
- return 500;
- }
- } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.readIrqStatus() != DEV_WIRE_NONE) {
- if (bmaSensor.isTilt() || bmaSensor.isDoubleTap()) {
- wakeScreen();
- return 500;
- }
- } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.shake()) {
- wakeScreen();
- return 500;
- }
-
- return ACCELEROMETER_CHECK_INTERVAL_MS;
- }
-
- private:
void wakeScreen()
{
if (powerFSM.getState() == &stateDARK) {
@@ -170,6 +254,12 @@ class AccelerometerThread : public concurrency::OSThread
Adafruit_MPU6050 mpu;
Adafruit_LIS3DH lis;
Adafruit_LSM6DS3TRC lsm;
+ SensorBMA423 bmaSensor;
+#ifdef RAK_4631
+ RAK_BMX160 bmx160;
+ float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0;
+#endif
+ bool BMA_IRQ = false;
};
-} // namespace concurrency
\ No newline at end of file
+#endif
\ No newline at end of file
diff --git a/src/AmbientLightingThread.h b/src/AmbientLightingThread.h
index 98ccedde4..6b3360b1f 100644
--- a/src/AmbientLightingThread.h
+++ b/src/AmbientLightingThread.h
@@ -5,6 +5,16 @@
NCP5623 rgb;
#endif
+#ifdef HAS_NEOPIXEL
+#include
+Adafruit_NeoPixel pixels(NEOPIXEL_COUNT, NEOPIXEL_DATA, NEOPIXEL_TYPE);
+#endif
+
+#ifdef UNPHONE
+#include "unPhone.h"
+extern unPhone unphone;
+#endif
+
namespace concurrency
{
class AmbientLightingThread : public concurrency::OSThread
@@ -27,15 +37,31 @@ class AmbientLightingThread : public concurrency::OSThread
disable();
return;
}
+#endif
+#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE)
if (!moduleConfig.ambient_lighting.led_state) {
LOG_DEBUG("AmbientLightingThread disabling due to moduleConfig.ambient_lighting.led_state OFF\n");
disable();
return;
}
LOG_DEBUG("AmbientLightingThread initializing\n");
+#ifdef HAS_NCP5623
if (_type == ScanI2C::NCP5623) {
rgb.begin();
+#endif
+#ifdef RGBLED_RED
+ pinMode(RGBLED_RED, OUTPUT);
+ pinMode(RGBLED_GREEN, OUTPUT);
+ pinMode(RGBLED_BLUE, OUTPUT);
+#endif
+#ifdef HAS_NEOPIXEL
+ pixels.begin(); // Initialise the pixel(s)
+ pixels.clear(); // Set all pixel colors to 'off'
+ pixels.setBrightness(moduleConfig.ambient_lighting.current);
+#endif
setLighting();
+#endif
+#ifdef HAS_NCP5623
}
#endif
}
@@ -43,16 +69,17 @@ class AmbientLightingThread : public concurrency::OSThread
protected:
int32_t runOnce() override
{
+#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE)
#ifdef HAS_NCP5623
if (_type == ScanI2C::NCP5623 && moduleConfig.ambient_lighting.led_state) {
+#endif
setLighting();
return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification
- } else {
- return disable();
+#ifdef HAS_NCP5623
}
-#else
- return disable();
#endif
+#endif
+ return disable();
}
private:
@@ -65,9 +92,36 @@ class AmbientLightingThread : public concurrency::OSThread
rgb.setRed(moduleConfig.ambient_lighting.red);
rgb.setGreen(moduleConfig.ambient_lighting.green);
rgb.setBlue(moduleConfig.ambient_lighting.blue);
- LOG_DEBUG("Initializing Ambient lighting w/ current=%d, red=%d, green=%d, blue=%d\n",
+ LOG_DEBUG("Initializing NCP5623 Ambient lighting w/ current=%d, red=%d, green=%d, blue=%d\n",
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
moduleConfig.ambient_lighting.blue);
+#endif
+#ifdef HAS_NEOPIXEL
+ pixels.fill(pixels.Color(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
+ moduleConfig.ambient_lighting.blue),
+ 0, NEOPIXEL_COUNT);
+ pixels.show();
+ LOG_DEBUG("Initializing NeoPixel Ambient lighting w/ brightness(current)=%d, red=%d, green=%d, blue=%d\n",
+ moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
+ moduleConfig.ambient_lighting.blue);
+#endif
+#ifdef RGBLED_CA
+ analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red);
+ analogWrite(RGBLED_GREEN, 255 - moduleConfig.ambient_lighting.green);
+ analogWrite(RGBLED_BLUE, 255 - moduleConfig.ambient_lighting.blue);
+ LOG_DEBUG("Initializing Ambient lighting RGB Common Anode w/ red=%d, green=%d, blue=%d\n",
+ moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
+#elif defined(RGBLED_RED)
+ analogWrite(RGBLED_RED, moduleConfig.ambient_lighting.red);
+ analogWrite(RGBLED_GREEN, moduleConfig.ambient_lighting.green);
+ analogWrite(RGBLED_BLUE, moduleConfig.ambient_lighting.blue);
+ LOG_DEBUG("Initializing Ambient lighting RGB Common Cathode w/ red=%d, green=%d, blue=%d\n",
+ moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
+#endif
+#ifdef UNPHONE
+ unphone.rgb(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
+ LOG_DEBUG("Initializing unPhone Ambient lighting w/ red=%d, green=%d, blue=%d\n", moduleConfig.ambient_lighting.red,
+ moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif
}
};
diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp
index 206bb7239..4b3bb3fbc 100644
--- a/src/ButtonThread.cpp
+++ b/src/ButtonThread.cpp
@@ -41,7 +41,11 @@ ButtonThread::ButtonThread() : OSThread("Button")
}
#elif defined(BUTTON_PIN)
int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; // Resolved button pin
+#if defined(HELTEC_CAPSULE_SENSOR_V3)
+ this->userButton = OneButton(pin, false, false);
+#else
this->userButton = OneButton(pin, true, true);
+#endif
LOG_DEBUG("Using GPIO%02d for button\n", pin);
#endif
@@ -52,8 +56,8 @@ ButtonThread::ButtonThread() : OSThread("Button")
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
userButton.attachClick(userButtonPressed);
- userButton.setClickMs(250);
- userButton.setPressMs(c_longPressTime);
+ userButton.setClickMs(BUTTON_CLICK_MS);
+ userButton.setPressMs(BUTTON_LONGPRESS_MS);
userButton.setDebounceMs(1);
userButton.attachDoubleClick(userButtonDoublePressed);
userButton.attachMultiClick(userButtonMultiPressed, this); // Reference to instance: get click count from non-static OneButton
@@ -70,8 +74,8 @@ ButtonThread::ButtonThread() : OSThread("Button")
pinMode(BUTTON_PIN_ALT, INPUT_PULLUP_SENSE);
#endif
userButtonAlt.attachClick(userButtonPressed);
- userButtonAlt.setClickMs(250);
- userButtonAlt.setPressMs(c_longPressTime);
+ userButtonAlt.setClickMs(BUTTON_CLICK_MS);
+ userButtonAlt.setPressMs(BUTTON_LONGPRESS_MS);
userButtonAlt.setDebounceMs(1);
userButtonAlt.attachDoubleClick(userButtonDoublePressed);
userButtonAlt.attachLongPressStart(userButtonPressedLongStart);
@@ -80,7 +84,7 @@ ButtonThread::ButtonThread() : OSThread("Button")
#ifdef BUTTON_PIN_TOUCH
userButtonTouch = OneButton(BUTTON_PIN_TOUCH, true, true);
- userButtonTouch.setPressMs(400);
+ userButtonTouch.setPressMs(BUTTON_TOUCH_MS);
userButtonTouch.attachLongPressStart(touchPressedLongStart); // Better handling with longpress than click?
#endif
@@ -136,9 +140,12 @@ int32_t ButtonThread::runOnce()
case BUTTON_EVENT_DOUBLE_PRESSED: {
LOG_BUTTON("Double press!\n");
service.refreshLocalMeshNode();
- service.sendNetworkPing(NODENUM_BROADCAST, true);
+ auto sentPosition = service.trySendPosition(NODENUM_BROADCAST, true);
if (screen) {
- screen->print("Sent ad-hoc ping\n");
+ if (sentPosition)
+ screen->print("Sent ad-hoc position\n");
+ else
+ screen->print("Sent ad-hoc nodeinfo\n");
screen->forceDisplay(true); // Force a new UI frame, then force an EInk update
}
break;
@@ -193,15 +200,13 @@ int32_t ButtonThread::runOnce()
#ifdef BUTTON_PIN_TOUCH
case BUTTON_EVENT_TOUCH_LONG_PRESSED: {
LOG_BUTTON("Touch press!\n");
- if (config.display.wake_on_tap_or_motion) {
- if (screen) {
- // Wake if asleep
- if (powerFSM.getState() == &stateDARK)
- powerFSM.trigger(EVENT_PRESS);
+ if (screen) {
+ // Wake if asleep
+ if (powerFSM.getState() == &stateDARK)
+ powerFSM.trigger(EVENT_PRESS);
- // Update display (legacy behaviour)
- screen->forceDisplay();
- }
+ // Update display (legacy behaviour)
+ screen->forceDisplay();
}
break;
}
@@ -213,6 +218,7 @@ int32_t ButtonThread::runOnce()
btnEvent = BUTTON_EVENT_NONE;
}
+ runASAP = false;
return 50;
}
@@ -230,9 +236,10 @@ void ButtonThread::attachButtonInterrupts()
attachInterrupt(
config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN,
[]() {
+ ButtonThread::userButton.tick();
+ runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
- ButtonThread::userButton.tick();
},
CHANGE);
#endif
@@ -279,6 +286,7 @@ void ButtonThread::wakeOnIrq(int irq, int mode)
[] {
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
+ runASAP = true;
},
FALLING);
}
diff --git a/src/ButtonThread.h b/src/ButtonThread.h
index 07c7ccff7..d7a9201a3 100644
--- a/src/ButtonThread.h
+++ b/src/ButtonThread.h
@@ -4,11 +4,22 @@
#include "concurrency/OSThread.h"
#include "configuration.h"
+#ifndef BUTTON_CLICK_MS
+#define BUTTON_CLICK_MS 250
+#endif
+
+#ifndef BUTTON_LONGPRESS_MS
+#define BUTTON_LONGPRESS_MS 5000
+#endif
+
+#ifndef BUTTON_TOUCH_MS
+#define BUTTON_TOCH_MS 400
+#endif
+
class ButtonThread : public concurrency::OSThread
{
public:
- static const uint32_t c_longPressTime = 5000; // shutdown after 5s
- static const uint32_t c_holdOffTime = 30000; // hold off 30s after boot
+ static const uint32_t c_holdOffTime = 30000; // hold off 30s after boot
enum ButtonEventType {
BUTTON_EVENT_NONE,
diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h
index f0686b811..ca908197e 100644
--- a/src/DebugConfiguration.h
+++ b/src/DebugConfiguration.h
@@ -36,7 +36,7 @@
#define LOG_CRIT(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#define LOG_TRACE(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#else
-#ifdef DEBUG_PORT
+#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
#define LOG_DEBUG(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_DEBUG, __VA_ARGS__)
#define LOG_INFO(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_INFO, __VA_ARGS__)
#define LOG_WARN(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_WARN, __VA_ARGS__)
diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp
index e7760c575..96aad1a9a 100644
--- a/src/FSCommon.cpp
+++ b/src/FSCommon.cpp
@@ -205,6 +205,62 @@ void rmDir(const char *dirname)
#endif
}
+bool fsCheck()
+{
+#if defined(ARCH_NRF52)
+ size_t write_size = 0;
+ size_t read_size = 0;
+ char buf[32] = {0};
+
+ Adafruit_LittleFS_Namespace::File file(FSCom);
+ const char *text = "meshtastic fs test";
+ size_t text_length = strlen(text);
+ const char *filename = "/meshtastic.txt";
+
+ LOG_DEBUG("Try create file .\n");
+ if (file.open(filename, FILE_O_WRITE)) {
+ write_size = file.write(text);
+ } else {
+ LOG_DEBUG("Open file failed .\n");
+ goto FORMAT_FS;
+ }
+
+ if (write_size != text_length) {
+ LOG_DEBUG("Text bytes do not match .\n");
+ file.close();
+ goto FORMAT_FS;
+ }
+
+ file.close();
+
+ if (!file.open(filename, FILE_O_READ)) {
+ LOG_DEBUG("Open file failed .\n");
+ goto FORMAT_FS;
+ }
+
+ read_size = file.readBytes(buf, text_length);
+ if (read_size != text_length) {
+ LOG_DEBUG("Text bytes do not match .\n");
+ file.close();
+ goto FORMAT_FS;
+ }
+
+ if (memcmp(buf, text, text_length) != 0) {
+ LOG_DEBUG("The written bytes do not match the read bytes .\n");
+ file.close();
+ goto FORMAT_FS;
+ }
+ return true;
+FORMAT_FS:
+ LOG_DEBUG("Format FS ....\n");
+ FSCom.format();
+ FSCom.begin();
+ return false;
+#else
+ return true;
+#endif
+}
+
void fsInit()
{
#ifdef FSCom
@@ -212,8 +268,37 @@ void fsInit()
LOG_ERROR("Filesystem mount Failed.\n");
// assert(0); This auto-formats the partition, so no need to fail here.
}
-#ifdef ARCH_ESP32
+#if defined(ARCH_ESP32)
LOG_DEBUG("Filesystem files (%d/%d Bytes):\n", FSCom.usedBytes(), FSCom.totalBytes());
+#elif defined(ARCH_NRF52)
+ /*
+ * nRF52840 has a certain chance of automatic formatting failure.
+ * Try to create a file after initializing the file system. If the creation fails,
+ * it means that the file system is not working properly. Please format it manually again.
+ * To check the normality of the file system, you need to disable the LFS_NO_ASSERT assertion.
+ * Otherwise, the assertion will be entered at the moment of reading or opening, and the FS will not be formatted.
+ * */
+ bool ret = false;
+ uint8_t retry = 3;
+
+ while (retry--) {
+ ret = fsCheck();
+ if (ret) {
+ LOG_DEBUG("File system check is OK.\n");
+ break;
+ }
+ delay(10);
+ }
+
+ // It may not be possible to reach this step.
+ // Add a loop here to prevent unpredictable situations from happening.
+ // Can add a screen to display error status later.
+ if (!ret) {
+ while (1) {
+ LOG_ERROR("The file system is damaged and cannot proceed to the next step.\n");
+ delay(1000);
+ }
+ }
#else
LOG_DEBUG("Filesystem files:\n");
#endif
diff --git a/src/Fusion/Fusion.h b/src/Fusion/Fusion.h
new file mode 100644
index 000000000..48f5198c5
--- /dev/null
+++ b/src/Fusion/Fusion.h
@@ -0,0 +1,32 @@
+/**
+ * @file Fusion.h
+ * @author Seb Madgwick
+ * @brief Main header file for the Fusion library. This is the only file that
+ * needs to be included when using the library.
+ */
+
+#ifndef FUSION_H
+#define FUSION_H
+
+//------------------------------------------------------------------------------
+// Includes
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "FusionAhrs.h"
+#include "FusionAxes.h"
+#include "FusionCalibration.h"
+#include "FusionCompass.h"
+#include "FusionConvention.h"
+#include "FusionMath.h"
+#include "FusionOffset.h"
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+//------------------------------------------------------------------------------
+// End of file
diff --git a/src/Fusion/FusionAhrs.c b/src/Fusion/FusionAhrs.c
new file mode 100644
index 000000000..d6c1d0215
--- /dev/null
+++ b/src/Fusion/FusionAhrs.c
@@ -0,0 +1,542 @@
+/**
+ * @file FusionAhrs.c
+ * @author Seb Madgwick
+ * @brief AHRS algorithm to combine gyroscope, accelerometer, and magnetometer
+ * measurements into a single measurement of orientation relative to the Earth.
+ */
+
+//------------------------------------------------------------------------------
+// Includes
+
+#include "FusionAhrs.h"
+#include // FLT_MAX
+#include // atan2f, cosf, fabsf, powf, sinf
+
+//------------------------------------------------------------------------------
+// Definitions
+
+/**
+ * @brief Initial gain used during the initialisation.
+ */
+#define INITIAL_GAIN (10.0f)
+
+/**
+ * @brief Initialisation period in seconds.
+ */
+#define INITIALISATION_PERIOD (3.0f)
+
+//------------------------------------------------------------------------------
+// Function declarations
+
+static inline FusionVector HalfGravity(const FusionAhrs *const ahrs);
+
+static inline FusionVector HalfMagnetic(const FusionAhrs *const ahrs);
+
+static inline FusionVector Feedback(const FusionVector sensor, const FusionVector reference);
+
+static inline int Clamp(const int value, const int min, const int max);
+
+//------------------------------------------------------------------------------
+// Functions
+
+/**
+ * @brief Initialises the AHRS algorithm structure.
+ * @param ahrs AHRS algorithm structure.
+ */
+void FusionAhrsInitialise(FusionAhrs *const ahrs)
+{
+ const FusionAhrsSettings settings = {
+ .convention = FusionConventionNwu,
+ .gain = 0.5f,
+ .gyroscopeRange = 0.0f,
+ .accelerationRejection = 90.0f,
+ .magneticRejection = 90.0f,
+ .recoveryTriggerPeriod = 0,
+ };
+ FusionAhrsSetSettings(ahrs, &settings);
+ FusionAhrsReset(ahrs);
+}
+
+/**
+ * @brief Resets the AHRS algorithm. This is equivalent to reinitialising the
+ * algorithm while maintaining the current settings.
+ * @param ahrs AHRS algorithm structure.
+ */
+void FusionAhrsReset(FusionAhrs *const ahrs)
+{
+ ahrs->quaternion = FUSION_IDENTITY_QUATERNION;
+ ahrs->accelerometer = FUSION_VECTOR_ZERO;
+ ahrs->initialising = true;
+ ahrs->rampedGain = INITIAL_GAIN;
+ ahrs->angularRateRecovery = false;
+ ahrs->halfAccelerometerFeedback = FUSION_VECTOR_ZERO;
+ ahrs->halfMagnetometerFeedback = FUSION_VECTOR_ZERO;
+ ahrs->accelerometerIgnored = false;
+ ahrs->accelerationRecoveryTrigger = 0;
+ ahrs->accelerationRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod;
+ ahrs->magnetometerIgnored = false;
+ ahrs->magneticRecoveryTrigger = 0;
+ ahrs->magneticRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod;
+}
+
+/**
+ * @brief Sets the AHRS algorithm settings.
+ * @param ahrs AHRS algorithm structure.
+ * @param settings Settings.
+ */
+void FusionAhrsSetSettings(FusionAhrs *const ahrs, const FusionAhrsSettings *const settings)
+{
+ ahrs->settings.convention = settings->convention;
+ ahrs->settings.gain = settings->gain;
+ ahrs->settings.gyroscopeRange = settings->gyroscopeRange == 0.0f ? FLT_MAX : 0.98f * settings->gyroscopeRange;
+ ahrs->settings.accelerationRejection = settings->accelerationRejection == 0.0f
+ ? FLT_MAX
+ : powf(0.5f * sinf(FusionDegreesToRadians(settings->accelerationRejection)), 2);
+ ahrs->settings.magneticRejection =
+ settings->magneticRejection == 0.0f ? FLT_MAX : powf(0.5f * sinf(FusionDegreesToRadians(settings->magneticRejection)), 2);
+ ahrs->settings.recoveryTriggerPeriod = settings->recoveryTriggerPeriod;
+ ahrs->accelerationRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod;
+ ahrs->magneticRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod;
+ if ((settings->gain == 0.0f) ||
+ (settings->recoveryTriggerPeriod == 0)) { // disable acceleration and magnetic rejection features if gain is zero
+ ahrs->settings.accelerationRejection = FLT_MAX;
+ ahrs->settings.magneticRejection = FLT_MAX;
+ }
+ if (ahrs->initialising == false) {
+ ahrs->rampedGain = ahrs->settings.gain;
+ }
+ ahrs->rampedGainStep = (INITIAL_GAIN - ahrs->settings.gain) / INITIALISATION_PERIOD;
+}
+
+/**
+ * @brief Updates the AHRS algorithm using the gyroscope, accelerometer, and
+ * magnetometer measurements.
+ * @param ahrs AHRS algorithm structure.
+ * @param gyroscope Gyroscope measurement in degrees per second.
+ * @param accelerometer Accelerometer measurement in g.
+ * @param magnetometer Magnetometer measurement in arbitrary units.
+ * @param deltaTime Delta time in seconds.
+ */
+void FusionAhrsUpdate(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer,
+ const FusionVector magnetometer, const float deltaTime)
+{
+#define Q ahrs->quaternion.element
+
+ // Store accelerometer
+ ahrs->accelerometer = accelerometer;
+
+ // Reinitialise if gyroscope range exceeded
+ if ((fabsf(gyroscope.axis.x) > ahrs->settings.gyroscopeRange) || (fabsf(gyroscope.axis.y) > ahrs->settings.gyroscopeRange) ||
+ (fabsf(gyroscope.axis.z) > ahrs->settings.gyroscopeRange)) {
+ const FusionQuaternion quaternion = ahrs->quaternion;
+ FusionAhrsReset(ahrs);
+ ahrs->quaternion = quaternion;
+ ahrs->angularRateRecovery = true;
+ }
+
+ // Ramp down gain during initialisation
+ if (ahrs->initialising) {
+ ahrs->rampedGain -= ahrs->rampedGainStep * deltaTime;
+ if ((ahrs->rampedGain < ahrs->settings.gain) || (ahrs->settings.gain == 0.0f)) {
+ ahrs->rampedGain = ahrs->settings.gain;
+ ahrs->initialising = false;
+ ahrs->angularRateRecovery = false;
+ }
+ }
+
+ // Calculate direction of gravity indicated by algorithm
+ const FusionVector halfGravity = HalfGravity(ahrs);
+
+ // Calculate accelerometer feedback
+ FusionVector halfAccelerometerFeedback = FUSION_VECTOR_ZERO;
+ ahrs->accelerometerIgnored = true;
+ if (FusionVectorIsZero(accelerometer) == false) {
+
+ // Calculate accelerometer feedback scaled by 0.5
+ ahrs->halfAccelerometerFeedback = Feedback(FusionVectorNormalise(accelerometer), halfGravity);
+
+ // Don't ignore accelerometer if acceleration error below threshold
+ if (ahrs->initialising ||
+ ((FusionVectorMagnitudeSquared(ahrs->halfAccelerometerFeedback) <= ahrs->settings.accelerationRejection))) {
+ ahrs->accelerometerIgnored = false;
+ ahrs->accelerationRecoveryTrigger -= 9;
+ } else {
+ ahrs->accelerationRecoveryTrigger += 1;
+ }
+
+ // Don't ignore accelerometer during acceleration recovery
+ if (ahrs->accelerationRecoveryTrigger > ahrs->accelerationRecoveryTimeout) {
+ ahrs->accelerationRecoveryTimeout = 0;
+ ahrs->accelerometerIgnored = false;
+ } else {
+ ahrs->accelerationRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod;
+ }
+ ahrs->accelerationRecoveryTrigger = Clamp(ahrs->accelerationRecoveryTrigger, 0, ahrs->settings.recoveryTriggerPeriod);
+
+ // Apply accelerometer feedback
+ if (ahrs->accelerometerIgnored == false) {
+ halfAccelerometerFeedback = ahrs->halfAccelerometerFeedback;
+ }
+ }
+
+ // Calculate magnetometer feedback
+ FusionVector halfMagnetometerFeedback = FUSION_VECTOR_ZERO;
+ ahrs->magnetometerIgnored = true;
+ if (FusionVectorIsZero(magnetometer) == false) {
+
+ // Calculate direction of magnetic field indicated by algorithm
+ const FusionVector halfMagnetic = HalfMagnetic(ahrs);
+
+ // Calculate magnetometer feedback scaled by 0.5
+ ahrs->halfMagnetometerFeedback =
+ Feedback(FusionVectorNormalise(FusionVectorCrossProduct(halfGravity, magnetometer)), halfMagnetic);
+
+ // Don't ignore magnetometer if magnetic error below threshold
+ if (ahrs->initialising ||
+ ((FusionVectorMagnitudeSquared(ahrs->halfMagnetometerFeedback) <= ahrs->settings.magneticRejection))) {
+ ahrs->magnetometerIgnored = false;
+ ahrs->magneticRecoveryTrigger -= 9;
+ } else {
+ ahrs->magneticRecoveryTrigger += 1;
+ }
+
+ // Don't ignore magnetometer during magnetic recovery
+ if (ahrs->magneticRecoveryTrigger > ahrs->magneticRecoveryTimeout) {
+ ahrs->magneticRecoveryTimeout = 0;
+ ahrs->magnetometerIgnored = false;
+ } else {
+ ahrs->magneticRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod;
+ }
+ ahrs->magneticRecoveryTrigger = Clamp(ahrs->magneticRecoveryTrigger, 0, ahrs->settings.recoveryTriggerPeriod);
+
+ // Apply magnetometer feedback
+ if (ahrs->magnetometerIgnored == false) {
+ halfMagnetometerFeedback = ahrs->halfMagnetometerFeedback;
+ }
+ }
+
+ // Convert gyroscope to radians per second scaled by 0.5
+ const FusionVector halfGyroscope = FusionVectorMultiplyScalar(gyroscope, FusionDegreesToRadians(0.5f));
+
+ // Apply feedback to gyroscope
+ const FusionVector adjustedHalfGyroscope = FusionVectorAdd(
+ halfGyroscope,
+ FusionVectorMultiplyScalar(FusionVectorAdd(halfAccelerometerFeedback, halfMagnetometerFeedback), ahrs->rampedGain));
+
+ // Integrate rate of change of quaternion
+ ahrs->quaternion = FusionQuaternionAdd(
+ ahrs->quaternion,
+ FusionQuaternionMultiplyVector(ahrs->quaternion, FusionVectorMultiplyScalar(adjustedHalfGyroscope, deltaTime)));
+
+ // Normalise quaternion
+ ahrs->quaternion = FusionQuaternionNormalise(ahrs->quaternion);
+#undef Q
+}
+
+/**
+ * @brief Returns the direction of gravity scaled by 0.5.
+ * @param ahrs AHRS algorithm structure.
+ * @return Direction of gravity scaled by 0.5.
+ */
+static inline FusionVector HalfGravity(const FusionAhrs *const ahrs)
+{
+#define Q ahrs->quaternion.element
+ switch (ahrs->settings.convention) {
+ case FusionConventionNwu:
+ case FusionConventionEnu: {
+ const FusionVector halfGravity = {.axis = {
+ .x = Q.x * Q.z - Q.w * Q.y,
+ .y = Q.y * Q.z + Q.w * Q.x,
+ .z = Q.w * Q.w - 0.5f + Q.z * Q.z,
+ }}; // third column of transposed rotation matrix scaled by 0.5
+ return halfGravity;
+ }
+ case FusionConventionNed: {
+ const FusionVector halfGravity = {.axis = {
+ .x = Q.w * Q.y - Q.x * Q.z,
+ .y = -1.0f * (Q.y * Q.z + Q.w * Q.x),
+ .z = 0.5f - Q.w * Q.w - Q.z * Q.z,
+ }}; // third column of transposed rotation matrix scaled by -0.5
+ return halfGravity;
+ }
+ }
+ return FUSION_VECTOR_ZERO; // avoid compiler warning
+#undef Q
+}
+
+/**
+ * @brief Returns the direction of the magnetic field scaled by 0.5.
+ * @param ahrs AHRS algorithm structure.
+ * @return Direction of the magnetic field scaled by 0.5.
+ */
+static inline FusionVector HalfMagnetic(const FusionAhrs *const ahrs)
+{
+#define Q ahrs->quaternion.element
+ switch (ahrs->settings.convention) {
+ case FusionConventionNwu: {
+ const FusionVector halfMagnetic = {.axis = {
+ .x = Q.x * Q.y + Q.w * Q.z,
+ .y = Q.w * Q.w - 0.5f + Q.y * Q.y,
+ .z = Q.y * Q.z - Q.w * Q.x,
+ }}; // second column of transposed rotation matrix scaled by 0.5
+ return halfMagnetic;
+ }
+ case FusionConventionEnu: {
+ const FusionVector halfMagnetic = {.axis = {
+ .x = 0.5f - Q.w * Q.w - Q.x * Q.x,
+ .y = Q.w * Q.z - Q.x * Q.y,
+ .z = -1.0f * (Q.x * Q.z + Q.w * Q.y),
+ }}; // first column of transposed rotation matrix scaled by -0.5
+ return halfMagnetic;
+ }
+ case FusionConventionNed: {
+ const FusionVector halfMagnetic = {.axis = {
+ .x = -1.0f * (Q.x * Q.y + Q.w * Q.z),
+ .y = 0.5f - Q.w * Q.w - Q.y * Q.y,
+ .z = Q.w * Q.x - Q.y * Q.z,
+ }}; // second column of transposed rotation matrix scaled by -0.5
+ return halfMagnetic;
+ }
+ }
+ return FUSION_VECTOR_ZERO; // avoid compiler warning
+#undef Q
+}
+
+/**
+ * @brief Returns the feedback.
+ * @param sensor Sensor.
+ * @param reference Reference.
+ * @return Feedback.
+ */
+static inline FusionVector Feedback(const FusionVector sensor, const FusionVector reference)
+{
+ if (FusionVectorDotProduct(sensor, reference) < 0.0f) { // if error is >90 degrees
+ return FusionVectorNormalise(FusionVectorCrossProduct(sensor, reference));
+ }
+ return FusionVectorCrossProduct(sensor, reference);
+}
+
+/**
+ * @brief Returns a value limited to maximum and minimum.
+ * @param value Value.
+ * @param min Minimum value.
+ * @param max Maximum value.
+ * @return Value limited to maximum and minimum.
+ */
+static inline int Clamp(const int value, const int min, const int max)
+{
+ if (value < min) {
+ return min;
+ }
+ if (value > max) {
+ return max;
+ }
+ return value;
+}
+
+/**
+ * @brief Updates the AHRS algorithm using the gyroscope and accelerometer
+ * measurements only.
+ * @param ahrs AHRS algorithm structure.
+ * @param gyroscope Gyroscope measurement in degrees per second.
+ * @param accelerometer Accelerometer measurement in g.
+ * @param deltaTime Delta time in seconds.
+ */
+void FusionAhrsUpdateNoMagnetometer(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer,
+ const float deltaTime)
+{
+
+ // Update AHRS algorithm
+ FusionAhrsUpdate(ahrs, gyroscope, accelerometer, FUSION_VECTOR_ZERO, deltaTime);
+
+ // Zero heading during initialisation
+ if (ahrs->initialising) {
+ FusionAhrsSetHeading(ahrs, 0.0f);
+ }
+}
+
+/**
+ * @brief Updates the AHRS algorithm using the gyroscope, accelerometer, and
+ * heading measurements.
+ * @param ahrs AHRS algorithm structure.
+ * @param gyroscope Gyroscope measurement in degrees per second.
+ * @param accelerometer Accelerometer measurement in g.
+ * @param heading Heading measurement in degrees.
+ * @param deltaTime Delta time in seconds.
+ */
+void FusionAhrsUpdateExternalHeading(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer,
+ const float heading, const float deltaTime)
+{
+#define Q ahrs->quaternion.element
+
+ // Calculate roll
+ const float roll = atan2f(Q.w * Q.x + Q.y * Q.z, 0.5f - Q.y * Q.y - Q.x * Q.x);
+
+ // Calculate magnetometer
+ const float headingRadians = FusionDegreesToRadians(heading);
+ const float sinHeadingRadians = sinf(headingRadians);
+ const FusionVector magnetometer = {.axis = {
+ .x = cosf(headingRadians),
+ .y = -1.0f * cosf(roll) * sinHeadingRadians,
+ .z = sinHeadingRadians * sinf(roll),
+ }};
+
+ // Update AHRS algorithm
+ FusionAhrsUpdate(ahrs, gyroscope, accelerometer, magnetometer, deltaTime);
+#undef Q
+}
+
+/**
+ * @brief Returns the quaternion describing the sensor relative to the Earth.
+ * @param ahrs AHRS algorithm structure.
+ * @return Quaternion describing the sensor relative to the Earth.
+ */
+FusionQuaternion FusionAhrsGetQuaternion(const FusionAhrs *const ahrs)
+{
+ return ahrs->quaternion;
+}
+
+/**
+ * @brief Sets the quaternion describing the sensor relative to the Earth.
+ * @param ahrs AHRS algorithm structure.
+ * @param quaternion Quaternion describing the sensor relative to the Earth.
+ */
+void FusionAhrsSetQuaternion(FusionAhrs *const ahrs, const FusionQuaternion quaternion)
+{
+ ahrs->quaternion = quaternion;
+}
+
+/**
+ * @brief Returns the linear acceleration measurement equal to the accelerometer
+ * measurement with the 1 g of gravity removed.
+ * @param ahrs AHRS algorithm structure.
+ * @return Linear acceleration measurement in g.
+ */
+FusionVector FusionAhrsGetLinearAcceleration(const FusionAhrs *const ahrs)
+{
+#define Q ahrs->quaternion.element
+
+ // Calculate gravity in the sensor coordinate frame
+ const FusionVector gravity = {.axis = {
+ .x = 2.0f * (Q.x * Q.z - Q.w * Q.y),
+ .y = 2.0f * (Q.y * Q.z + Q.w * Q.x),
+ .z = 2.0f * (Q.w * Q.w - 0.5f + Q.z * Q.z),
+ }}; // third column of transposed rotation matrix
+
+ // Remove gravity from accelerometer measurement
+ switch (ahrs->settings.convention) {
+ case FusionConventionNwu:
+ case FusionConventionEnu: {
+ return FusionVectorSubtract(ahrs->accelerometer, gravity);
+ }
+ case FusionConventionNed: {
+ return FusionVectorAdd(ahrs->accelerometer, gravity);
+ }
+ }
+ return FUSION_VECTOR_ZERO; // avoid compiler warning
+#undef Q
+}
+
+/**
+ * @brief Returns the Earth acceleration measurement equal to accelerometer
+ * measurement in the Earth coordinate frame with the 1 g of gravity removed.
+ * @param ahrs AHRS algorithm structure.
+ * @return Earth acceleration measurement in g.
+ */
+FusionVector FusionAhrsGetEarthAcceleration(const FusionAhrs *const ahrs)
+{
+#define Q ahrs->quaternion.element
+#define A ahrs->accelerometer.axis
+
+ // Calculate accelerometer measurement in the Earth coordinate frame
+ const float qwqw = Q.w * Q.w; // calculate common terms to avoid repeated operations
+ const float qwqx = Q.w * Q.x;
+ const float qwqy = Q.w * Q.y;
+ const float qwqz = Q.w * Q.z;
+ const float qxqy = Q.x * Q.y;
+ const float qxqz = Q.x * Q.z;
+ const float qyqz = Q.y * Q.z;
+ FusionVector accelerometer = {.axis = {
+ .x = 2.0f * ((qwqw - 0.5f + Q.x * Q.x) * A.x + (qxqy - qwqz) * A.y + (qxqz + qwqy) * A.z),
+ .y = 2.0f * ((qxqy + qwqz) * A.x + (qwqw - 0.5f + Q.y * Q.y) * A.y + (qyqz - qwqx) * A.z),
+ .z = 2.0f * ((qxqz - qwqy) * A.x + (qyqz + qwqx) * A.y + (qwqw - 0.5f + Q.z * Q.z) * A.z),
+ }}; // rotation matrix multiplied with the accelerometer
+
+ // Remove gravity from accelerometer measurement
+ switch (ahrs->settings.convention) {
+ case FusionConventionNwu:
+ case FusionConventionEnu:
+ accelerometer.axis.z -= 1.0f;
+ break;
+ case FusionConventionNed:
+ accelerometer.axis.z += 1.0f;
+ break;
+ }
+ return accelerometer;
+#undef Q
+#undef A
+}
+
+/**
+ * @brief Returns the AHRS algorithm internal states.
+ * @param ahrs AHRS algorithm structure.
+ * @return AHRS algorithm internal states.
+ */
+FusionAhrsInternalStates FusionAhrsGetInternalStates(const FusionAhrs *const ahrs)
+{
+ const FusionAhrsInternalStates internalStates = {
+ .accelerationError = FusionRadiansToDegrees(FusionAsin(2.0f * FusionVectorMagnitude(ahrs->halfAccelerometerFeedback))),
+ .accelerometerIgnored = ahrs->accelerometerIgnored,
+ .accelerationRecoveryTrigger =
+ ahrs->settings.recoveryTriggerPeriod == 0
+ ? 0.0f
+ : (float)ahrs->accelerationRecoveryTrigger / (float)ahrs->settings.recoveryTriggerPeriod,
+ .magneticError = FusionRadiansToDegrees(FusionAsin(2.0f * FusionVectorMagnitude(ahrs->halfMagnetometerFeedback))),
+ .magnetometerIgnored = ahrs->magnetometerIgnored,
+ .magneticRecoveryTrigger = ahrs->settings.recoveryTriggerPeriod == 0
+ ? 0.0f
+ : (float)ahrs->magneticRecoveryTrigger / (float)ahrs->settings.recoveryTriggerPeriod,
+ };
+ return internalStates;
+}
+
+/**
+ * @brief Returns the AHRS algorithm flags.
+ * @param ahrs AHRS algorithm structure.
+ * @return AHRS algorithm flags.
+ */
+FusionAhrsFlags FusionAhrsGetFlags(const FusionAhrs *const ahrs)
+{
+ const FusionAhrsFlags flags = {
+ .initialising = ahrs->initialising,
+ .angularRateRecovery = ahrs->angularRateRecovery,
+ .accelerationRecovery = ahrs->accelerationRecoveryTrigger > ahrs->accelerationRecoveryTimeout,
+ .magneticRecovery = ahrs->magneticRecoveryTrigger > ahrs->magneticRecoveryTimeout,
+ };
+ return flags;
+}
+
+/**
+ * @brief Sets the heading of the orientation measurement provided by the AHRS
+ * algorithm. This function can be used to reset drift in heading when the AHRS
+ * algorithm is being used without a magnetometer.
+ * @param ahrs AHRS algorithm structure.
+ * @param heading Heading angle in degrees.
+ */
+void FusionAhrsSetHeading(FusionAhrs *const ahrs, const float heading)
+{
+#define Q ahrs->quaternion.element
+ const float yaw = atan2f(Q.w * Q.z + Q.x * Q.y, 0.5f - Q.y * Q.y - Q.z * Q.z);
+ const float halfYawMinusHeading = 0.5f * (yaw - FusionDegreesToRadians(heading));
+ const FusionQuaternion rotation = {.element = {
+ .w = cosf(halfYawMinusHeading),
+ .x = 0.0f,
+ .y = 0.0f,
+ .z = -1.0f * sinf(halfYawMinusHeading),
+ }};
+ ahrs->quaternion = FusionQuaternionMultiply(rotation, ahrs->quaternion);
+#undef Q
+}
+
+//------------------------------------------------------------------------------
+// End of file
diff --git a/src/Fusion/FusionAhrs.h b/src/Fusion/FusionAhrs.h
new file mode 100644
index 000000000..aa2326e43
--- /dev/null
+++ b/src/Fusion/FusionAhrs.h
@@ -0,0 +1,112 @@
+/**
+ * @file FusionAhrs.h
+ * @author Seb Madgwick
+ * @brief AHRS algorithm to combine gyroscope, accelerometer, and magnetometer
+ * measurements into a single measurement of orientation relative to the Earth.
+ */
+
+#ifndef FUSION_AHRS_H
+#define FUSION_AHRS_H
+
+//------------------------------------------------------------------------------
+// Includes
+
+#include "FusionConvention.h"
+#include "FusionMath.h"
+#include
+
+//------------------------------------------------------------------------------
+// Definitions
+
+/**
+ * @brief AHRS algorithm settings.
+ */
+typedef struct {
+ FusionConvention convention;
+ float gain;
+ float gyroscopeRange;
+ float accelerationRejection;
+ float magneticRejection;
+ unsigned int recoveryTriggerPeriod;
+} FusionAhrsSettings;
+
+/**
+ * @brief AHRS algorithm structure. Structure members are used internally and
+ * must not be accessed by the application.
+ */
+typedef struct {
+ FusionAhrsSettings settings;
+ FusionQuaternion quaternion;
+ FusionVector accelerometer;
+ bool initialising;
+ float rampedGain;
+ float rampedGainStep;
+ bool angularRateRecovery;
+ FusionVector halfAccelerometerFeedback;
+ FusionVector halfMagnetometerFeedback;
+ bool accelerometerIgnored;
+ int accelerationRecoveryTrigger;
+ int accelerationRecoveryTimeout;
+ bool magnetometerIgnored;
+ int magneticRecoveryTrigger;
+ int magneticRecoveryTimeout;
+} FusionAhrs;
+
+/**
+ * @brief AHRS algorithm internal states.
+ */
+typedef struct {
+ float accelerationError;
+ bool accelerometerIgnored;
+ float accelerationRecoveryTrigger;
+ float magneticError;
+ bool magnetometerIgnored;
+ float magneticRecoveryTrigger;
+} FusionAhrsInternalStates;
+
+/**
+ * @brief AHRS algorithm flags.
+ */
+typedef struct {
+ bool initialising;
+ bool angularRateRecovery;
+ bool accelerationRecovery;
+ bool magneticRecovery;
+} FusionAhrsFlags;
+
+//------------------------------------------------------------------------------
+// Function declarations
+
+void FusionAhrsInitialise(FusionAhrs *const ahrs);
+
+void FusionAhrsReset(FusionAhrs *const ahrs);
+
+void FusionAhrsSetSettings(FusionAhrs *const ahrs, const FusionAhrsSettings *const settings);
+
+void FusionAhrsUpdate(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer,
+ const FusionVector magnetometer, const float deltaTime);
+
+void FusionAhrsUpdateNoMagnetometer(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer,
+ const float deltaTime);
+
+void FusionAhrsUpdateExternalHeading(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer,
+ const float heading, const float deltaTime);
+
+FusionQuaternion FusionAhrsGetQuaternion(const FusionAhrs *const ahrs);
+
+void FusionAhrsSetQuaternion(FusionAhrs *const ahrs, const FusionQuaternion quaternion);
+
+FusionVector FusionAhrsGetLinearAcceleration(const FusionAhrs *const ahrs);
+
+FusionVector FusionAhrsGetEarthAcceleration(const FusionAhrs *const ahrs);
+
+FusionAhrsInternalStates FusionAhrsGetInternalStates(const FusionAhrs *const ahrs);
+
+FusionAhrsFlags FusionAhrsGetFlags(const FusionAhrs *const ahrs);
+
+void FusionAhrsSetHeading(FusionAhrs *const ahrs, const float heading);
+
+#endif
+
+//------------------------------------------------------------------------------
+// End of file
diff --git a/src/Fusion/FusionAxes.h b/src/Fusion/FusionAxes.h
new file mode 100644
index 000000000..9673c88ff
--- /dev/null
+++ b/src/Fusion/FusionAxes.h
@@ -0,0 +1,188 @@
+/**
+ * @file FusionAxes.h
+ * @author Seb Madgwick
+ * @brief Swaps sensor axes for alignment with the body axes.
+ */
+
+#ifndef FUSION_AXES_H
+#define FUSION_AXES_H
+
+//------------------------------------------------------------------------------
+// Includes
+
+#include "FusionMath.h"
+
+//------------------------------------------------------------------------------
+// Definitions
+
+/**
+ * @brief Axes alignment describing the sensor axes relative to the body axes.
+ * For example, if the body X axis is aligned with the sensor Y axis and the
+ * body Y axis is aligned with sensor X axis but pointing the opposite direction
+ * then alignment is +Y-X+Z.
+ */
+typedef enum {
+ FusionAxesAlignmentPXPYPZ, /* +X+Y+Z */
+ FusionAxesAlignmentPXNZPY, /* +X-Z+Y */
+ FusionAxesAlignmentPXNYNZ, /* +X-Y-Z */
+ FusionAxesAlignmentPXPZNY, /* +X+Z-Y */
+ FusionAxesAlignmentNXPYNZ, /* -X+Y-Z */
+ FusionAxesAlignmentNXPZPY, /* -X+Z+Y */
+ FusionAxesAlignmentNXNYPZ, /* -X-Y+Z */
+ FusionAxesAlignmentNXNZNY, /* -X-Z-Y */
+ FusionAxesAlignmentPYNXPZ, /* +Y-X+Z */
+ FusionAxesAlignmentPYNZNX, /* +Y-Z-X */
+ FusionAxesAlignmentPYPXNZ, /* +Y+X-Z */
+ FusionAxesAlignmentPYPZPX, /* +Y+Z+X */
+ FusionAxesAlignmentNYPXPZ, /* -Y+X+Z */
+ FusionAxesAlignmentNYNZPX, /* -Y-Z+X */
+ FusionAxesAlignmentNYNXNZ, /* -Y-X-Z */
+ FusionAxesAlignmentNYPZNX, /* -Y+Z-X */
+ FusionAxesAlignmentPZPYNX, /* +Z+Y-X */
+ FusionAxesAlignmentPZPXPY, /* +Z+X+Y */
+ FusionAxesAlignmentPZNYPX, /* +Z-Y+X */
+ FusionAxesAlignmentPZNXNY, /* +Z-X-Y */
+ FusionAxesAlignmentNZPYPX, /* -Z+Y+X */
+ FusionAxesAlignmentNZNXPY, /* -Z-X+Y */
+ FusionAxesAlignmentNZNYNX, /* -Z-Y-X */
+ FusionAxesAlignmentNZPXNY, /* -Z+X-Y */
+} FusionAxesAlignment;
+
+//------------------------------------------------------------------------------
+// Inline functions
+
+/**
+ * @brief Swaps sensor axes for alignment with the body axes.
+ * @param sensor Sensor axes.
+ * @param alignment Axes alignment.
+ * @return Sensor axes aligned with the body axes.
+ */
+static inline FusionVector FusionAxesSwap(const FusionVector sensor, const FusionAxesAlignment alignment)
+{
+ FusionVector result;
+ switch (alignment) {
+ case FusionAxesAlignmentPXPYPZ:
+ break;
+ case FusionAxesAlignmentPXNZPY:
+ result.axis.x = +sensor.axis.x;
+ result.axis.y = -sensor.axis.z;
+ result.axis.z = +sensor.axis.y;
+ return result;
+ case FusionAxesAlignmentPXNYNZ:
+ result.axis.x = +sensor.axis.x;
+ result.axis.y = -sensor.axis.y;
+ result.axis.z = -sensor.axis.z;
+ return result;
+ case FusionAxesAlignmentPXPZNY:
+ result.axis.x = +sensor.axis.x;
+ result.axis.y = +sensor.axis.z;
+ result.axis.z = -sensor.axis.y;
+ return result;
+ case FusionAxesAlignmentNXPYNZ:
+ result.axis.x = -sensor.axis.x;
+ result.axis.y = +sensor.axis.y;
+ result.axis.z = -sensor.axis.z;
+ return result;
+ case FusionAxesAlignmentNXPZPY:
+ result.axis.x = -sensor.axis.x;
+ result.axis.y = +sensor.axis.z;
+ result.axis.z = +sensor.axis.y;
+ return result;
+ case FusionAxesAlignmentNXNYPZ:
+ result.axis.x = -sensor.axis.x;
+ result.axis.y = -sensor.axis.y;
+ result.axis.z = +sensor.axis.z;
+ return result;
+ case FusionAxesAlignmentNXNZNY:
+ result.axis.x = -sensor.axis.x;
+ result.axis.y = -sensor.axis.z;
+ result.axis.z = -sensor.axis.y;
+ return result;
+ case FusionAxesAlignmentPYNXPZ:
+ result.axis.x = +sensor.axis.y;
+ result.axis.y = -sensor.axis.x;
+ result.axis.z = +sensor.axis.z;
+ return result;
+ case FusionAxesAlignmentPYNZNX:
+ result.axis.x = +sensor.axis.y;
+ result.axis.y = -sensor.axis.z;
+ result.axis.z = -sensor.axis.x;
+ return result;
+ case FusionAxesAlignmentPYPXNZ:
+ result.axis.x = +sensor.axis.y;
+ result.axis.y = +sensor.axis.x;
+ result.axis.z = -sensor.axis.z;
+ return result;
+ case FusionAxesAlignmentPYPZPX:
+ result.axis.x = +sensor.axis.y;
+ result.axis.y = +sensor.axis.z;
+ result.axis.z = +sensor.axis.x;
+ return result;
+ case FusionAxesAlignmentNYPXPZ:
+ result.axis.x = -sensor.axis.y;
+ result.axis.y = +sensor.axis.x;
+ result.axis.z = +sensor.axis.z;
+ return result;
+ case FusionAxesAlignmentNYNZPX:
+ result.axis.x = -sensor.axis.y;
+ result.axis.y = -sensor.axis.z;
+ result.axis.z = +sensor.axis.x;
+ return result;
+ case FusionAxesAlignmentNYNXNZ:
+ result.axis.x = -sensor.axis.y;
+ result.axis.y = -sensor.axis.x;
+ result.axis.z = -sensor.axis.z;
+ return result;
+ case FusionAxesAlignmentNYPZNX:
+ result.axis.x = -sensor.axis.y;
+ result.axis.y = +sensor.axis.z;
+ result.axis.z = -sensor.axis.x;
+ return result;
+ case FusionAxesAlignmentPZPYNX:
+ result.axis.x = +sensor.axis.z;
+ result.axis.y = +sensor.axis.y;
+ result.axis.z = -sensor.axis.x;
+ return result;
+ case FusionAxesAlignmentPZPXPY:
+ result.axis.x = +sensor.axis.z;
+ result.axis.y = +sensor.axis.x;
+ result.axis.z = +sensor.axis.y;
+ return result;
+ case FusionAxesAlignmentPZNYPX:
+ result.axis.x = +sensor.axis.z;
+ result.axis.y = -sensor.axis.y;
+ result.axis.z = +sensor.axis.x;
+ return result;
+ case FusionAxesAlignmentPZNXNY:
+ result.axis.x = +sensor.axis.z;
+ result.axis.y = -sensor.axis.x;
+ result.axis.z = -sensor.axis.y;
+ return result;
+ case FusionAxesAlignmentNZPYPX:
+ result.axis.x = -sensor.axis.z;
+ result.axis.y = +sensor.axis.y;
+ result.axis.z = +sensor.axis.x;
+ return result;
+ case FusionAxesAlignmentNZNXPY:
+ result.axis.x = -sensor.axis.z;
+ result.axis.y = -sensor.axis.x;
+ result.axis.z = +sensor.axis.y;
+ return result;
+ case FusionAxesAlignmentNZNYNX:
+ result.axis.x = -sensor.axis.z;
+ result.axis.y = -sensor.axis.y;
+ result.axis.z = -sensor.axis.x;
+ return result;
+ case FusionAxesAlignmentNZPXNY:
+ result.axis.x = -sensor.axis.z;
+ result.axis.y = +sensor.axis.x;
+ result.axis.z = -sensor.axis.y;
+ return result;
+ }
+ return sensor; // avoid compiler warning
+}
+
+#endif
+
+//------------------------------------------------------------------------------
+// End of file
diff --git a/src/Fusion/FusionCalibration.h b/src/Fusion/FusionCalibration.h
new file mode 100644
index 000000000..be7102b73
--- /dev/null
+++ b/src/Fusion/FusionCalibration.h
@@ -0,0 +1,49 @@
+/**
+ * @file FusionCalibration.h
+ * @author Seb Madgwick
+ * @brief Gyroscope, accelerometer, and magnetometer calibration models.
+ */
+
+#ifndef FUSION_CALIBRATION_H
+#define FUSION_CALIBRATION_H
+
+//------------------------------------------------------------------------------
+// Includes
+
+#include "FusionMath.h"
+
+//------------------------------------------------------------------------------
+// Inline functions
+
+/**
+ * @brief Gyroscope and accelerometer calibration model.
+ * @param uncalibrated Uncalibrated measurement.
+ * @param misalignment Misalignment matrix.
+ * @param sensitivity Sensitivity.
+ * @param offset Offset.
+ * @return Calibrated measurement.
+ */
+static inline FusionVector FusionCalibrationInertial(const FusionVector uncalibrated, const FusionMatrix misalignment,
+ const FusionVector sensitivity, const FusionVector offset)
+{
+ return FusionMatrixMultiplyVector(misalignment,
+ FusionVectorHadamardProduct(FusionVectorSubtract(uncalibrated, offset), sensitivity));
+}
+
+/**
+ * @brief Magnetometer calibration model.
+ * @param uncalibrated Uncalibrated measurement.
+ * @param softIronMatrix Soft-iron matrix.
+ * @param hardIronOffset Hard-iron offset.
+ * @return Calibrated measurement.
+ */
+static inline FusionVector FusionCalibrationMagnetic(const FusionVector uncalibrated, const FusionMatrix softIronMatrix,
+ const FusionVector hardIronOffset)
+{
+ return FusionMatrixMultiplyVector(softIronMatrix, FusionVectorSubtract(uncalibrated, hardIronOffset));
+}
+
+#endif
+
+//------------------------------------------------------------------------------
+// End of file
diff --git a/src/Fusion/FusionCompass.c b/src/Fusion/FusionCompass.c
new file mode 100644
index 000000000..6a6f9591a
--- /dev/null
+++ b/src/Fusion/FusionCompass.c
@@ -0,0 +1,51 @@
+/**
+ * @file FusionCompass.c
+ * @author Seb Madgwick
+ * @brief Tilt-compensated compass to calculate the magnetic heading using
+ * accelerometer and magnetometer measurements.
+ */
+
+//------------------------------------------------------------------------------
+// Includes
+
+#include "FusionCompass.h"
+#include "FusionAxes.h"
+#include // atan2f
+
+//------------------------------------------------------------------------------
+// Functions
+
+/**
+ * @brief Calculates the magnetic heading.
+ * @param convention Earth axes convention.
+ * @param accelerometer Accelerometer measurement in any calibrated units.
+ * @param magnetometer Magnetometer measurement in any calibrated units.
+ * @return Heading angle in degrees.
+ */
+float FusionCompassCalculateHeading(const FusionConvention convention, const FusionVector accelerometer,
+ const FusionVector magnetometer)
+{
+ switch (convention) {
+ case FusionConventionNwu: {
+ const FusionVector west = FusionVectorNormalise(FusionVectorCrossProduct(accelerometer, magnetometer));
+ const FusionVector north = FusionVectorNormalise(FusionVectorCrossProduct(west, accelerometer));
+ return FusionRadiansToDegrees(atan2f(west.axis.x, north.axis.x));
+ }
+ case FusionConventionEnu: {
+ const FusionVector west = FusionVectorNormalise(FusionVectorCrossProduct(accelerometer, magnetometer));
+ const FusionVector north = FusionVectorNormalise(FusionVectorCrossProduct(west, accelerometer));
+ const FusionVector east = FusionVectorMultiplyScalar(west, -1.0f);
+ return FusionRadiansToDegrees(atan2f(north.axis.x, east.axis.x));
+ }
+ case FusionConventionNed: {
+ const FusionVector up = FusionVectorMultiplyScalar(accelerometer, -1.0f);
+ const FusionVector west = FusionVectorNormalise(FusionVectorCrossProduct(up, magnetometer));
+ const FusionVector north = FusionVectorNormalise(FusionVectorCrossProduct(west, up));
+ return FusionRadiansToDegrees(atan2f(west.axis.x, north.axis.x));
+ }
+ }
+ return 0; // avoid compiler warning
+}
+
+//------------------------------------------------------------------------------
+// End of file
diff --git a/src/Fusion/FusionCompass.h b/src/Fusion/FusionCompass.h
new file mode 100644
index 000000000..a3d0b466a
--- /dev/null
+++ b/src/Fusion/FusionCompass.h
@@ -0,0 +1,26 @@
+/**
+ * @file FusionCompass.h
+ * @author Seb Madgwick
+ * @brief Tilt-compensated compass to calculate the magnetic heading using
+ * accelerometer and magnetometer measurements.
+ */
+
+#ifndef FUSION_COMPASS_H
+#define FUSION_COMPASS_H
+
+//------------------------------------------------------------------------------
+// Includes
+
+#include "FusionConvention.h"
+#include "FusionMath.h"
+
+//------------------------------------------------------------------------------
+// Function declarations
+
+float FusionCompassCalculateHeading(const FusionConvention convention, const FusionVector accelerometer,
+ const FusionVector magnetometer);
+
+#endif
+
+//------------------------------------------------------------------------------
+// End of file
diff --git a/src/Fusion/FusionConvention.h b/src/Fusion/FusionConvention.h
new file mode 100644
index 000000000..0b0d43adc
--- /dev/null
+++ b/src/Fusion/FusionConvention.h
@@ -0,0 +1,25 @@
+/**
+ * @file FusionConvention.h
+ * @author Seb Madgwick
+ * @brief Earth axes convention.
+ */
+
+#ifndef FUSION_CONVENTION_H
+#define FUSION_CONVENTION_H
+
+//------------------------------------------------------------------------------
+// Definitions
+
+/**
+ * @brief Earth axes convention.
+ */
+typedef enum {
+ FusionConventionNwu, /* North-West-Up */
+ FusionConventionEnu, /* East-North-Up */
+ FusionConventionNed, /* North-East-Down */
+} FusionConvention;
+
+#endif
+
+//------------------------------------------------------------------------------
+// End of file
diff --git a/src/Fusion/FusionMath.h b/src/Fusion/FusionMath.h
new file mode 100644
index 000000000..c3fc34b2d
--- /dev/null
+++ b/src/Fusion/FusionMath.h
@@ -0,0 +1,503 @@
+/**
+ * @file FusionMath.h
+ * @author Seb Madgwick
+ * @brief Math library.
+ */
+
+#ifndef FUSION_MATH_H
+#define FUSION_MATH_H
+
+//------------------------------------------------------------------------------
+// Includes
+
+#include // M_PI, sqrtf, atan2f, asinf
+#include
+#include
+
+//------------------------------------------------------------------------------
+// Definitions
+
+/**
+ * @brief 3D vector.
+ */
+typedef union {
+ float array[3];
+
+ struct {
+ float x;
+ float y;
+ float z;
+ } axis;
+} FusionVector;
+
+/**
+ * @brief Quaternion.
+ */
+typedef union {
+ float array[4];
+
+ struct {
+ float w;
+ float x;
+ float y;
+ float z;
+ } element;
+} FusionQuaternion;
+
+/**
+ * @brief 3x3 matrix in row-major order.
+ * See http://en.wikipedia.org/wiki/Row-major_order
+ */
+typedef union {
+ float array[3][3];
+
+ struct {
+ float xx;
+ float xy;
+ float xz;
+ float yx;
+ float yy;
+ float yz;
+ float zx;
+ float zy;
+ float zz;
+ } element;
+} FusionMatrix;
+
+/**
+ * @brief Euler angles. Roll, pitch, and yaw correspond to rotations around
+ * X, Y, and Z respectively.
+ */
+typedef union {
+ float array[3];
+
+ struct {
+ float roll;
+ float pitch;
+ float yaw;
+ } angle;
+} FusionEuler;
+
+/**
+ * @brief Vector of zeros.
+ */
+#define FUSION_VECTOR_ZERO ((FusionVector){.array = {0.0f, 0.0f, 0.0f}})
+
+/**
+ * @brief Vector of ones.
+ */
+#define FUSION_VECTOR_ONES ((FusionVector){.array = {1.0f, 1.0f, 1.0f}})
+
+/**
+ * @brief Identity quaternion.
+ */
+#define FUSION_IDENTITY_QUATERNION ((FusionQuaternion){.array = {1.0f, 0.0f, 0.0f, 0.0f}})
+
+/**
+ * @brief Identity matrix.
+ */
+#define FUSION_IDENTITY_MATRIX ((FusionMatrix){.array = {{1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}})
+
+/**
+ * @brief Euler angles of zero.
+ */
+#define FUSION_EULER_ZERO ((FusionEuler){.array = {0.0f, 0.0f, 0.0f}})
+
+/**
+ * @brief Pi. May not be defined in math.h.
+ */
+#ifndef M_PI
+#define M_PI (3.14159265358979323846)
+#endif
+
+/**
+ * @brief Include this definition or add as a preprocessor definition to use
+ * normal square root operations.
+ */
+// #define FUSION_USE_NORMAL_SQRT
+
+//------------------------------------------------------------------------------
+// Inline functions - Degrees and radians conversion
+
+/**
+ * @brief Converts degrees to radians.
+ * @param degrees Degrees.
+ * @return Radians.
+ */
+static inline float FusionDegreesToRadians(const float degrees)
+{
+ return degrees * ((float)M_PI / 180.0f);
+}
+
+/**
+ * @brief Converts radians to degrees.
+ * @param radians Radians.
+ * @return Degrees.
+ */
+static inline float FusionRadiansToDegrees(const float radians)
+{
+ return radians * (180.0f / (float)M_PI);
+}
+
+//------------------------------------------------------------------------------
+// Inline functions - Arc sine
+
+/**
+ * @brief Returns the arc sine of the value.
+ * @param value Value.
+ * @return Arc sine of the value.
+ */
+static inline float FusionAsin(const float value)
+{
+ if (value <= -1.0f) {
+ return (float)M_PI / -2.0f;
+ }
+ if (value >= 1.0f) {
+ return (float)M_PI / 2.0f;
+ }
+ return asinf(value);
+}
+
+//------------------------------------------------------------------------------
+// Inline functions - Fast inverse square root
+
+#ifndef FUSION_USE_NORMAL_SQRT
+
+/**
+ * @brief Calculates the reciprocal of the square root.
+ * See https://pizer.wordpress.com/2008/10/12/fast-inverse-square-root/
+ * @param x Operand.
+ * @return Reciprocal of the square root of x.
+ */
+static inline float FusionFastInverseSqrt(const float x)
+{
+
+ typedef union {
+ float f;
+ int32_t i;
+ } Union32;
+
+ Union32 union32 = {.f = x};
+ union32.i = 0x5F1F1412 - (union32.i >> 1);
+ return union32.f * (1.69000231f - 0.714158168f * x * union32.f * union32.f);
+}
+
+#endif
+
+//------------------------------------------------------------------------------
+// Inline functions - Vector operations
+
+/**
+ * @brief Returns true if the vector is zero.
+ * @param vector Vector.
+ * @return True if the vector is zero.
+ */
+static inline bool FusionVectorIsZero(const FusionVector vector)
+{
+ return (vector.axis.x == 0.0f) && (vector.axis.y == 0.0f) && (vector.axis.z == 0.0f);
+}
+
+/**
+ * @brief Returns the sum of two vectors.
+ * @param vectorA Vector A.
+ * @param vectorB Vector B.
+ * @return Sum of two vectors.
+ */
+static inline FusionVector FusionVectorAdd(const FusionVector vectorA, const FusionVector vectorB)
+{
+ const FusionVector result = {.axis = {
+ .x = vectorA.axis.x + vectorB.axis.x,
+ .y = vectorA.axis.y + vectorB.axis.y,
+ .z = vectorA.axis.z + vectorB.axis.z,
+ }};
+ return result;
+}
+
+/**
+ * @brief Returns vector B subtracted from vector A.
+ * @param vectorA Vector A.
+ * @param vectorB Vector B.
+ * @return Vector B subtracted from vector A.
+ */
+static inline FusionVector FusionVectorSubtract(const FusionVector vectorA, const FusionVector vectorB)
+{
+ const FusionVector result = {.axis = {
+ .x = vectorA.axis.x - vectorB.axis.x,
+ .y = vectorA.axis.y - vectorB.axis.y,
+ .z = vectorA.axis.z - vectorB.axis.z,
+ }};
+ return result;
+}
+
+/**
+ * @brief Returns the sum of the elements.
+ * @param vector Vector.
+ * @return Sum of the elements.
+ */
+static inline float FusionVectorSum(const FusionVector vector)
+{
+ return vector.axis.x + vector.axis.y + vector.axis.z;
+}
+
+/**
+ * @brief Returns the multiplication of a vector by a scalar.
+ * @param vector Vector.
+ * @param scalar Scalar.
+ * @return Multiplication of a vector by a scalar.
+ */
+static inline FusionVector FusionVectorMultiplyScalar(const FusionVector vector, const float scalar)
+{
+ const FusionVector result = {.axis = {
+ .x = vector.axis.x * scalar,
+ .y = vector.axis.y * scalar,
+ .z = vector.axis.z * scalar,
+ }};
+ return result;
+}
+
+/**
+ * @brief Calculates the Hadamard product (element-wise multiplication).
+ * @param vectorA Vector A.
+ * @param vectorB Vector B.
+ * @return Hadamard product.
+ */
+static inline FusionVector FusionVectorHadamardProduct(const FusionVector vectorA, const FusionVector vectorB)
+{
+ const FusionVector result = {.axis = {
+ .x = vectorA.axis.x * vectorB.axis.x,
+ .y = vectorA.axis.y * vectorB.axis.y,
+ .z = vectorA.axis.z * vectorB.axis.z,
+ }};
+ return result;
+}
+
+/**
+ * @brief Returns the cross product.
+ * @param vectorA Vector A.
+ * @param vectorB Vector B.
+ * @return Cross product.
+ */
+static inline FusionVector FusionVectorCrossProduct(const FusionVector vectorA, const FusionVector vectorB)
+{
+#define A vectorA.axis
+#define B vectorB.axis
+ const FusionVector result = {.axis = {
+ .x = A.y * B.z - A.z * B.y,
+ .y = A.z * B.x - A.x * B.z,
+ .z = A.x * B.y - A.y * B.x,
+ }};
+ return result;
+#undef A
+#undef B
+}
+
+/**
+ * @brief Returns the dot product.
+ * @param vectorA Vector A.
+ * @param vectorB Vector B.
+ * @return Dot product.
+ */
+static inline float FusionVectorDotProduct(const FusionVector vectorA, const FusionVector vectorB)
+{
+ return FusionVectorSum(FusionVectorHadamardProduct(vectorA, vectorB));
+}
+
+/**
+ * @brief Returns the vector magnitude squared.
+ * @param vector Vector.
+ * @return Vector magnitude squared.
+ */
+static inline float FusionVectorMagnitudeSquared(const FusionVector vector)
+{
+ return FusionVectorSum(FusionVectorHadamardProduct(vector, vector));
+}
+
+/**
+ * @brief Returns the vector magnitude.
+ * @param vector Vector.
+ * @return Vector magnitude.
+ */
+static inline float FusionVectorMagnitude(const FusionVector vector)
+{
+ return sqrtf(FusionVectorMagnitudeSquared(vector));
+}
+
+/**
+ * @brief Returns the normalised vector.
+ * @param vector Vector.
+ * @return Normalised vector.
+ */
+static inline FusionVector FusionVectorNormalise(const FusionVector vector)
+{
+#ifdef FUSION_USE_NORMAL_SQRT
+ const float magnitudeReciprocal = 1.0f / sqrtf(FusionVectorMagnitudeSquared(vector));
+#else
+ const float magnitudeReciprocal = FusionFastInverseSqrt(FusionVectorMagnitudeSquared(vector));
+#endif
+ return FusionVectorMultiplyScalar(vector, magnitudeReciprocal);
+}
+
+//------------------------------------------------------------------------------
+// Inline functions - Quaternion operations
+
+/**
+ * @brief Returns the sum of two quaternions.
+ * @param quaternionA Quaternion A.
+ * @param quaternionB Quaternion B.
+ * @return Sum of two quaternions.
+ */
+static inline FusionQuaternion FusionQuaternionAdd(const FusionQuaternion quaternionA, const FusionQuaternion quaternionB)
+{
+ const FusionQuaternion result = {.element = {
+ .w = quaternionA.element.w + quaternionB.element.w,
+ .x = quaternionA.element.x + quaternionB.element.x,
+ .y = quaternionA.element.y + quaternionB.element.y,
+ .z = quaternionA.element.z + quaternionB.element.z,
+ }};
+ return result;
+}
+
+/**
+ * @brief Returns the multiplication of two quaternions.
+ * @param quaternionA Quaternion A (to be post-multiplied).
+ * @param quaternionB Quaternion B (to be pre-multiplied).
+ * @return Multiplication of two quaternions.
+ */
+static inline FusionQuaternion FusionQuaternionMultiply(const FusionQuaternion quaternionA, const FusionQuaternion quaternionB)
+{
+#define A quaternionA.element
+#define B quaternionB.element
+ const FusionQuaternion result = {.element = {
+ .w = A.w * B.w - A.x * B.x - A.y * B.y - A.z * B.z,
+ .x = A.w * B.x + A.x * B.w + A.y * B.z - A.z * B.y,
+ .y = A.w * B.y - A.x * B.z + A.y * B.w + A.z * B.x,
+ .z = A.w * B.z + A.x * B.y - A.y * B.x + A.z * B.w,
+ }};
+ return result;
+#undef A
+#undef B
+}
+
+/**
+ * @brief Returns the multiplication of a quaternion with a vector. This is a
+ * normal quaternion multiplication where the vector is treated a
+ * quaternion with a W element value of zero. The quaternion is post-
+ * multiplied by the vector.
+ * @param quaternion Quaternion.
+ * @param vector Vector.
+ * @return Multiplication of a quaternion with a vector.
+ */
+static inline FusionQuaternion FusionQuaternionMultiplyVector(const FusionQuaternion quaternion, const FusionVector vector)
+{
+#define Q quaternion.element
+#define V vector.axis
+ const FusionQuaternion result = {.element = {
+ .w = -Q.x * V.x - Q.y * V.y - Q.z * V.z,
+ .x = Q.w * V.x + Q.y * V.z - Q.z * V.y,
+ .y = Q.w * V.y - Q.x * V.z + Q.z * V.x,
+ .z = Q.w * V.z + Q.x * V.y - Q.y * V.x,
+ }};
+ return result;
+#undef Q
+#undef V
+}
+
+/**
+ * @brief Returns the normalised quaternion.
+ * @param quaternion Quaternion.
+ * @return Normalised quaternion.
+ */
+static inline FusionQuaternion FusionQuaternionNormalise(const FusionQuaternion quaternion)
+{
+#define Q quaternion.element
+#ifdef FUSION_USE_NORMAL_SQRT
+ const float magnitudeReciprocal = 1.0f / sqrtf(Q.w * Q.w + Q.x * Q.x + Q.y * Q.y + Q.z * Q.z);
+#else
+ const float magnitudeReciprocal = FusionFastInverseSqrt(Q.w * Q.w + Q.x * Q.x + Q.y * Q.y + Q.z * Q.z);
+#endif
+ const FusionQuaternion result = {.element = {
+ .w = Q.w * magnitudeReciprocal,
+ .x = Q.x * magnitudeReciprocal,
+ .y = Q.y * magnitudeReciprocal,
+ .z = Q.z * magnitudeReciprocal,
+ }};
+ return result;
+#undef Q
+}
+
+//------------------------------------------------------------------------------
+// Inline functions - Matrix operations
+
+/**
+ * @brief Returns the multiplication of a matrix with a vector.
+ * @param matrix Matrix.
+ * @param vector Vector.
+ * @return Multiplication of a matrix with a vector.
+ */
+static inline FusionVector FusionMatrixMultiplyVector(const FusionMatrix matrix, const FusionVector vector)
+{
+#define R matrix.element
+ const FusionVector result = {.axis = {
+ .x = R.xx * vector.axis.x + R.xy * vector.axis.y + R.xz * vector.axis.z,
+ .y = R.yx * vector.axis.x + R.yy * vector.axis.y + R.yz * vector.axis.z,
+ .z = R.zx * vector.axis.x + R.zy * vector.axis.y + R.zz * vector.axis.z,
+ }};
+ return result;
+#undef R
+}
+
+//------------------------------------------------------------------------------
+// Inline functions - Conversion operations
+
+/**
+ * @brief Converts a quaternion to a rotation matrix.
+ * @param quaternion Quaternion.
+ * @return Rotation matrix.
+ */
+static inline FusionMatrix FusionQuaternionToMatrix(const FusionQuaternion quaternion)
+{
+#define Q quaternion.element
+ const float qwqw = Q.w * Q.w; // calculate common terms to avoid repeated operations
+ const float qwqx = Q.w * Q.x;
+ const float qwqy = Q.w * Q.y;
+ const float qwqz = Q.w * Q.z;
+ const float qxqy = Q.x * Q.y;
+ const float qxqz = Q.x * Q.z;
+ const float qyqz = Q.y * Q.z;
+ const FusionMatrix matrix = {.element = {
+ .xx = 2.0f * (qwqw - 0.5f + Q.x * Q.x),
+ .xy = 2.0f * (qxqy - qwqz),
+ .xz = 2.0f * (qxqz + qwqy),
+ .yx = 2.0f * (qxqy + qwqz),
+ .yy = 2.0f * (qwqw - 0.5f + Q.y * Q.y),
+ .yz = 2.0f * (qyqz - qwqx),
+ .zx = 2.0f * (qxqz - qwqy),
+ .zy = 2.0f * (qyqz + qwqx),
+ .zz = 2.0f * (qwqw - 0.5f + Q.z * Q.z),
+ }};
+ return matrix;
+#undef Q
+}
+
+/**
+ * @brief Converts a quaternion to ZYX Euler angles in degrees.
+ * @param quaternion Quaternion.
+ * @return Euler angles in degrees.
+ */
+static inline FusionEuler FusionQuaternionToEuler(const FusionQuaternion quaternion)
+{
+#define Q quaternion.element
+ const float halfMinusQySquared = 0.5f - Q.y * Q.y; // calculate common terms to avoid repeated operations
+ const FusionEuler euler = {.angle = {
+ .roll = FusionRadiansToDegrees(atan2f(Q.w * Q.x + Q.y * Q.z, halfMinusQySquared - Q.x * Q.x)),
+ .pitch = FusionRadiansToDegrees(FusionAsin(2.0f * (Q.w * Q.y - Q.z * Q.x))),
+ .yaw = FusionRadiansToDegrees(atan2f(Q.w * Q.z + Q.x * Q.y, halfMinusQySquared - Q.z * Q.z)),
+ }};
+ return euler;
+#undef Q
+}
+
+#endif
+
+//------------------------------------------------------------------------------
+// End of file
diff --git a/src/Fusion/FusionOffset.c b/src/Fusion/FusionOffset.c
new file mode 100644
index 000000000..d4334c874
--- /dev/null
+++ b/src/Fusion/FusionOffset.c
@@ -0,0 +1,80 @@
+/**
+ * @file FusionOffset.c
+ * @author Seb Madgwick
+ * @brief Gyroscope offset correction algorithm for run-time calibration of the
+ * gyroscope offset.
+ */
+
+//------------------------------------------------------------------------------
+// Includes
+
+#include "FusionOffset.h"
+#include // fabsf
+
+//------------------------------------------------------------------------------
+// Definitions
+
+/**
+ * @brief Cutoff frequency in Hz.
+ */
+#define CUTOFF_FREQUENCY (0.02f)
+
+/**
+ * @brief Timeout in seconds.
+ */
+#define TIMEOUT (5)
+
+/**
+ * @brief Threshold in degrees per second.
+ */
+#define THRESHOLD (3.0f)
+
+//------------------------------------------------------------------------------
+// Functions
+
+/**
+ * @brief Initialises the gyroscope offset algorithm.
+ * @param offset Gyroscope offset algorithm structure.
+ * @param sampleRate Sample rate in Hz.
+ */
+void FusionOffsetInitialise(FusionOffset *const offset, const unsigned int sampleRate)
+{
+ offset->filterCoefficient = 2.0f * (float)M_PI * CUTOFF_FREQUENCY * (1.0f / (float)sampleRate);
+ offset->timeout = TIMEOUT * sampleRate;
+ offset->timer = 0;
+ offset->gyroscopeOffset = FUSION_VECTOR_ZERO;
+}
+
+/**
+ * @brief Updates the gyroscope offset algorithm and returns the corrected
+ * gyroscope measurement.
+ * @param offset Gyroscope offset algorithm structure.
+ * @param gyroscope Gyroscope measurement in degrees per second.
+ * @return Corrected gyroscope measurement in degrees per second.
+ */
+FusionVector FusionOffsetUpdate(FusionOffset *const offset, FusionVector gyroscope)
+{
+
+ // Subtract offset from gyroscope measurement
+ gyroscope = FusionVectorSubtract(gyroscope, offset->gyroscopeOffset);
+
+ // Reset timer if gyroscope not stationary
+ if ((fabsf(gyroscope.axis.x) > THRESHOLD) || (fabsf(gyroscope.axis.y) > THRESHOLD) || (fabsf(gyroscope.axis.z) > THRESHOLD)) {
+ offset->timer = 0;
+ return gyroscope;
+ }
+
+ // Increment timer while gyroscope stationary
+ if (offset->timer < offset->timeout) {
+ offset->timer++;
+ return gyroscope;
+ }
+
+ // Adjust offset if timer has elapsed
+ offset->gyroscopeOffset =
+ FusionVectorAdd(offset->gyroscopeOffset, FusionVectorMultiplyScalar(gyroscope, offset->filterCoefficient));
+ return gyroscope;
+}
+
+//------------------------------------------------------------------------------
+// End of file
diff --git a/src/Fusion/FusionOffset.h b/src/Fusion/FusionOffset.h
new file mode 100644
index 000000000..51ae4a896
--- /dev/null
+++ b/src/Fusion/FusionOffset.h
@@ -0,0 +1,40 @@
+/**
+ * @file FusionOffset.h
+ * @author Seb Madgwick
+ * @brief Gyroscope offset correction algorithm for run-time calibration of the
+ * gyroscope offset.
+ */
+
+#ifndef FUSION_OFFSET_H
+#define FUSION_OFFSET_H
+
+//------------------------------------------------------------------------------
+// Includes
+
+#include "FusionMath.h"
+
+//------------------------------------------------------------------------------
+// Definitions
+
+/**
+ * @brief Gyroscope offset algorithm structure. Structure members are used
+ * internally and must not be accessed by the application.
+ */
+typedef struct {
+ float filterCoefficient;
+ unsigned int timeout;
+ unsigned int timer;
+ FusionVector gyroscopeOffset;
+} FusionOffset;
+
+//------------------------------------------------------------------------------
+// Function declarations
+
+void FusionOffsetInitialise(FusionOffset *const offset, const unsigned int sampleRate);
+
+FusionVector FusionOffsetUpdate(FusionOffset *const offset, FusionVector gyroscope);
+
+#endif
+
+//------------------------------------------------------------------------------
+// End of file
diff --git a/src/Power.cpp b/src/Power.cpp
index d13fd6891..18a527cee 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -50,7 +50,7 @@ RTC_NOINIT_ATTR uint64_t RTC_reg_b;
esp_adc_cal_characteristics_t *adc_characs = (esp_adc_cal_characteristics_t *)calloc(1, sizeof(esp_adc_cal_characteristics_t));
#ifndef ADC_ATTENUATION
-static const adc_atten_t atten = ADC_ATTEN_DB_11;
+static const adc_atten_t atten = ADC_ATTEN_DB_12;
#else
static const adc_atten_t atten = ADC_ATTENUATION;
#endif
@@ -69,12 +69,16 @@ static const uint8_t ext_chrg_detect_value = EXT_CHRG_DETECT_VALUE;
#endif
#endif
-#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
+#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO)
INA260Sensor ina260Sensor;
INA219Sensor ina219Sensor;
INA3221Sensor ina3221Sensor;
#endif
+#if HAS_RAKPROT && !defined(ARCH_PORTDUINO)
+RAK9154Sensor rak9154Sensor;
+#endif
+
#ifdef HAS_PMU
#include "XPowersAXP192.tpp"
#include "XPowersAXP2101.tpp"
@@ -145,6 +149,12 @@ class AnalogBatteryLevel : public HasBatteryLevel
*/
virtual int getBatteryPercent() override
{
+#if defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU)
+ if (hasRAK()) {
+ return rak9154Sensor.getBusBatteryPercent();
+ }
+#endif
+
float v = getBattVoltage();
if (v < noBatVolt)
@@ -184,7 +194,13 @@ class AnalogBatteryLevel : public HasBatteryLevel
virtual uint16_t getBattVoltage() override
{
-#if defined(HAS_TELEMETRY) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU)
+#if defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU)
+ if (hasRAK()) {
+ return getRAKVoltage();
+ }
+#endif
+
+#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
if (hasINA()) {
LOG_DEBUG("Using INA on I2C addr 0x%x for device battery voltage\n", config.power.device_battery_ina_address);
return getINAVoltage();
@@ -223,7 +239,17 @@ class AnalogBatteryLevel : public HasBatteryLevel
raw = raw / BATTERY_SENSE_SAMPLES;
scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw;
#endif
- last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF
+
+ if (!initial_read_done) {
+ // Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
+ if (scaled > last_read_value)
+ last_read_value = scaled;
+ initial_read_done = true;
+ } else {
+ // Already initialized - filter this reading
+ last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF
+ }
+
// LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u\n", BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t)
// (last_read_value));
}
@@ -325,13 +351,20 @@ class AnalogBatteryLevel : public HasBatteryLevel
virtual bool isVbusIn() override
{
#ifdef EXT_PWR_DETECT
+#ifdef HELTEC_CAPSULE_SENSOR_V3
+ // if external powered that pin will be pulled down
+ if (digitalRead(EXT_PWR_DETECT) == LOW) {
+ return true;
+ }
+ // if it's not LOW - check the battery
+#else
// if external powered that pin will be pulled up
if (digitalRead(EXT_PWR_DETECT) == HIGH) {
return true;
}
// if it's not HIGH - check the battery
#endif
-
+#endif
return getBattVoltage() > chargingVolt;
}
@@ -339,6 +372,11 @@ class AnalogBatteryLevel : public HasBatteryLevel
/// we can't be smart enough to say 'full'?
virtual bool isCharging() override
{
+#if defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU)
+ if (hasRAK()) {
+ return (rak9154Sensor.isCharging()) ? OptTrue : OptFalse;
+ }
+#endif
#ifdef EXT_CHRG_DETECT
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
#else
@@ -357,10 +395,24 @@ class AnalogBatteryLevel : public HasBatteryLevel
const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS;
// Start value from minimum voltage for the filter to not start from 0
// that could trigger some events.
+ // This value is over-written by the first ADC reading, it the voltage seems reasonable.
+ bool initial_read_done = false;
float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS);
uint32_t last_read_time_ms = 0;
-#if defined(HAS_TELEMETRY) && !defined(ARCH_PORTDUINO)
+#if defined(HAS_RAKPROT)
+
+ uint16_t getRAKVoltage() { return rak9154Sensor.getBusVoltageMv(); }
+
+ bool hasRAK()
+ {
+ if (!rak9154Sensor.isInitialized())
+ return rak9154Sensor.runOnce() > 0;
+ return rak9154Sensor.isRunning();
+ }
+#endif
+
+#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO)
uint16_t getINAVoltage()
{
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) {
@@ -409,8 +461,12 @@ Power::Power() : OSThread("Power")
bool Power::analogInit()
{
#ifdef EXT_PWR_DETECT
+#ifdef HELTEC_CAPSULE_SENSOR_V3
+ pinMode(EXT_PWR_DETECT, INPUT_PULLUP);
+#else
pinMode(EXT_PWR_DETECT, INPUT);
#endif
+#endif
#ifdef EXT_CHRG_DETECT
pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode);
#endif
@@ -543,14 +599,24 @@ void Power::readPowerStatus()
#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates the power states. Takes 20 seconds or so to detect
// changes.
+ static nrfx_power_usb_state_t prev_nrf_usb_state = (nrfx_power_usb_state_t)-1; // -1 so that state detected at boot
nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get();
- if (nrf_usb_state == NRFX_POWER_USB_STATE_DISCONNECTED) {
- powerFSM.trigger(EVENT_POWER_DISCONNECTED);
- NRF_USB = OptFalse;
- } else {
- powerFSM.trigger(EVENT_POWER_CONNECTED);
- NRF_USB = OptTrue;
+ // If state changed
+ if (nrf_usb_state != prev_nrf_usb_state) {
+ // If changed to DISCONNECTED
+ if (nrf_usb_state == NRFX_POWER_USB_STATE_DISCONNECTED) {
+ powerFSM.trigger(EVENT_POWER_DISCONNECTED);
+ NRF_USB = OptFalse;
+ }
+ // If changed to CONNECTED / READY
+ else {
+ powerFSM.trigger(EVENT_POWER_CONNECTED);
+ NRF_USB = OptTrue;
+ }
+
+ // Cache the current state
+ prev_nrf_usb_state = nrf_usb_state;
}
#endif
// Notify any status instances that are observing us
diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp
index 4f42b36b5..a7bc18f1a 100644
--- a/src/PowerFSM.cpp
+++ b/src/PowerFSM.cpp
@@ -348,12 +348,18 @@ void PowerFSM_setup()
powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone");
- powerFSM.add_timed_transition(&stateON, &stateDARK,
- Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
- "Screen-on timeout");
- powerFSM.add_timed_transition(&statePOWER, &stateDARK,
- Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
- "Screen-on timeout");
+#ifdef USE_EINK
+ // Allow E-Ink devices to suppress the screensaver, if screen timeout set to 0
+ if (config.display.screen_on_secs > 0)
+#endif
+ {
+ powerFSM.add_timed_transition(&stateON, &stateDARK,
+ Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs),
+ NULL, "Screen-on timeout");
+ powerFSM.add_timed_transition(&statePOWER, &stateDARK,
+ Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs),
+ NULL, "Screen-on timeout");
+ }
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
#ifdef ARCH_ESP32
diff --git a/src/PowerStatus.h b/src/PowerStatus.h
index 56d19b758..592a03328 100644
--- a/src/PowerStatus.h
+++ b/src/PowerStatus.h
@@ -59,9 +59,18 @@ class PowerStatus : public Status
int getBatteryVoltageMv() const { return batteryVoltageMv; }
/**
- * Note: 0% battery means 'unknown/this board doesn't have a battery installed'
+ * Note: for boards with battery pin or PMU, 0% battery means 'unknown/this board doesn't have a battery installed'
*/
+#if defined(HAS_PMU) || defined(BATTERY_PIN)
uint8_t getBatteryChargePercent() const { return getHasBattery() ? batteryChargePercent : 0; }
+#endif
+
+ /**
+ * Note: for boards without battery pin and PMU, 101% battery means 'the board is using external power'
+ */
+#if !defined(HAS_PMU) && !defined(BATTERY_PIN)
+ uint8_t getBatteryChargePercent() const { return getHasBattery() ? batteryChargePercent : 101; }
+#endif
bool matches(const PowerStatus *newStatus) const
{
diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp
index e17c8f99e..53ece0fa3 100644
--- a/src/SerialConsole.cpp
+++ b/src/SerialConsole.cpp
@@ -2,6 +2,7 @@
#include "NodeDB.h"
#include "PowerFSM.h"
#include "configuration.h"
+#include "time.h"
#ifdef RP2040_SLOW_CLOCK
#define Port Serial2
@@ -50,7 +51,9 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
}
}
#endif
+#if !ARCH_PORTDUINO
emitRebooted();
+#endif
}
int32_t SerialConsole::runOnce()
diff --git a/src/configuration.h b/src/configuration.h
index cf22846d5..55088e62e 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -127,8 +127,17 @@ along with this program. If not, see .
#define SHTC3_ADDR 0x70
#define LPS22HB_ADDR 0x5C
#define LPS22HB_ADDR_ALT 0x5D
-#define SHT31_ADDR 0x44
+#define SHT31_4x_ADDR 0x44
#define PMSA0031_ADDR 0x12
+#define AHT10_ADDR 0x38
+#define RCWL9620_ADDR 0x57
+#define VEML7700_ADDR 0x10
+#define TSL25911_ADDR 0x29
+#define OPT3001_ADDR 0x45
+#define OPT3001_ADDR_ALT 0x44
+#define MLX90632_ADDR 0x3A
+#define DFROBOT_LARK_ADDR 0x42
+#define NAU7802_ADDR 0x2A
// -----------------------------------------------------------------------------
// ACCELEROMETER
@@ -137,6 +146,7 @@ along with this program. If not, see .
#define LIS3DH_ADR 0x18
#define BMA423_ADDR 0x19
#define LSM6DS3_ADDR 0x6A
+#define BMX160_ADDR 0x69
// -----------------------------------------------------------------------------
// LED
diff --git a/src/detect/LoRaRadioType.h b/src/detect/LoRaRadioType.h
index eadd92e64..3975153b5 100644
--- a/src/detect/LoRaRadioType.h
+++ b/src/detect/LoRaRadioType.h
@@ -1,5 +1,16 @@
#pragma once
-enum LoRaRadioType { NO_RADIO, STM32WLx_RADIO, SIM_RADIO, RF95_RADIO, SX1262_RADIO, SX1268_RADIO, LLCC68_RADIO, SX1280_RADIO };
+enum LoRaRadioType {
+ NO_RADIO,
+ STM32WLx_RADIO,
+ SIM_RADIO,
+ RF95_RADIO,
+ SX1262_RADIO,
+ SX1268_RADIO,
+ LLCC68_RADIO,
+ SX1280_RADIO,
+ LR1110_RADIO,
+ LR1120_RADIO
+};
extern LoRaRadioType radioType;
\ No newline at end of file
diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp
index 395de5162..b82ccc8d8 100644
--- a/src/detect/ScanI2C.cpp
+++ b/src/detect/ScanI2C.cpp
@@ -6,6 +6,7 @@ const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C::
ScanI2C::ScanI2C() = default;
void ScanI2C::scanPort(ScanI2C::I2CPort port) {}
+void ScanI2C::scanPort(ScanI2C::I2CPort port, uint8_t *address, uint8_t asize) {}
void ScanI2C::setSuppressScreen()
{
@@ -36,8 +37,8 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
{
- ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3};
- return firstOfOrNONE(4, types);
+ ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160};
+ return firstOfOrNONE(5, types);
}
ScanI2C::FoundDevice ScanI2C::find(ScanI2C::DeviceType) const
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index e3f034da8..c33a3812d 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -30,6 +30,7 @@ class ScanI2C
INA3221,
MCP9808,
SHT31,
+ SHT4X,
SHTC3,
LPS22HB,
QMC6310,
@@ -42,9 +43,16 @@ class ScanI2C
BQ24295,
LSM6DS3,
TCA9555,
-#ifdef HAS_NCP5623
+ VEML7700,
+ RCWL9620,
NCP5623,
-#endif
+ TSL2591,
+ OPT3001,
+ MLX90632,
+ AHT10,
+ BMX160,
+ DFROBOT_LARK,
+ NAU7802
} DeviceType;
// typedef uint8_t DeviceAddress;
@@ -81,6 +89,7 @@ class ScanI2C
ScanI2C();
virtual void scanPort(ScanI2C::I2CPort);
+ virtual void scanPort(ScanI2C::I2CPort, uint8_t *, uint8_t);
/*
* A bit of a hack, this tells the scanner not to tell later systems there is a screen to avoid enabling it.
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 905794e2a..104deb82c 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -14,6 +14,15 @@
#define XPOWERS_AXP192_AXP2101_ADDRESS 0x34
#endif
+bool in_array(uint8_t *array, int size, uint8_t lookfor)
+{
+ int i;
+ for (i = 0; i < size; i++)
+ if (lookfor == array[i])
+ return true;
+ return false;
+}
+
ScanI2C::FoundDevice ScanI2CTwoWire::find(ScanI2C::DeviceType type) const
{
concurrency::LockGuard guard((concurrency::Lock *)&lock);
@@ -135,11 +144,11 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation
type = T; \
break;
-void ScanI2CTwoWire::scanPort(I2CPort port)
+void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
{
concurrency::LockGuard guard((concurrency::Lock *)&lock);
- LOG_DEBUG("Scanning for i2c devices on port %d\n", port);
+ LOG_DEBUG("Scanning for I2C devices on port %d\n", port);
uint8_t err;
@@ -163,6 +172,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
#endif
for (addr.address = 1; addr.address < 127; addr.address++) {
+ if (asize != 0) {
+ if (!in_array(address, asize, addr.address))
+ continue;
+ LOG_DEBUG("Scanning address 0x%x\n", addr.address);
+ }
i2cBus->beginTransmission(addr.address);
#ifdef ARCH_PORTDUINO
if (i2cBus->read() != -1)
@@ -257,7 +271,12 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
type = BMP_280;
}
break;
-
+#ifndef HAS_NCP5623
+ case AHT10_ADDR:
+ LOG_INFO("AHT10 sensor found at address 0x%x\n", (uint8_t)addr.address);
+ type = AHT10;
+ break;
+#endif
case INA_ADDR:
case INA_ADDR_ALTERNATE:
case INA_ADDR_WAVESHARE_UPS:
@@ -277,8 +296,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
if (registerValue == 0x5449) {
LOG_INFO("INA3221 sensor found at address 0x%x\n", (uint8_t)addr.address);
type = INA3221;
- } else { // Unknown device
- LOG_INFO("No INA3221 found at address 0x%x\n", (uint8_t)addr.address);
+ } else {
+ LOG_INFO("DFRobot Lark weather station found at address 0x%x\n", (uint8_t)addr.address);
+ type = DFROBOT_LARK;
}
break;
case MCP9808_ADDR:
@@ -293,8 +313,23 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
break;
- SCAN_SIMPLE_CASE(SHT31_ADDR, SHT31, "SHT31 sensor found\n")
+ case SHT31_4x_ADDR:
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
+ if (registerValue == 0x11a2) {
+ type = SHT4X;
+ LOG_INFO("SHT4X sensor found\n");
+ } else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
+ type = OPT3001;
+ LOG_INFO("OPT3001 light sensor found\n");
+ } else {
+ type = SHT31;
+ LOG_INFO("SHT31 sensor found\n");
+ }
+
+ break;
+
SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTC3, "SHTC3 sensor found\n")
+ SCAN_SIMPLE_CASE(RCWL9620_ADDR, RCWL9620, "RCWL9620 sensor found\n")
case LPS22HB_ADDR_ALT:
SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB sensor found\n")
@@ -322,15 +357,21 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031 air quality sensor found\n")
SCAN_SIMPLE_CASE(MPU6050_ADDR, MPU6050, "MPU6050 accelerometer found\n");
+ SCAN_SIMPLE_CASE(BMX160_ADDR, BMX160, "BMX160 accelerometer found\n");
SCAN_SIMPLE_CASE(BMA423_ADDR, BMA423, "BMA423 accelerometer found\n");
SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555 I2C expander found\n");
+ SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700 light sensor found\n");
+ SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591 light sensor found\n");
+ SCAN_SIMPLE_CASE(OPT3001_ADDR, OPT3001, "OPT3001 light sensor found\n");
+ SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632 IR temp sensor found\n");
+ SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802 based scale found\n");
default:
LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address);
}
} else if (err == 4) {
- LOG_ERROR("Unknown error at address 0x%x\n", addr);
+ LOG_ERROR("Unknown error at address 0x%x\n", addr.address);
}
// Check if a type was found for the enumerated device - save, if so
@@ -341,6 +382,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
}
}
+void ScanI2CTwoWire::scanPort(I2CPort port)
+{
+ scanPort(port, nullptr, 0);
+}
+
TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const
{
if (address.port == ScanI2C::I2CPort::WIRE) {
diff --git a/src/detect/ScanI2CTwoWire.h b/src/detect/ScanI2CTwoWire.h
index 9acd736d2..82b48f6b4 100644
--- a/src/detect/ScanI2CTwoWire.h
+++ b/src/detect/ScanI2CTwoWire.h
@@ -16,6 +16,8 @@ class ScanI2CTwoWire : public ScanI2C
public:
void scanPort(ScanI2C::I2CPort) override;
+ void scanPort(ScanI2C::I2CPort, uint8_t *, uint8_t) override;
+
ScanI2C::FoundDevice find(ScanI2C::DeviceType) const override;
TwoWire *fetchI2CBus(ScanI2C::DeviceAddress) const;
@@ -53,4 +55,4 @@ class ScanI2CTwoWire : public ScanI2C
uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth) const;
DeviceType probeOLED(ScanI2C::DeviceAddress) const;
-};
+};
\ No newline at end of file
diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index 17e35a4b3..8d46742ba 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -21,6 +21,19 @@
#define GPS_RESET_MODE HIGH
#endif
+// How many minutes of sleep make it worthwhile to power-off the GPS
+// Shorter than this, and GPS will only enter standby
+// Affected by lock-time, and config.position.gps_update_interval
+#ifndef GPS_STANDBY_THRESHOLD_MINUTES
+#define GPS_STANDBY_THRESHOLD_MINUTES 15
+#endif
+
+// How many seconds of sleep make it worthwhile for the GPS to use powered-on standby
+// Shorter than this, and we'll just wait instead
+#ifndef GPS_IDLE_THRESHOLD_SECONDS
+#define GPS_IDLE_THRESHOLD_SECONDS 10
+#endif
+
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
HardwareSerial *GPS::_serial_gps = &Serial1;
#else
@@ -62,10 +75,10 @@ void GPS::CASChecksum(uint8_t *message, size_t length)
// Iterate over the payload as a series of uint32_t's and
// accumulate the cksum
- uint32_t *payload = (uint32_t *)(message + 6);
+ uint32_t const *payload = (uint32_t *)(message + 6);
for (size_t i = 0; i < (length - 10) / 4; i++) {
- uint32_t p = payload[i];
- cksum += p;
+ uint32_t pl = payload[i];
+ cksum += pl;
}
// Place the checksum values in the message
@@ -452,7 +465,7 @@ bool GPS::setup()
// Set the NEMA output messages
// Ask for only RMC and GGA
uint8_t fields[] = {CAS_NEMA_RMC, CAS_NEMA_GGA};
- for (uint i = 0; i < sizeof(fields); i++) {
+ for (unsigned int i = 0; i < sizeof(fields); i++) {
// Construct a CAS-CFG-MSG packet
uint8_t cas_cfg_msg_packet[] = {0x4e, fields[i], 0x01, 0x00};
msglen = makeCASPacket(0x06, 0x01, sizeof(cas_cfg_msg_packet), cas_cfg_msg_packet);
@@ -472,6 +485,9 @@ bool GPS::setup()
// Turn off GSV messages, we don't really care about which and where the sats are, maybe someday.
_serial_gps->write("$CFGMSG,0,3,0\r\n");
delay(250);
+ // Turn off GSA messages, TinyGPS++ doesn't use this message.
+ _serial_gps->write("$CFGMSG,0,2,0\r\n");
+ delay(250);
// Turn off NOTICE __TXT messages, these may provide Unicore some info but we don't care.
_serial_gps->write("$CFGMSG,6,0,0\r\n");
delay(250);
@@ -764,7 +780,24 @@ GPS::~GPS()
void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime)
{
- LOG_INFO("Setting GPS power=%d\n", on);
+ // Record the current powerState
+ if (on)
+ powerState = GPS_ACTIVE;
+ else if (!enabled) // User has disabled with triple press
+ powerState = GPS_OFF;
+ else if (sleepTime <= GPS_IDLE_THRESHOLD_SECONDS * 1000UL)
+ powerState = GPS_IDLE;
+ else if (standbyOnly)
+ powerState = GPS_STANDBY;
+ else
+ powerState = GPS_OFF;
+
+ LOG_DEBUG("GPS::powerState=%d\n", powerState);
+
+ // If the next update is due *really soon*, don't actually power off or enter standby. Just wait it out.
+ if (!on && powerState == GPS_IDLE)
+ return;
+
if (on) {
clearBuffer(); // drop any old data waiting in the buffer before re-enabling
if (en_gpio)
@@ -858,45 +891,72 @@ void GPS::setConnected()
*
* calls sleep/wake
*/
-void GPS::setAwake(bool on)
+void GPS::setAwake(bool wantAwake)
{
- if (isAwake != on) {
- LOG_DEBUG("WANT GPS=%d\n", on);
- isAwake = on;
- if (!enabled) { // short circuit if the user has disabled GPS
- setGPSPower(false, false, 0);
- return;
- }
- if (on) {
+ // If user has disabled GPS, make sure it is off, not just in standby or idle
+ if (!wantAwake && !enabled && powerState != GPS_OFF) {
+ setGPSPower(false, false, 0);
+ return;
+ }
+
+ // If GPS power state needs to change
+ if ((wantAwake && powerState != GPS_ACTIVE) || (!wantAwake && powerState == GPS_ACTIVE)) {
+ LOG_DEBUG("WANT GPS=%d\n", wantAwake);
+
+ // Calculate how long it takes to get a GPS lock
+ if (wantAwake) {
+ // Record the time we start looking for a lock
lastWakeStartMsec = millis();
} else {
+ // Record by how much we missed our ideal target postion.gps_update_interval (for logging only)
+ // Need to calculate this before we update lastSleepStartMsec, to make the new prediction
+ int32_t lateByMsec = (int32_t)(millis() - lastSleepStartMsec) - (int32_t)getSleepTime();
+
+ // Record the time we finish looking for a lock
lastSleepStartMsec = millis();
- if (GPSCycles == 1) { // Skipping initial lock time, as it will likely be much longer than average
- averageLockTime = lastSleepStartMsec - lastWakeStartMsec;
- } else if (GPSCycles > 1) {
- averageLockTime += ((int32_t)(lastSleepStartMsec - lastWakeStartMsec) - averageLockTime) / (int32_t)GPSCycles;
+
+ // How long did it take to get GPS lock this time?
+ uint32_t lockTime = lastSleepStartMsec - lastWakeStartMsec;
+
+ // Update the lock-time prediction
+ // Used pre-emptively, attempting to hit target of gps.position_update_interval
+ switch (GPSCycles) {
+ case 0:
+ LOG_DEBUG("Initial GPS lock took %ds\n", lockTime / 1000);
+ break;
+ case 1:
+ predictedLockTime = lockTime; // Avoid slow ramp-up - start with a real value
+ LOG_DEBUG("GPS Lock took %ds\n", lockTime / 1000);
+ break;
+ default:
+ // Predict lock-time using exponential smoothing: respond slowly to changes
+ predictedLockTime = (lockTime * 0.2) + (predictedLockTime * 0.8); // Latest lock time has 20% weight on prediction
+ LOG_INFO("GPS Lock took %ds. %s by %ds. Next lock predicted to take %ds.\n", lockTime / 1000,
+ (lateByMsec > 0) ? "Late" : "Early", abs(lateByMsec) / 1000, predictedLockTime / 1000);
}
GPSCycles++;
- LOG_DEBUG("GPS Lock took %d, average %d\n", (lastSleepStartMsec - lastWakeStartMsec) / 1000, averageLockTime / 1000);
}
- if ((int32_t)getSleepTime() - averageLockTime >
- 15 * 60 * 1000) { // 15 minutes is probably long enough to make a complete poweroff worth it.
- setGPSPower(on, false, getSleepTime() - averageLockTime);
- return;
- } else if ((int32_t)getSleepTime() - averageLockTime > 10000) { // 10 seconds is enough for standby
+
+ // How long to wait before attempting next GPS update
+ // Aims to hit position.gps_update_interval by using the lock-time prediction
+ uint32_t compensatedSleepTime = (getSleepTime() > predictedLockTime) ? (getSleepTime() - predictedLockTime) : 0;
+
+ // If long interval between updates: power off between updates
+ if (compensatedSleepTime > GPS_STANDBY_THRESHOLD_MINUTES * MS_IN_MINUTE) {
+ setGPSPower(wantAwake, false, getSleepTime() - predictedLockTime);
+ }
+
+ // If waking relatively frequently: don't power off. Would use more energy trying to reacquire lock each time
+ // We'll either use a "powered-on" standby, or just wait it out, depending on how soon the next update is due
+ // Will decide which inside setGPSPower method
+ else {
#ifdef GPS_UC6580
- setGPSPower(on, false, getSleepTime() - averageLockTime);
+ setGPSPower(wantAwake, false, compensatedSleepTime);
#else
- setGPSPower(on, true, getSleepTime() - averageLockTime);
+ setGPSPower(wantAwake, true, compensatedSleepTime);
#endif
- return;
}
- if (averageLockTime > 20000) {
- averageLockTime -= 1000; // eventually want to sleep again.
- }
- if (on)
- setGPSPower(true, true, 0); // make sure we don't have a fallthrough where GPS is stuck off
}
}
@@ -1002,14 +1062,14 @@ int32_t GPS::runOnce()
uint32_t timeAsleep = now - lastSleepStartMsec;
auto sleepTime = getSleepTime();
- if (!isAwake && (sleepTime != UINT32_MAX) &&
- ((timeAsleep > sleepTime) || (isInPowersave && timeAsleep > (sleepTime - averageLockTime)))) {
+ if (powerState != GPS_ACTIVE && (sleepTime != UINT32_MAX) &&
+ ((timeAsleep > sleepTime) || (isInPowersave && timeAsleep > (sleepTime - predictedLockTime)))) {
// We now want to be awake - so wake up the GPS
setAwake(true);
}
// While we are awake
- if (isAwake) {
+ if (powerState == GPS_ACTIVE) {
// LOG_DEBUG("looking for location\n");
// If we've already set time from the GPS, no need to ask the GPS
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
@@ -1055,7 +1115,7 @@ int32_t GPS::runOnce()
// 9600bps is approx 1 byte per msec, so considering our buffer size we never need to wake more often than 200ms
// if not awake we can run super infrquently (once every 5 secs?) to see if we need to wake.
- return isAwake ? GPS_THREAD_INTERVAL : 5000;
+ return (powerState == GPS_ACTIVE) ? GPS_THREAD_INTERVAL : 5000;
}
// clear the GPS rx buffer as quickly as possible
@@ -1467,7 +1527,7 @@ bool GPS::lookForLocation()
#endif // GPS_EXTRAVERBOSE
// Is this a new point or are we re-reading the previous one?
- if (!reader.location.isUpdated())
+ if (!reader.location.isUpdated() && !reader.altitude.isUpdated())
return false;
// check if a complete GPS solution set is available for reading
@@ -1584,11 +1644,11 @@ bool GPS::hasFlow()
bool GPS::whileIdle()
{
- uint charsInBuf = 0;
+ unsigned int charsInBuf = 0;
bool isValid = false;
- if (!isAwake) {
+ if (powerState != GPS_ACTIVE) {
clearBuffer();
- return isAwake;
+ return (powerState == GPS_ACTIVE);
}
#ifdef SERIAL_BUFFER_SIZE
if (_serial_gps->available() >= SERIAL_BUFFER_SIZE - 1) {
@@ -1619,6 +1679,10 @@ bool GPS::whileIdle()
}
void GPS::enable()
{
+ // Clear the old lock-time prediction
+ GPSCycles = 0;
+ predictedLockTime = 0;
+
enabled = true;
setInterval(GPS_THREAD_INTERVAL);
setAwake(true);
diff --git a/src/gps/GPS.h b/src/gps/GPS.h
index 77c6c0269..55bd42d0f 100644
--- a/src/gps/GPS.h
+++ b/src/gps/GPS.h
@@ -38,6 +38,13 @@ typedef enum {
GNSS_RESPONSE_OK,
} GPS_RESPONSE;
+enum GPSPowerState : uint8_t {
+ GPS_OFF = 0, // Physically powered off
+ GPS_ACTIVE = 1, // Awake and want a position
+ GPS_STANDBY = 2, // Physically powered on, but soft-sleeping
+ GPS_IDLE = 3, // Awake, but not wanting another position yet
+};
+
// Generate a string representation of DOP
const char *getDOPString(uint32_t dop);
@@ -66,7 +73,7 @@ class GPS : private concurrency::OSThread
uint32_t rx_gpio = 0;
uint32_t tx_gpio = 0;
uint32_t en_gpio = 0;
- int32_t averageLockTime = 0;
+ uint32_t predictedLockTime = 0;
uint32_t GPSCycles = 0;
int speedSelect = 0;
@@ -78,8 +85,6 @@ class GPS : private concurrency::OSThread
*/
bool hasValidLocation = false; // default to false, until we complete our first read
- bool isAwake = false; // true if we want a location right now
-
bool isInPowersave = false;
bool shouldPublish = false; // If we've changed GPS state, this will force a publish the next loop()
@@ -89,6 +94,8 @@ class GPS : private concurrency::OSThread
bool GPSInitFinished = false; // Init thread finished?
bool GPSInitStarted = false; // Init thread finished?
+ GPSPowerState powerState = GPS_OFF; // GPS_ACTIVE if we want a location right now
+
uint8_t numSatellites = 0;
CallbackObserver notifyDeepSleepObserver = CallbackObserver(this, &GPS::prepareDeepSleep);
diff --git a/src/gps/GeoCoord.cpp b/src/gps/GeoCoord.cpp
index cb4e69ff2..2224bd281 100644
--- a/src/gps/GeoCoord.cpp
+++ b/src/gps/GeoCoord.cpp
@@ -486,3 +486,91 @@ std::shared_ptr GeoCoord::pointAtDistance(double bearing, double range
return std::make_shared(double(lat), double(lon), this->getAltitude());
}
+
+/**
+ * Convert bearing to degrees
+ * @param bearing
+ * The bearing in string format
+ * @return Bearing in degrees
+ */
+uint GeoCoord::bearingToDegrees(const char *bearing)
+{
+ if (strcmp(bearing, "N") == 0)
+ return 0;
+ else if (strcmp(bearing, "NNE") == 0)
+ return 22;
+ else if (strcmp(bearing, "NE") == 0)
+ return 45;
+ else if (strcmp(bearing, "ENE") == 0)
+ return 67;
+ else if (strcmp(bearing, "E") == 0)
+ return 90;
+ else if (strcmp(bearing, "ESE") == 0)
+ return 112;
+ else if (strcmp(bearing, "SE") == 0)
+ return 135;
+ else if (strcmp(bearing, "SSE") == 0)
+ return 157;
+ else if (strcmp(bearing, "S") == 0)
+ return 180;
+ else if (strcmp(bearing, "SSW") == 0)
+ return 202;
+ else if (strcmp(bearing, "SW") == 0)
+ return 225;
+ else if (strcmp(bearing, "WSW") == 0)
+ return 247;
+ else if (strcmp(bearing, "W") == 0)
+ return 270;
+ else if (strcmp(bearing, "WNW") == 0)
+ return 292;
+ else if (strcmp(bearing, "NW") == 0)
+ return 315;
+ else if (strcmp(bearing, "NNW") == 0)
+ return 337;
+ else
+ return 0;
+}
+
+/**
+ * Convert bearing to string
+ * @param degrees
+ * The bearing in degrees
+ * @return Bearing in string format
+ */
+const char *GeoCoord::degreesToBearing(uint degrees)
+{
+ if (degrees >= 348 || degrees < 11)
+ return "N";
+ else if (degrees >= 11 && degrees < 34)
+ return "NNE";
+ else if (degrees >= 34 && degrees < 56)
+ return "NE";
+ else if (degrees >= 56 && degrees < 79)
+ return "ENE";
+ else if (degrees >= 79 && degrees < 101)
+ return "E";
+ else if (degrees >= 101 && degrees < 124)
+ return "ESE";
+ else if (degrees >= 124 && degrees < 146)
+ return "SE";
+ else if (degrees >= 146 && degrees < 169)
+ return "SSE";
+ else if (degrees >= 169 && degrees < 191)
+ return "S";
+ else if (degrees >= 191 && degrees < 214)
+ return "SSW";
+ else if (degrees >= 214 && degrees < 236)
+ return "SW";
+ else if (degrees >= 236 && degrees < 259)
+ return "WSW";
+ else if (degrees >= 259 && degrees < 281)
+ return "W";
+ else if (degrees >= 281 && degrees < 304)
+ return "WNW";
+ else if (degrees >= 304 && degrees < 326)
+ return "NW";
+ else if (degrees >= 326 && degrees < 348)
+ return "NNW";
+ else
+ return "N";
+}
diff --git a/src/gps/GeoCoord.h b/src/gps/GeoCoord.h
index e811035db..b02d12afb 100644
--- a/src/gps/GeoCoord.h
+++ b/src/gps/GeoCoord.h
@@ -117,6 +117,8 @@ class GeoCoord
static float bearing(double lat1, double lon1, double lat2, double lon2);
static float rangeRadiansToMeters(double range_radians);
static float rangeMetersToRadians(double range_meters);
+ static uint bearingToDegrees(const char *bearing);
+ static const char *degreesToBearing(uint degrees);
// Point to point conversions
int32_t distanceTo(const GeoCoord &pointB);
diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp
index 85931900f..d60e3825c 100644
--- a/src/gps/RTC.cpp
+++ b/src/gps/RTC.cpp
@@ -96,13 +96,17 @@ void readFromRTC()
*
* If we haven't yet set our RTC this boot, set it from a GPS derived time
*/
-bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
+bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
{
static uint32_t lastSetMsec = 0;
uint32_t now = millis();
bool shouldSet;
- if (q > currentQuality) {
+ if (forceUpdate) {
+ shouldSet = true;
+ LOG_DEBUG("Overriding current RTC quality (%s) with incoming time of RTC quality of %s\n", RtcName(currentQuality),
+ RtcName(q));
+ } else if (q > currentQuality) {
shouldSet = true;
LOG_DEBUG("Upgrading time to quality %s\n", RtcName(q));
} else if (q >= RTCQualityNTP && (now - lastSetMsec) > (12 * 60 * 60 * 1000UL)) {
@@ -218,12 +222,11 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t)
*/
int32_t getTZOffset()
{
- time_t now;
+ time_t now = getTime(false);
struct tm *gmt;
- now = time(NULL);
gmt = gmtime(&now);
gmt->tm_isdst = -1;
- return (int16_t)difftime(now, mktime(gmt));
+ return (int32_t)difftime(now, mktime(gmt));
}
/**
@@ -261,4 +264,4 @@ time_t gm_mktime(struct tm *tm)
setenv("TZ", "UTC0", 1);
}
return res;
-}
+}
\ No newline at end of file
diff --git a/src/gps/RTC.h b/src/gps/RTC.h
index 1d609f136..4b065b376 100644
--- a/src/gps/RTC.h
+++ b/src/gps/RTC.h
@@ -25,7 +25,7 @@ enum RTCQuality {
RTCQuality getRTCQuality();
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
-bool perhapsSetRTC(RTCQuality q, const struct timeval *tv);
+bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false);
bool perhapsSetRTC(RTCQuality q, struct tm &t);
/// Return a string name for the quality
diff --git a/src/gps/ubx.h b/src/gps/ubx.h
index 5b2cb24ce..0852c331d 100644
--- a/src/gps/ubx.h
+++ b/src/gps/ubx.h
@@ -206,14 +206,14 @@ const uint8_t GPS::_message_GLL[] = {
0x00 // Reserved
};
-// Enable GSA. GSA - GPS DOP and active satellites, used for detailing the satellites used in the positioning and
+// Disable GSA. GSA - GPS DOP and active satellites, used for detailing the satellites used in the positioning and
// the DOP (Dilution of Precision)
const uint8_t GPS::_message_GSA[] = {
0xF0, 0x02, // NMEA ID for GSA
0x00, // Rate for DDC
- 0x01, // Rate for UART1
+ 0x00, // Rate for UART1
0x00, // Rate for UART2
- 0x01, // Rate for USB usefull for native linux
+ 0x00, // Rate for USB usefull for native linux
0x00, // Rate for SPI
0x00 // Reserved
};
@@ -319,6 +319,8 @@ const uint8_t GPS::_message_SAVE[] = {
// As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR.
// BBR will survive a restart, and power off for a while, but modules with small backup
// batteries or super caps will not retain the config for a long power off time.
+// for all configurations using sleep / low power modes, V_BCKP needs to be hooked to permanent power for fast aquisition after
+// sleep
// VALSET Commands for M10
// Please refer to the M10 Protocol Specification:
@@ -327,40 +329,42 @@ const uint8_t GPS::_message_SAVE[] = {
// and:
// https://content.u-blox.com/sites/default/files/u-blox-M10-ROM-5.10_ReleaseNotes_UBX-22001426.pdf
// for interesting insights.
+//
+// Integration manual:
+// https://content.u-blox.com/sites/default/files/documents/SAM-M10Q_IntegrationManual_UBX-22020019.pdf
+// has details on low-power modes
+
/*
CFG-PM2 has been replaced by many CFG-PM commands
-OPERATEMODE E1 2 (0 | 1 | 2)
-POSUPDATEPERIOD U4 1000ms for M10 must be >= 5s try 5
-ACQPERIOD U4 10 seems ok for M10 def ok
-GRIDOFFSET U4 0 seems ok for M10 def ok
-ONTIME U2 1 will try 1
-MINACQTIME U1 0 will try 0 def ok
-MAXACQTIME U1 stick with default of 0 def ok
-DONOTENTEROFF L 1 stay at 1
-WAITTIMEFIX L 1 stay with 1
-UPDATEEPH L 1 changed to 1 for gps rework default is 1
-EXTINTWAKE L 0 no ext ints
-EXTINTBACKUP L 0 no ext ints
-EXTINTINACTIVE L 0 no ext ints
-EXTINTACTIVITY U4 0 no ext ints
-LIMITPEAKCURRENT L 1 stay with 1
-*/
-// CFG-PMS has been removed
+CFG-PMS has been removed
+
+CFG-PM-OPERATEMODE E1 (0 | 1 | 2) -> 1 (PSMOO), because sporadic position updates are required instead of continous tracking <10s
+(PSMCT) CFG-PM-POSUPDATEPERIOD U4 -> 0ms, no self-timed wakup because receiver power mode is controlled via "software standby
+mode" by legacy UBX-RXM-PMREQ request CFG-PM-ACQPERIOD U4 -> 0ms, because receiver power mode is controlled via "software standby
+mode" by legacy UBX-RXM-PMREQ request CFG-PM-ONTIME U4 -> 0ms, optional I guess CFG-PM-EXTINTBACKUP L -> 1, force receiver into
+BACKUP mode when EXTINT (should be connected to GPS_EN_PIN) pin is "low"
+
+This is required because the receiver never enters low power mode if microcontroller is in deep-sleep.
+Maybe the changing UART_RX levels trigger a wakeup but even with UBX-RXM-PMREQ[12] = 0x00 (all external wakeup sources disabled)
+the receivcer remains in aquisition state -> potentially a bug
+
+Workaround: Control the EXTINT pin by the GPS_EN_PIN signal
+
+As mentioned in the M10 operational issues down below, power save won't allow the use of BDS B1C.
+CFG-SIGNAL-BDS_B1C_ENA L -> 0
// Ram layer config message:
-// b5 62 06 8a 26 00 00 01 00 00 01 00 d0 20 02 02 00 d0 40 05 00 00 00 05 00 d0 30 01 00 08 00 d0 10 01 09 00 d0 10 01 10 00 d0
-// 10 01 8b de
+// 01 01 00 00 01 00 D0 20 01 02 00 D0 40 00 00 00 00 03 00 D0 40 00 00 00 00 05 00 D0 30 00 00 0D 00 D0 10 01
// BBR layer config message:
-// b5 62 06 8a 26 00 00 02 00 00 01 00 d0 20 02 02 00 d0 40 05 00 00 00 05 00 d0 30 01 00 08 00 d0 10 01 09 00 d0 10 01 10 00 d0
-// 10 01 8c 03
-
-const uint8_t GPS::_message_VALSET_PM_RAM[] = {0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0xd0, 0x20, 0x02, 0x02, 0x00, 0xd0, 0x40,
- 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0xd0, 0x30, 0x01, 0x00, 0x08, 0x00, 0xd0,
- 0x10, 0x01, 0x09, 0x00, 0xd0, 0x10, 0x01, 0x10, 0x00, 0xd0, 0x10, 0x01};
-const uint8_t GPS::_message_VALSET_PM_BBR[] = {0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0xd0, 0x20, 0x02, 0x02, 0x00, 0xd0, 0x40,
- 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0xd0, 0x30, 0x01, 0x00, 0x08, 0x00, 0xd0,
- 0x10, 0x01, 0x09, 0x00, 0xd0, 0x10, 0x01, 0x10, 0x00, 0xd0, 0x10, 0x01};
+// 01 02 00 00 01 00 D0 20 01 02 00 D0 40 00 00 00 00 03 00 D0 40 00 00 00 00 05 00 D0 30 00 00 0D 00 D0 10 01
+*/
+const uint8_t GPS::_message_VALSET_PM_RAM[] = {0x01, 0x01, 0x00, 0x00, 0x0F, 0x00, 0x31, 0x10, 0x00, 0x01, 0x00, 0xD0, 0x20, 0x01,
+ 0x02, 0x00, 0xD0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xD0, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x05, 0x00, 0xD0, 0x30, 0x00, 0x00, 0x0D, 0x00, 0xD0, 0x10, 0x01};
+const uint8_t GPS::_message_VALSET_PM_BBR[] = {0x01, 0x02, 0x00, 0x00, 0x0F, 0x00, 0x31, 0x10, 0x00, 0x01, 0x00, 0xD0, 0x20, 0x01,
+ 0x02, 0x00, 0xD0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xD0, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x05, 0x00, 0xD0, 0x30, 0x00, 0x00, 0x0D, 0x00, 0xD0, 0x10, 0x01};
/*
CFG-ITFM replaced by 5 valset messages which can be combined into one for RAM and one for BBR
@@ -402,23 +406,28 @@ const uint8_t GPS::_message_VALSET_DISABLE_NMEA_BBR[] = {0x00, 0x02, 0x00, 0x00,
// BBR layer config message:
// b5 62 06 8a 09 00 00 02 00 00 07 00 92 20 06 5a 58
-// Turn NMEA GSA, GGA, RMC messages on:
-// Ram layer config message:
-// b5 62 06 8a 13 00 00 01 00 00 c0 00 91 20 01 bb 00 91 20 01 ac 00 91 20 01 e1 3b
-
-// BBR layer config message:
-// b5 62 06 8a 13 00 00 02 00 00 c0 00 91 20 01 bb 00 91 20 01 ac 00 91 20 01 e2 4d
+// Turn NMEA GGA, RMC messages on:
+// Layer config messages:
+// RAM:
+// b5 62 06 8a 0e 00 00 01 00 00 bb 00 91 20 01 ac 00 91 20 01 6a 8f
+// BBR:
+// b5 62 06 8a 0e 00 00 02 00 00 bb 00 91 20 01 ac 00 91 20 01 6b 9c
+// FLASH:
+// b5 62 06 8a 0e 00 00 04 00 00 bb 00 91 20 01 ac 00 91 20 01 6d b6
+// Doing this for the FLASH layer isn't really required since we save the config to flash later
const uint8_t GPS::_message_VALSET_DISABLE_TXT_INFO_RAM[] = {0x00, 0x01, 0x00, 0x00, 0x07, 0x00, 0x92, 0x20, 0x03};
const uint8_t GPS::_message_VALSET_DISABLE_TXT_INFO_BBR[] = {0x00, 0x02, 0x00, 0x00, 0x07, 0x00, 0x92, 0x20, 0x03};
-const uint8_t GPS::_message_VALSET_ENABLE_NMEA_RAM[] = {0x00, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x91, 0x20, 0x01, 0xbb,
- 0x00, 0x91, 0x20, 0x01, 0xac, 0x00, 0x91, 0x20, 0x01};
-const uint8_t GPS::_message_VALSET_ENABLE_NMEA_BBR[] = {0x00, 0x02, 0x00, 0x00, 0xc0, 0x00, 0x91, 0x20, 0x01, 0xbb,
- 0x00, 0x91, 0x20, 0x01, 0xac, 0x00, 0x91, 0x20, 0x01};
+
+const uint8_t GPS::_message_VALSET_ENABLE_NMEA_RAM[] = {0x00, 0x01, 0x00, 0x00, 0xbb, 0x00, 0x91,
+ 0x20, 0x01, 0xac, 0x00, 0x91, 0x20, 0x01};
+const uint8_t GPS::_message_VALSET_ENABLE_NMEA_BBR[] = {0x00, 0x02, 0x00, 0x00, 0xbb, 0x00, 0x91,
+ 0x20, 0x01, 0xac, 0x00, 0x91, 0x20, 0x01};
const uint8_t GPS::_message_VALSET_DISABLE_SBAS_RAM[] = {0x00, 0x01, 0x00, 0x00, 0x20, 0x00, 0x31,
0x10, 0x00, 0x05, 0x00, 0x31, 0x10, 0x00};
const uint8_t GPS::_message_VALSET_DISABLE_SBAS_BBR[] = {0x00, 0x02, 0x00, 0x00, 0x20, 0x00, 0x31,
0x10, 0x00, 0x05, 0x00, 0x31, 0x10, 0x00};
+
/*
Operational issues with the M10:
diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp
index 04915fe07..bbc12521a 100644
--- a/src/graphics/EInkDisplay2.cpp
+++ b/src/graphics/EInkDisplay2.cpp
@@ -62,12 +62,19 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
return false;
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
+ const bool flipped = config.display.flip_screen;
for (uint32_t y = 0; y < displayHeight; y++) {
for (uint32_t x = 0; x < displayWidth; x++) {
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficient
auto b = buffer[x + (y / 8) * displayWidth];
auto isset = b & (1 << (y & 7));
- adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
+
+ // Handle flip here, rather than with setRotation(),
+ // Avoids issues when display width is not a multiple of 8
+ if (flipped)
+ adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
+ else
+ adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
}
}
diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp
index b396446fa..5b97b8d48 100644
--- a/src/graphics/EInkDynamicDisplay.cpp
+++ b/src/graphics/EInkDynamicDisplay.cpp
@@ -534,6 +534,10 @@ void EInkDynamicDisplay::checkBusyAsyncRefresh()
return;
}
+
+ // Async refresh appears to have stopped, but wasn't caught by onNotify()
+ else
+ pollAsyncRefresh(); // Check (and terminate) the async refresh manually
}
// Hold control while an async refresh runs
diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h
index 8f3ce205a..9e131dca7 100644
--- a/src/graphics/EInkDynamicDisplay.h
+++ b/src/graphics/EInkDynamicDisplay.h
@@ -109,6 +109,7 @@ class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWo
refreshTypes currentConfig = FULL; // Which refresh type is GxEPD2 currently configured for
// Optional - track ghosting, pixel by pixel
+ // May 2024: no longer used by any display. Kept for possible future use.
#ifdef EINK_LIMIT_GHOSTING_PX
void countGhostPixels(); // Count any pixels which have moved from black to white since last full-refresh
void checkExcessiveGhosting(); // Check if ghosting exceeds defined limit
diff --git a/src/graphics/NeoPixel.h b/src/graphics/NeoPixel.h
new file mode 100644
index 000000000..dde74366e
--- /dev/null
+++ b/src/graphics/NeoPixel.h
@@ -0,0 +1,4 @@
+#ifdef HAS_NEOPIXEL
+#include
+extern Adafruit_NeoPixel pixels;
+#endif
\ No newline at end of file
diff --git a/src/graphics/PointStruct.h b/src/graphics/PointStruct.h
new file mode 100644
index 000000000..218731978
--- /dev/null
+++ b/src/graphics/PointStruct.h
@@ -0,0 +1,4 @@
+struct PointStruct {
+ int x;
+ int y;
+};
\ No newline at end of file
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index e5f392036..60168cffc 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -94,6 +94,11 @@ std::vector moduleFrames;
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
static char ourId[5];
+// vector where symbols (string) are displayed in bottom corner of display.
+std::vector functionSymbals;
+// string displayed in bottom right corner of display. Created from elements in functionSymbals vector
+std::string functionSymbalString = "";
+
#if HAS_GPS
// GeoCoord object for the screen
GeoCoord geoCoord;
@@ -260,6 +265,42 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i
#endif
}
+// draw overlay in bottom right corner of screen to show when notifications are muted or modifier key is active
+static void drawFunctionOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
+{
+ // LOG_DEBUG("Drawing function overlay\n");
+ if (functionSymbals.begin() != functionSymbals.end()) {
+ char buf[64];
+ display->setFont(FONT_SMALL);
+ snprintf(buf, sizeof(buf), "%s", functionSymbalString.c_str());
+ display->drawString(SCREEN_WIDTH - display->getStringWidth(buf), SCREEN_HEIGHT - FONT_HEIGHT_SMALL, buf);
+ }
+}
+
+/// Check if the display can render a string (detect special chars; emoji)
+static bool haveGlyphs(const char *str)
+{
+#if defined(OLED_UA) || defined(OLED_RU)
+ // Don't want to make any assumptions about custom language support
+ return true;
+#endif
+
+ // Check each character with the lookup function for the OLED library
+ // We're not really meant to use this directly..
+ bool have = true;
+ for (uint16_t i = 0; i < strlen(str); i++) {
+ uint8_t result = Screen::customFontTableLookup((uint8_t)str[i]);
+ // If font doesn't support a character, it is substituted for ¿
+ if (result == 191 && (uint8_t)str[i] != 191) {
+ have = false;
+ break;
+ }
+ }
+
+ LOG_DEBUG("haveGlyphs=%d\n", have);
+ return have;
+}
+
#ifdef USE_EINK
/// Used on eink displays while in deep sleep
static void drawDeepSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
@@ -284,14 +325,15 @@ static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *sta
display->setTextAlignment(TEXT_ALIGN_LEFT);
const char *pauseText = "Screen Paused";
const char *idText = owner.short_name;
+ const bool useId = haveGlyphs(idText); // This bool is used to hide the idText box if we can't render the short name
constexpr uint16_t padding = 5;
constexpr uint8_t dividerGap = 1;
constexpr uint8_t imprecision = 5; // How far the box origins can drift from center. Combat burn-in.
// Dimensions
- const uint16_t idTextWidth = display->getStringWidth(idText, strlen(idText));
+ const uint16_t idTextWidth = display->getStringWidth(idText, strlen(idText), true); // "true": handle utf8 chars
const uint16_t pauseTextWidth = display->getStringWidth(pauseText, strlen(pauseText));
- const uint16_t boxWidth = padding + idTextWidth + padding + padding + pauseTextWidth + padding;
+ const uint16_t boxWidth = padding + (useId ? idTextWidth + padding + padding : 0) + pauseTextWidth + padding;
const uint16_t boxHeight = padding + FONT_HEIGHT_SMALL + padding;
// Position
@@ -301,7 +343,7 @@ static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *sta
const int16_t boxBottom = boxTop + boxHeight - 1;
const int16_t idTextLeft = boxLeft + padding;
const int16_t idTextTop = boxTop + padding;
- const int16_t pauseTextLeft = boxLeft + padding + idTextWidth + padding + padding;
+ const int16_t pauseTextLeft = boxLeft + (useId ? padding + idTextWidth + padding : 0) + padding;
const int16_t pauseTextTop = boxTop + padding;
const int16_t dividerX = boxLeft + padding + idTextWidth + padding;
const int16_t dividerTop = boxTop + 1 + dividerGap;
@@ -314,12 +356,14 @@ static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *sta
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
// Draw: Text
- display->drawString(idTextLeft, idTextTop, idText);
+ if (useId)
+ display->drawString(idTextLeft, idTextTop, idText);
display->drawString(pauseTextLeft, pauseTextTop, pauseText);
display->drawString(pauseTextLeft + 1, pauseTextTop, pauseText); // Faux bold
// Draw: divider
- display->drawLine(dividerX, dividerTop, dividerX, dividerBottom);
+ if (useId)
+ display->drawLine(dividerX, dividerTop, dividerX, dividerBottom);
}
#endif
@@ -402,6 +446,536 @@ static bool shouldDrawMessage(const meshtastic_MeshPacket *packet)
return packet->from != 0 && !moduleConfig.store_forward.enabled;
}
+// Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage.
+static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, const PowerStatus *powerStatus)
+{
+ static const uint8_t powerBar[3] = {0x81, 0xBD, 0xBD};
+ static const uint8_t lightning[8] = {0xA1, 0xA1, 0xA5, 0xAD, 0xB5, 0xA5, 0x85, 0x85};
+ // Clear the bar area on the battery image
+ for (int i = 1; i < 14; i++) {
+ imgBuffer[i] = 0x81;
+ }
+ // If charging, draw a charging indicator
+ if (powerStatus->getIsCharging()) {
+ memcpy(imgBuffer + 3, lightning, 8);
+ // If not charging, Draw power bars
+ } else {
+ for (int i = 0; i < 4; i++) {
+ if (powerStatus->getBatteryChargePercent() >= 25 * i)
+ memcpy(imgBuffer + 1 + (i * 3), powerBar, 3);
+ }
+ }
+ display->drawFastImage(x, y, 16, 8, imgBuffer);
+}
+
+#ifdef T_WATCH_S3
+
+void Screen::drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode, float scale)
+{
+ uint16_t segmentWidth = SEGMENT_WIDTH * scale;
+ uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
+
+ if (digitalMode) {
+ uint16_t radius = (segmentWidth + (segmentHeight * 2) + 4) / 2;
+ uint16_t centerX = (x + segmentHeight + 2) + (radius / 2);
+ uint16_t centerY = (y + segmentHeight + 2) + (radius / 2);
+
+ display->drawCircle(centerX, centerY, radius);
+ display->drawCircle(centerX, centerY, radius + 1);
+ display->drawLine(centerX, centerY, centerX, centerY - radius + 3);
+ display->drawLine(centerX, centerY, centerX + radius - 3, centerY);
+ } else {
+ uint16_t segmentOneX = x + segmentHeight + 2;
+ uint16_t segmentOneY = y;
+
+ uint16_t segmentTwoX = segmentOneX + segmentWidth + 2;
+ uint16_t segmentTwoY = segmentOneY + segmentHeight + 2;
+
+ uint16_t segmentThreeX = segmentOneX;
+ uint16_t segmentThreeY = segmentTwoY + segmentWidth + 2;
+
+ uint16_t segmentFourX = x;
+ uint16_t segmentFourY = y + segmentHeight + 2;
+
+ drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight);
+ drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight);
+ drawHorizontalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight);
+ drawVerticalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight);
+ }
+}
+
+// Draw a digital clock
+void Screen::drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
+{
+ display->setTextAlignment(TEXT_ALIGN_LEFT);
+
+ drawBattery(display, x, y + 7, imgBattery, powerStatus);
+
+ if (powerStatus->getHasBattery()) {
+ String batteryPercent = String(powerStatus->getBatteryChargePercent()) + "%";
+
+ display->setFont(FONT_SMALL);
+
+ display->drawString(x + 20, y + 2, batteryPercent);
+ }
+
+ if (nimbleBluetooth->isConnected()) {
+ drawBluetoothConnectedIcon(display, display->getWidth() - 18, y + 2);
+ }
+
+ drawWatchFaceToggleButton(display, display->getWidth() - 36, display->getHeight() - 36, screen->digitalWatchFace, 1);
+
+ display->setColor(OLEDDISPLAY_COLOR::WHITE);
+
+ uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
+ if (rtc_sec > 0) {
+ long hms = rtc_sec % SEC_PER_DAY;
+ hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
+
+ int hour = hms / SEC_PER_HOUR;
+ int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
+ int second = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
+
+ hour = hour > 12 ? hour - 12 : hour;
+
+ if (hour == 0) {
+ hour = 12;
+ }
+
+ // hours string
+ String hourString = String(hour);
+
+ // minutes string
+ String minuteString = minute < 10 ? "0" + String(minute) : String(minute);
+
+ String timeString = hourString + ":" + minuteString;
+
+ // seconds string
+ String secondString = second < 10 ? "0" + String(second) : String(second);
+
+ float scale = 1.5;
+
+ uint16_t segmentWidth = SEGMENT_WIDTH * scale;
+ uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
+
+ // calculate hours:minutes string width
+ uint16_t timeStringWidth = timeString.length() * 5;
+
+ for (uint8_t i = 0; i < timeString.length(); i++) {
+ String character = String(timeString[i]);
+
+ if (character == ":") {
+ timeStringWidth += segmentHeight;
+ } else {
+ timeStringWidth += segmentWidth + (segmentHeight * 2) + 4;
+ }
+ }
+
+ // calculate seconds string width
+ uint16_t secondStringWidth = (secondString.length() * 12) + 4;
+
+ // sum these to get total string width
+ uint16_t totalWidth = timeStringWidth + secondStringWidth;
+
+ uint16_t hourMinuteTextX = (display->getWidth() / 2) - (totalWidth / 2);
+
+ uint16_t startingHourMinuteTextX = hourMinuteTextX;
+
+ uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2);
+
+ // iterate over characters in hours:minutes string and draw segmented characters
+ for (uint8_t i = 0; i < timeString.length(); i++) {
+ String character = String(timeString[i]);
+
+ if (character == ":") {
+ drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale);
+
+ hourMinuteTextX += segmentHeight + 6;
+ } else {
+ drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character.toInt(), scale);
+
+ hourMinuteTextX += segmentWidth + (segmentHeight * 2) + 4;
+ }
+
+ hourMinuteTextX += 5;
+ }
+
+ // draw seconds string
+ display->setFont(FONT_MEDIUM);
+ display->drawString(startingHourMinuteTextX + timeStringWidth + 4,
+ (display->getHeight() - hourMinuteTextY) - FONT_HEIGHT_MEDIUM + 6, secondString);
+ }
+}
+
+void Screen::drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale)
+{
+ uint16_t segmentWidth = SEGMENT_WIDTH * scale;
+ uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
+
+ uint16_t cellHeight = (segmentWidth * 2) + (segmentHeight * 3) + 8;
+
+ uint16_t topAndBottomX = x + (4 * scale);
+
+ uint16_t quarterCellHeight = cellHeight / 4;
+
+ uint16_t topY = y + quarterCellHeight;
+ uint16_t bottomY = y + (quarterCellHeight * 3);
+
+ display->fillRect(topAndBottomX, topY, segmentHeight, segmentHeight);
+ display->fillRect(topAndBottomX, bottomY, segmentHeight, segmentHeight);
+}
+
+void Screen::drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale)
+{
+ // the numbers 0-9, each expressed as an array of seven boolean (0|1) values encoding the on/off state of
+ // segment {innerIndex + 1}
+ // e.g., to display the numeral '0', segments 1-6 are on, and segment 7 is off.
+ uint8_t numbers[10][7] = {
+ {1, 1, 1, 1, 1, 1, 0}, // 0 Display segment key
+ {0, 1, 1, 0, 0, 0, 0}, // 1 1
+ {1, 1, 0, 1, 1, 0, 1}, // 2 ___
+ {1, 1, 1, 1, 0, 0, 1}, // 3 6 | | 2
+ {0, 1, 1, 0, 0, 1, 1}, // 4 |_7̲_|
+ {1, 0, 1, 1, 0, 1, 1}, // 5 5 | | 3
+ {1, 0, 1, 1, 1, 1, 1}, // 6 |___|
+ {1, 1, 1, 0, 0, 1, 0}, // 7
+ {1, 1, 1, 1, 1, 1, 1}, // 8 4
+ {1, 1, 1, 1, 0, 1, 1}, // 9
+ };
+
+ // the width and height of each segment's central rectangle:
+ // _____________________
+ // ⋰| (only this part, |⋱
+ // ⋰ | not including | ⋱
+ // ⋱ | the triangles | ⋰
+ // ⋱| on the ends) |⋰
+ // ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
+
+ uint16_t segmentWidth = SEGMENT_WIDTH * scale;
+ uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
+
+ // segment x and y coordinates
+ uint16_t segmentOneX = x + segmentHeight + 2;
+ uint16_t segmentOneY = y;
+
+ uint16_t segmentTwoX = segmentOneX + segmentWidth + 2;
+ uint16_t segmentTwoY = segmentOneY + segmentHeight + 2;
+
+ uint16_t segmentThreeX = segmentTwoX;
+ uint16_t segmentThreeY = segmentTwoY + segmentWidth + 2 + segmentHeight + 2;
+
+ uint16_t segmentFourX = segmentOneX;
+ uint16_t segmentFourY = segmentThreeY + segmentWidth + 2;
+
+ uint16_t segmentFiveX = x;
+ uint16_t segmentFiveY = segmentThreeY;
+
+ uint16_t segmentSixX = x;
+ uint16_t segmentSixY = segmentTwoY;
+
+ uint16_t segmentSevenX = segmentOneX;
+ uint16_t segmentSevenY = segmentTwoY + segmentWidth + 2;
+
+ if (numbers[number][0]) {
+ drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight);
+ }
+
+ if (numbers[number][1]) {
+ drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight);
+ }
+
+ if (numbers[number][2]) {
+ drawVerticalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight);
+ }
+
+ if (numbers[number][3]) {
+ drawHorizontalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight);
+ }
+
+ if (numbers[number][4]) {
+ drawVerticalSegment(display, segmentFiveX, segmentFiveY, segmentWidth, segmentHeight);
+ }
+
+ if (numbers[number][5]) {
+ drawVerticalSegment(display, segmentSixX, segmentSixY, segmentWidth, segmentHeight);
+ }
+
+ if (numbers[number][6]) {
+ drawHorizontalSegment(display, segmentSevenX, segmentSevenY, segmentWidth, segmentHeight);
+ }
+}
+
+void Screen::drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height)
+{
+ int halfHeight = height / 2;
+
+ // draw central rectangle
+ display->fillRect(x, y, width, height);
+
+ // draw end triangles
+ display->fillTriangle(x, y, x, y + height - 1, x - halfHeight, y + halfHeight);
+
+ display->fillTriangle(x + width, y, x + width + halfHeight, y + halfHeight, x + width, y + height - 1);
+}
+
+void Screen::drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int height)
+{
+ int halfHeight = height / 2;
+
+ // draw central rectangle
+ display->fillRect(x, y, height, width);
+
+ // draw end triangles
+ display->fillTriangle(x + halfHeight, y - halfHeight, x + height - 1, y, x, y);
+
+ display->fillTriangle(x, y + width, x + height - 1, y + width, x + halfHeight, y + width + halfHeight);
+}
+
+void Screen::drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
+{
+ display->drawFastImage(x, y, 18, 14, bluetoothConnectedIcon);
+}
+
+// Draw an analog clock
+void Screen::drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
+{
+ display->setTextAlignment(TEXT_ALIGN_LEFT);
+
+ drawBattery(display, x, y + 7, imgBattery, powerStatus);
+
+ if (powerStatus->getHasBattery()) {
+ String batteryPercent = String(powerStatus->getBatteryChargePercent()) + "%";
+
+ display->setFont(FONT_SMALL);
+
+ display->drawString(x + 20, y + 2, batteryPercent);
+ }
+
+ if (nimbleBluetooth->isConnected()) {
+ drawBluetoothConnectedIcon(display, display->getWidth() - 18, y + 2);
+ }
+
+ drawWatchFaceToggleButton(display, display->getWidth() - 36, display->getHeight() - 36, screen->digitalWatchFace, 1);
+
+ // clock face center coordinates
+ int16_t centerX = display->getWidth() / 2;
+ int16_t centerY = display->getHeight() / 2;
+
+ // clock face radius
+ int16_t radius = (display->getWidth() / 2) * 0.8;
+
+ // noon (0 deg) coordinates (outermost circle)
+ int16_t noonX = centerX;
+ int16_t noonY = centerY - radius;
+
+ // second hand radius and y coordinate (outermost circle)
+ int16_t secondHandNoonY = noonY + 1;
+
+ // tick mark outer y coordinate; (first nested circle)
+ int16_t tickMarkOuterNoonY = secondHandNoonY;
+
+ // seconds tick mark inner y coordinate; (second nested circle)
+ double secondsTickMarkInnerNoonY = (double)noonY + 8;
+
+ // hours tick mark inner y coordinate; (third nested circle)
+ double hoursTickMarkInnerNoonY = (double)noonY + 16;
+
+ // minute hand y coordinate
+ int16_t minuteHandNoonY = secondsTickMarkInnerNoonY + 4;
+
+ // hour string y coordinate
+ int16_t hourStringNoonY = minuteHandNoonY + 18;
+
+ // hour hand radius and y coordinate
+ int16_t hourHandRadius = radius * 0.55;
+ int16_t hourHandNoonY = centerY - hourHandRadius;
+
+ display->setColor(OLEDDISPLAY_COLOR::WHITE);
+ display->drawCircle(centerX, centerY, radius);
+
+ uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
+ if (rtc_sec > 0) {
+ long hms = rtc_sec % SEC_PER_DAY;
+ hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
+
+ // Tear apart hms into h:m:s
+ int hour = hms / SEC_PER_HOUR;
+ int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
+ int second = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
+
+ hour = hour > 12 ? hour - 12 : hour;
+
+ int16_t degreesPerHour = 30;
+ int16_t degreesPerMinuteOrSecond = 6;
+
+ double hourBaseAngle = hour * degreesPerHour;
+ double hourAngleOffset = ((double)minute / 60) * degreesPerHour;
+ double hourAngle = radians(hourBaseAngle + hourAngleOffset);
+
+ double minuteBaseAngle = minute * degreesPerMinuteOrSecond;
+ double minuteAngleOffset = ((double)second / 60) * degreesPerMinuteOrSecond;
+ double minuteAngle = radians(minuteBaseAngle + minuteAngleOffset);
+
+ double secondAngle = radians(second * degreesPerMinuteOrSecond);
+
+ double hourX = sin(-hourAngle) * (hourHandNoonY - centerY) + noonX;
+ double hourY = cos(-hourAngle) * (hourHandNoonY - centerY) + centerY;
+
+ double minuteX = sin(-minuteAngle) * (minuteHandNoonY - centerY) + noonX;
+ double minuteY = cos(-minuteAngle) * (minuteHandNoonY - centerY) + centerY;
+
+ double secondX = sin(-secondAngle) * (secondHandNoonY - centerY) + noonX;
+ double secondY = cos(-secondAngle) * (secondHandNoonY - centerY) + centerY;
+
+ display->setFont(FONT_MEDIUM);
+
+ // draw minute and hour tick marks and hour numbers
+ for (uint16_t angle = 0; angle < 360; angle += 6) {
+ double angleInRadians = radians(angle);
+
+ double sineAngleInRadians = sin(-angleInRadians);
+ double cosineAngleInRadians = cos(-angleInRadians);
+
+ double endX = sineAngleInRadians * (tickMarkOuterNoonY - centerY) + noonX;
+ double endY = cosineAngleInRadians * (tickMarkOuterNoonY - centerY) + centerY;
+
+ if (angle % degreesPerHour == 0) {
+ double startX = sineAngleInRadians * (hoursTickMarkInnerNoonY - centerY) + noonX;
+ double startY = cosineAngleInRadians * (hoursTickMarkInnerNoonY - centerY) + centerY;
+
+ // draw hour tick mark
+ display->drawLine(startX, startY, endX, endY);
+
+ static char buffer[2];
+
+ uint8_t hourInt = (angle / 30);
+
+ if (hourInt == 0) {
+ hourInt = 12;
+ }
+
+ // hour number x offset needs to be adjusted for some cases
+ int8_t hourStringXOffset;
+ int8_t hourStringYOffset = 13;
+
+ switch (hourInt) {
+ case 3:
+ hourStringXOffset = 5;
+ break;
+ case 9:
+ hourStringXOffset = 7;
+ break;
+ case 10:
+ case 11:
+ hourStringXOffset = 8;
+ break;
+ case 12:
+ hourStringXOffset = 13;
+ break;
+ default:
+ hourStringXOffset = 6;
+ break;
+ }
+
+ double hourStringX = (sineAngleInRadians * (hourStringNoonY - centerY) + noonX) - hourStringXOffset;
+ double hourStringY = (cosineAngleInRadians * (hourStringNoonY - centerY) + centerY) - hourStringYOffset;
+
+ // draw hour number
+ display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
+ }
+
+ if (angle % degreesPerMinuteOrSecond == 0) {
+ double startX = sineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + noonX;
+ double startY = cosineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + centerY;
+
+ // draw minute tick mark
+ display->drawLine(startX, startY, endX, endY);
+ }
+ }
+
+ // draw hour hand
+ display->drawLine(centerX, centerY, hourX, hourY);
+
+ // draw minute hand
+ display->drawLine(centerX, centerY, minuteX, minuteY);
+
+ // draw second hand
+ display->drawLine(centerX, centerY, secondX, secondY);
+ }
+}
+
+#endif
+
+// Get an absolute time from "seconds ago" info. Returns false if no valid timestamp possible
+bool deltaToTimestamp(uint32_t secondsAgo, uint8_t *hours, uint8_t *minutes, int32_t *daysAgo)
+{
+ // Cache the result - avoid frequent recalculation
+ static uint8_t hoursCached = 0, minutesCached = 0;
+ static uint32_t daysAgoCached = 0;
+ static uint32_t secondsAgoCached = 0;
+ static bool validCached = false;
+
+ // Abort: if timezone not set
+ if (strlen(config.device.tzdef) == 0) {
+ validCached = false;
+ return validCached;
+ }
+
+ // Abort: if invalid pointers passed
+ if (hours == nullptr || minutes == nullptr || daysAgo == nullptr) {
+ validCached = false;
+ return validCached;
+ }
+
+ // Abort: if time seems invalid.. (> 6 months ago, probably seen before RTC set)
+ if (secondsAgo > SEC_PER_DAY * 30UL * 6) {
+ validCached = false;
+ return validCached;
+ }
+
+ // If repeated request, don't bother recalculating
+ if (secondsAgo - secondsAgoCached < 60 && secondsAgoCached != 0) {
+ if (validCached) {
+ *hours = hoursCached;
+ *minutes = minutesCached;
+ *daysAgo = daysAgoCached;
+ }
+ return validCached;
+ }
+
+ // Get local time
+ uint32_t secondsRTC = getValidTime(RTCQuality::RTCQualityDevice, true); // Get local time
+
+ // Abort: if RTC not set
+ if (!secondsRTC) {
+ validCached = false;
+ return validCached;
+ }
+
+ // Get absolute time when last seen
+ uint32_t secondsSeenAt = secondsRTC - secondsAgo;
+
+ // Calculate daysAgo
+ *daysAgo = (secondsRTC / SEC_PER_DAY) - (secondsSeenAt / SEC_PER_DAY); // How many "midnights" have passed
+
+ // Get seconds since midnight
+ uint32_t hms = (secondsRTC - secondsAgo) % SEC_PER_DAY;
+ hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
+
+ // Tear apart hms into hours and minutes
+ *hours = hms / SEC_PER_HOUR;
+ *minutes = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
+
+ // Cache the result
+ daysAgoCached = *daysAgo;
+ hoursCached = *hours;
+ minutesCached = *minutes;
+ secondsAgoCached = secondsAgo;
+
+ validCached = true;
+ return validCached;
+}
+
/// Draw the last text message we received
static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
@@ -423,22 +997,98 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
display->setColor(BLACK);
}
+ // For time delta
uint32_t seconds = sinceReceived(&mp);
uint32_t minutes = seconds / 60;
uint32_t hours = minutes / 60;
uint32_t days = hours / 24;
- if (config.display.heading_bold) {
- display->drawStringf(1 + x, 0 + y, tempBuf, "%s ago from %s",
- screen->drawTimeDelta(days, hours, minutes, seconds).c_str(),
- (node && node->has_user) ? node->user.short_name : "???");
+ // For timestamp
+ uint8_t timestampHours, timestampMinutes;
+ int32_t daysAgo;
+ bool useTimestamp = deltaToTimestamp(seconds, ×tampHours, ×tampMinutes, &daysAgo);
+
+ // If bold, draw twice, shifting right by one pixel
+ for (uint8_t xOff = 0; xOff <= (config.display.heading_bold ? 1 : 0); xOff++) {
+ // Show a timestamp if received today, but longer than 15 minutes ago
+ if (useTimestamp && minutes >= 15 && daysAgo == 0) {
+ display->drawStringf(xOff + x, 0 + y, tempBuf, "At %02hu:%02hu from %s", timestampHours, timestampMinutes,
+ (node && node->has_user) ? node->user.short_name : "???");
+ }
+ // Timestamp yesterday (if display is wide enough)
+ else if (useTimestamp && daysAgo == 1 && display->width() >= 200) {
+ display->drawStringf(xOff + x, 0 + y, tempBuf, "Yesterday %02hu:%02hu from %s", timestampHours, timestampMinutes,
+ (node && node->has_user) ? node->user.short_name : "???");
+ }
+ // Otherwise, show a time delta
+ else {
+ display->drawStringf(xOff + x, 0 + y, tempBuf, "%s ago from %s",
+ screen->drawTimeDelta(days, hours, minutes, seconds).c_str(),
+ (node && node->has_user) ? node->user.short_name : "???");
+ }
}
- display->drawStringf(0 + x, 0 + y, tempBuf, "%s ago from %s", screen->drawTimeDelta(days, hours, minutes, seconds).c_str(),
- (node && node->has_user) ? node->user.short_name : "???");
display->setColor(WHITE);
+#ifndef EXCLUDE_EMOJI
+ if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44D") == 0) {
+ display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2,
+ y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height,
+ thumbup);
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44E") == 0) {
+ display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2,
+ y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height,
+ thumbdown);
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"❓") == 0) {
+ display->drawXbm(x + (SCREEN_WIDTH - question_width) / 2,
+ y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - question_height) / 2 + 2 + 5, question_width, question_height,
+ question);
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"‼️") == 0) {
+ display->drawXbm(x + (SCREEN_WIDTH - bang_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - bang_height) / 2 + 2 + 5,
+ bang_width, bang_height, bang);
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F4A9") == 0) {
+ display->drawXbm(x + (SCREEN_WIDTH - poo_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - poo_height) / 2 + 2 + 5,
+ poo_width, poo_height, poo);
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\xf0\x9f\xa4\xa3") == 0) {
+ display->drawXbm(x + (SCREEN_WIDTH - haha_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - haha_height) / 2 + 2 + 5,
+ haha_width, haha_height, haha);
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44B") == 0) {
+ display->drawXbm(x + (SCREEN_WIDTH - wave_icon_width) / 2,
+ y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - wave_icon_height) / 2 + 2 + 5, wave_icon_width,
+ wave_icon_height, wave_icon);
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F920") == 0) {
+ display->drawXbm(x + (SCREEN_WIDTH - cowboy_width) / 2,
+ y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - cowboy_height) / 2 + 2 + 5, cowboy_width, cowboy_height,
+ cowboy);
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F42D") == 0) {
+ display->drawXbm(x + (SCREEN_WIDTH - deadmau5_width) / 2,
+ y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - deadmau5_height) / 2 + 2 + 5, deadmau5_width, deadmau5_height,
+ deadmau5);
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\xE2\x98\x80\xEF\xB8\x8F") == 0) {
+ display->drawXbm(x + (SCREEN_WIDTH - sun_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - sun_height) / 2 + 2 + 5,
+ sun_width, sun_height, sun);
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\u2614") == 0) {
+ display->drawXbm(x + (SCREEN_WIDTH - rain_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - rain_height) / 2 + 2 + 10,
+ rain_width, rain_height, rain);
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"☁️") == 0) {
+ display->drawXbm(x + (SCREEN_WIDTH - cloud_width) / 2,
+ y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - cloud_height) / 2 + 2 + 5, cloud_width, cloud_height, cloud);
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"🌫️") == 0) {
+ display->drawXbm(x + (SCREEN_WIDTH - fog_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - fog_height) / 2 + 2 + 5,
+ fog_width, fog_height, fog);
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\xf0\x9f\x98\x88") == 0) {
+ display->drawXbm(x + (SCREEN_WIDTH - devil_width) / 2,
+ y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - devil_height) / 2 + 2 + 5, devil_width, devil_height, devil);
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"♥️") == 0) {
+ display->drawXbm(x + (SCREEN_WIDTH - heart_width) / 2,
+ y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - heart_height) / 2 + 2 + 5, heart_width, heart_height, heart);
+ } else {
+ snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes);
+ display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf);
+ }
+#else
snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes);
display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf);
+#endif
}
/// Draw the last waypoint we received
@@ -501,28 +1151,6 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char *
}
}
-// Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage.
-static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, const PowerStatus *powerStatus)
-{
- static const uint8_t powerBar[3] = {0x81, 0xBD, 0xBD};
- static const uint8_t lightning[8] = {0xA1, 0xA1, 0xA5, 0xAD, 0xB5, 0xA5, 0x85, 0x85};
- // Clear the bar area on the battery image
- for (int i = 1; i < 14; i++) {
- imgBuffer[i] = 0x81;
- }
- // If charging, draw a charging indicator
- if (powerStatus->getIsCharging()) {
- memcpy(imgBuffer + 3, lightning, 8);
- // If not charging, Draw power bars
- } else {
- for (int i = 0; i < 4; i++) {
- if (powerStatus->getBatteryChargePercent() >= 25 * i)
- memcpy(imgBuffer + 1 + (i * 3), powerBar, 3);
- }
- }
- display->drawFastImage(x, y, 16, 8, imgBuffer);
-}
-
// Draw nodes status
static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStatus *nodeStatus)
{
@@ -858,23 +1486,42 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
const char *username = node->has_user ? node->user.long_name : "Unknown Name";
static char signalStr[20];
- snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100));
+
+ // section here to choose whether to display hops away rather than signal strength if more than 0 hops away.
+ if (node->hops_away > 0) {
+ snprintf(signalStr, sizeof(signalStr), "Hops Away: %d", node->hops_away);
+ } else {
+ snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100));
+ }
uint32_t agoSecs = sinceLastSeen(node);
static char lastStr[20];
+
+ // Use an absolute timestamp in some cases.
+ // Particularly useful with E-Ink displays. Static UI, fewer refreshes.
+ uint8_t timestampHours, timestampMinutes;
+ int32_t daysAgo;
+ bool useTimestamp = deltaToTimestamp(agoSecs, ×tampHours, ×tampMinutes, &daysAgo);
+
if (agoSecs < 120) // last 2 mins?
snprintf(lastStr, sizeof(lastStr), "%u seconds ago", agoSecs);
+ // -- if suitable for timestamp --
+ else if (useTimestamp && agoSecs < 15 * SECONDS_IN_MINUTE) // Last 15 minutes
+ snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / SECONDS_IN_MINUTE);
+ else if (useTimestamp && daysAgo == 0) // Today
+ snprintf(lastStr, sizeof(lastStr), "Last seen: %02u:%02u", (unsigned int)timestampHours, (unsigned int)timestampMinutes);
+ else if (useTimestamp && daysAgo == 1) // Yesterday
+ snprintf(lastStr, sizeof(lastStr), "Seen yesterday");
+ else if (useTimestamp && daysAgo > 1) // Last six months (capped by deltaToTimestamp method)
+ snprintf(lastStr, sizeof(lastStr), "%li days ago", (long)daysAgo);
+ // -- if using time delta instead --
else if (agoSecs < 120 * 60) // last 2 hrs
snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / 60);
- else {
- // Only show hours ago if it's been less than 6 months. Otherwise, we may have bad
- // data.
- if ((agoSecs / 60 / 60) < (hours_in_month * 6)) {
- snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60);
- } else {
- snprintf(lastStr, sizeof(lastStr), "unknown age");
- }
- }
+ // Only show hours ago if it's been less than 6 months. Otherwise, we may have bad data.
+ else if ((agoSecs / 60 / 60) < (hours_in_month * 6))
+ snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60);
+ else
+ snprintf(lastStr, sizeof(lastStr), "unknown age");
static char distStr[20];
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
@@ -883,7 +1530,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
strncpy(distStr, "? km", sizeof(distStr));
}
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
- const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
+ const char *fields[] = {username, lastStr, signalStr, distStr, NULL};
int16_t compassX = 0, compassY = 0;
// coordinates for the center of the compass/circle
@@ -896,9 +1543,13 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
}
bool hasNodeHeading = false;
- if (ourNode && hasValidPosition(ourNode)) {
+ if (ourNode && (hasValidPosition(ourNode) || screen->hasHeading())) {
const meshtastic_PositionLite &op = ourNode->position;
- float myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
+ float myHeading;
+ if (screen->hasHeading())
+ myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians
+ else
+ myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
drawCompassNorth(display, compassX, compassY, myHeading);
if (hasValidPosition(node)) {
@@ -1023,7 +1674,14 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
#if !ARCH_PORTDUINO
dispdev->displayOn();
#endif
+
+#if defined(ST7789_CS) && \
+ !defined(M5STACK) // set display brightness when turning on screens. Just moved function from TFTDisplay to here.
+ static_cast(dispdev)->setDisplayBrightness(brightness);
+#endif
+
dispdev->displayOn();
+
enabled = true;
setInterval(0); // Draw ASAP
runASAP = true;
@@ -1194,6 +1852,10 @@ int32_t Screen::runOnce()
return RUN_SAME;
}
+ if (displayHeight == 0) {
+ displayHeight = dispdev->getHeight();
+ }
+
// Show boot screen for first logo_timeout seconds, then switch to normal operation.
// serialSinceMsec adjusts for additional serial wait time during nRF52 bootup
static bool showingBootScreen = true;
@@ -1424,6 +2086,15 @@ void Screen::setFrames()
LOG_DEBUG("showing standard frames\n");
showingNormalScreen = true;
+#ifdef USE_EINK
+ // If user has disabled the screensaver, warn them after boot
+ static bool warnedScreensaverDisabled = false;
+ if (config.display.screen_on_secs == 0 && !warnedScreensaverDisabled) {
+ screen->print("Screensaver disabled\n");
+ warnedScreensaverDisabled = true;
+ }
+#endif
+
moduleFrames = MeshModule::GetMeshModulesWithUIFrames();
LOG_DEBUG("Showing %d module frames\n", moduleFrames.size());
#ifdef DEBUG_PORT
@@ -1431,7 +2102,7 @@ void Screen::setFrames()
LOG_DEBUG("Total frame count: %d\n", totalFrameCount);
#endif
- // We don't show the node info our our node (if we have it yet - we should)
+ // We don't show the node info of our node (if we have it yet - we should)
size_t numMeshNodes = nodeDB->getNumMeshNodes();
if (numMeshNodes > 0)
numMeshNodes--;
@@ -1454,6 +2125,10 @@ void Screen::setFrames()
if (error_code)
normalFrames[numframes++] = drawCriticalFaultFrame;
+#ifdef T_WATCH_S3
+ normalFrames[numframes++] = screen->digitalWatchFace ? &Screen::drawDigitalClockFrame : &Screen::drawAnalogClockFrame;
+#endif
+
// If we have a text message - show it next, unless it's a phone message and we aren't using any special modules
if (devicestate.has_rx_text_message && shouldDrawMessage(&devicestate.rx_text_message)) {
normalFrames[numframes++] = drawTextMessageFrame;
@@ -1490,6 +2165,11 @@ void Screen::setFrames()
ui->setFrames(normalFrames, numframes);
ui->enableAllIndicators();
+ // Add function overlay here. This can show when notifications muted, modifier key is active etc
+ static OverlayCallback functionOverlay[] = {drawFunctionOverlay};
+ static const int functionOverlayCount = sizeof(functionOverlay) / sizeof(functionOverlay[0]);
+ ui->setOverlays(functionOverlay, functionOverlayCount);
+
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
// just changed)
@@ -1573,9 +2253,55 @@ void Screen::blink()
delay(50);
count = count - 1;
}
+ // The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in OLEDDisplay.
dispdev->setBrightness(brightness);
}
+void Screen::increaseBrightness()
+{
+ brightness = ((brightness + 62) > 254) ? brightness : (brightness + 62);
+
+#if defined(ST7789_CS)
+ // run the setDisplayBrightness function. This works on t-decks
+ static_cast(dispdev)->setDisplayBrightness(brightness);
+#endif
+
+ /* TO DO: add little popup in center of screen saying what brightness level it is set to*/
+}
+
+void Screen::decreaseBrightness()
+{
+ brightness = (brightness < 70) ? brightness : (brightness - 62);
+
+#if defined(ST7789_CS)
+ static_cast(dispdev)->setDisplayBrightness(brightness);
+#endif
+
+ /* TO DO: add little popup in center of screen saying what brightness level it is set to*/
+}
+
+void Screen::setFunctionSymbal(std::string sym)
+{
+ if (std::find(functionSymbals.begin(), functionSymbals.end(), sym) == functionSymbals.end()) {
+ functionSymbals.push_back(sym);
+ functionSymbalString = "";
+ for (auto symbol : functionSymbals) {
+ functionSymbalString = symbol + " " + functionSymbalString;
+ }
+ setFastFramerate();
+ }
+}
+
+void Screen::removeFunctionSymbal(std::string sym)
+{
+ functionSymbals.erase(std::remove(functionSymbals.begin(), functionSymbals.end(), sym), functionSymbals.end());
+ functionSymbalString = "";
+ for (auto symbol : functionSymbals) {
+ functionSymbalString = symbol + " " + functionSymbalString;
+ }
+ setFastFramerate();
+}
+
std::string Screen::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds)
{
std::string uptime;
@@ -1983,6 +2709,21 @@ int Screen::handleUIFrameEvent(const UIFrameEvent *event)
int Screen::handleInputEvent(const InputEvent *event)
{
+
+#ifdef T_WATCH_S3
+ // For the T-Watch, intercept touches to the 'toggle digital/analog watch face' button
+ uint8_t watchFaceFrame = error_code ? 1 : 0;
+
+ if (this->ui->getUiState()->currentFrame == watchFaceFrame && event->touchX >= 204 && event->touchX <= 240 &&
+ event->touchY >= 204 && event->touchY <= 240) {
+ screen->digitalWatchFace = !screen->digitalWatchFace;
+
+ setFrames();
+
+ return 0;
+ }
+#endif
+
if (showingNormalScreen && moduleFrames.size() == 0) {
// LOG_DEBUG("Screen::handleInputEvent from %s\n", event->source);
if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) {
@@ -1998,4 +2739,4 @@ int Screen::handleInputEvent(const InputEvent *event)
} // namespace graphics
#else
graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}
-#endif // HAS_SCREEN
+#endif // HAS_SCREEN
\ No newline at end of file
diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h
index 2cb1cd5a9..f4d719715 100644
--- a/src/graphics/Screen.h
+++ b/src/graphics/Screen.h
@@ -48,6 +48,7 @@ class Screen
#include "EInkDisplay2.h"
#include "EInkDynamicDisplay.h"
+#include "PointStruct.h"
#include "TFTDisplay.h"
#include "TypedQueue.h"
#include "commands.h"
@@ -77,6 +78,10 @@ class Screen
#define EINK_BLACK OLEDDISPLAY_COLOR::WHITE
#define EINK_WHITE OLEDDISPLAY_COLOR::BLACK
+// Base segment dimensions for T-Watch segmented display
+#define SEGMENT_WIDTH 16
+#define SEGMENT_HEIGHT 4
+
namespace graphics
{
@@ -166,9 +171,6 @@ class Screen : public concurrency::OSThread
void showPrevFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_PREV_FRAME}); }
void showNextFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_NEXT_FRAME}); }
- // Implementation to Adjust Brightness
- uint8_t brightness = BRIGHTNESS_DEFAULT;
-
/// Starts showing the Bluetooth PIN screen.
//
// Switches over to a static frame showing the Bluetooth pairing screen
@@ -202,6 +204,24 @@ class Screen : public concurrency::OSThread
enqueueCmd(cmd);
}
+ // Function to allow the AccelerometerThread to set the heading if a sensor provides it
+ // Mutex needed?
+ void setHeading(long _heading)
+ {
+ hasCompass = true;
+ compassHeading = _heading;
+ }
+
+ bool hasHeading() { return hasCompass; }
+
+ long getHeading() { return compassHeading; }
+ // functions for display brightness
+ void increaseBrightness();
+ void decreaseBrightness();
+
+ void setFunctionSymbal(std::string sym);
+ void removeFunctionSymbal(std::string sym);
+
/// Stops showing the bluetooth PIN screen.
void stopBluetoothPinScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BLUETOOTH_PIN_SCREEN}); }
@@ -351,7 +371,7 @@ class Screen : public concurrency::OSThread
bool enqueueCmd(const ScreenCmd &cmd)
{
if (!useDisplay)
- return true; // claim success if our display is not in use
+ return false; // not enqueued if our display is not in use
else {
bool success = cmdQueue.enqueue(cmd, 0);
enabled = true; // handle ASAP (we are the registered reader for cmdQueue, but might have been disabled)
@@ -385,6 +405,27 @@ class Screen : public concurrency::OSThread
static void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
+#ifdef T_WATCH_S3
+ static void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
+
+ static void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
+
+ static void drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale = 1);
+
+ static void drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height);
+
+ static void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int height);
+
+ static void drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale = 1);
+
+ static void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode = true, float scale = 1);
+
+ static void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y);
+
+ // Whether we are showing the digital watch face or the analog one
+ bool digitalWatchFace = true;
+#endif
+
/// Queue of commands to execute in doTask.
TypedQueue cmdQueue;
/// Whether we are using a display
@@ -395,6 +436,11 @@ class Screen : public concurrency::OSThread
// Bluetooth PIN screen)
bool showingNormalScreen = false;
+ // Implementation to Adjust Brightness
+ uint8_t brightness = BRIGHTNESS_DEFAULT; // H = 254, MH = 192, ML = 130 L = 103
+
+ bool hasCompass = false;
+ float compassHeading;
/// Holds state for debug information
DebugInfo debugInfo;
diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp
index 12e549424..8ea90c523 100644
--- a/src/graphics/TFTDisplay.cpp
+++ b/src/graphics/TFTDisplay.cpp
@@ -1,7 +1,6 @@
#include "configuration.h"
#include "main.h"
#if ARCH_PORTDUINO
-#include "mesh_bus_spi.h"
#include "platform/portduino/PortduinoGlue.h"
#endif
@@ -9,6 +8,12 @@
#define TFT_BACKLIGHT_ON HIGH
#endif
+#ifdef GPIO_EXTENDER
+#include
+#include
+extern SX1509 gpioExtender;
+#endif
+
#ifndef TFT_MESH
#define TFT_MESH COLOR565(0x67, 0xEA, 0x94)
#endif
@@ -112,8 +117,16 @@ class LGFX : public lgfx::LGFX_Device
static LGFX *tft = nullptr;
#elif defined(RAK14014)
+#include
#include
TFT_eSPI *tft = nullptr;
+FT6336U ft6336u;
+
+static uint8_t _rak14014_touch_int = false; // TP interrupt generation flag.
+static void rak14014_tpIntHandle(void)
+{
+ _rak14014_touch_int = true;
+}
#elif defined(ST7789_CS)
#include // Graphics and font library for ST7735 driver chip
@@ -340,7 +353,7 @@ static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_LCD *_panel_instance;
- lgfx::Mesh_Bus_SPI _bus_instance;
+ lgfx::Bus_SPI _bus_instance;
lgfx::ITouch *_touch_instance;
@@ -357,7 +370,7 @@ class LGFX : public lgfx::LGFX_Device
_panel_instance = new lgfx::Panel_ILI9341;
auto buscfg = _bus_instance.config();
buscfg.spi_mode = 0;
- _bus_instance.spi_device(DisplaySPI, settingsStrings[displayspidev]);
+ buscfg.spi_host = settingsMap[displayspidev];
buscfg.pin_dc = settingsMap[displayDC]; // Set SPI DC pin number (-1 = disable)
@@ -383,6 +396,8 @@ class LGFX : public lgfx::LGFX_Device
_touch_instance = new lgfx::Touch_XPT2046;
} else if (settingsMap[touchscreenModule] == stmpe610) {
_touch_instance = new lgfx::Touch_STMPE610;
+ } else if (settingsMap[touchscreenModule] == ft5x06) {
+ _touch_instance = new lgfx::Touch_FT5x06;
}
auto touch_cfg = _touch_instance->config();
@@ -394,6 +409,11 @@ class LGFX : public lgfx::LGFX_Device
touch_cfg.pin_int = settingsMap[touchscreenIRQ];
touch_cfg.bus_shared = true;
touch_cfg.offset_rotation = 1;
+ if (settingsMap[touchscreenI2CAddr] != -1) {
+ touch_cfg.i2c_addr = settingsMap[touchscreenI2CAddr];
+ } else {
+ touch_cfg.spi_host = settingsMap[touchscreenspidev];
+ }
_touch_instance->config(touch_cfg);
_panel_instance->setTouch(_touch_instance);
@@ -584,7 +604,7 @@ void TFTDisplay::sendCommand(uint8_t com)
unphone.backlight(true); // using unPhone library
#endif
#ifdef RAK14014
-#elif !defined(M5STACK)
+#elif !defined(M5STACK) && !defined(ST7789_CS) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function
tft->setBrightness(172);
#endif
break;
@@ -628,6 +648,16 @@ void TFTDisplay::sendCommand(uint8_t com)
// Drop all other commands to device (we just update the buffer)
}
+void TFTDisplay::setDisplayBrightness(uint8_t _brightness)
+{
+#ifdef RAK14014
+ // todo
+#else
+ tft->setBrightness(_brightness);
+ LOG_DEBUG("Brightness is set to value: %i \n", _brightness);
+#endif
+}
+
void TFTDisplay::flipScreenVertically()
{
#if defined(T_WATCH_S3)
@@ -639,6 +669,7 @@ void TFTDisplay::flipScreenVertically()
bool TFTDisplay::hasTouch(void)
{
#ifdef RAK14014
+ return true;
#elif !defined(M5STACK)
return tft->touch() != nullptr;
#else
@@ -649,6 +680,15 @@ bool TFTDisplay::hasTouch(void)
bool TFTDisplay::getTouch(int16_t *x, int16_t *y)
{
#ifdef RAK14014
+ if (_rak14014_touch_int) {
+ _rak14014_touch_int = false;
+ /* The X and Y axes have to be switched */
+ *y = ft6336u.read_touch1_x();
+ *x = TFT_HEIGHT - ft6336u.read_touch1_y();
+ return true;
+ } else {
+ return false;
+ }
#elif !defined(M5STACK)
return tft->getTouch(x, y);
#else
@@ -698,7 +738,10 @@ bool TFTDisplay::connect()
#elif defined(RAK14014)
tft->setRotation(1);
tft->setSwapBytes(true);
-// tft->fillScreen(TFT_BLACK);
+ // tft->fillScreen(TFT_BLACK);
+ ft6336u.begin();
+ pinMode(SCREEN_TOUCH_INT, INPUT_PULLUP);
+ attachInterrupt(digitalPinToInterrupt(SCREEN_TOUCH_INT), rak14014_tpIntHandle, FALLING);
#elif defined(T_DECK) || defined(PICOMPUTER_S3) || defined(CHATTER_2)
tft->setRotation(1); // T-Deck has the TFT in landscape
#elif defined(T_WATCH_S3)
@@ -711,4 +754,4 @@ bool TFTDisplay::connect()
return true;
}
-#endif
+#endif
\ No newline at end of file
diff --git a/src/graphics/TFTDisplay.h b/src/graphics/TFTDisplay.h
index 3d6ea6cc6..42aa3abff 100644
--- a/src/graphics/TFTDisplay.h
+++ b/src/graphics/TFTDisplay.h
@@ -30,6 +30,9 @@ class TFTDisplay : public OLEDDisplay
static bool hasTouch(void);
static bool getTouch(int16_t *x, int16_t *y);
+ // Functions for changing display brightness
+ void setDisplayBrightness(uint8_t);
+
/**
* shim to make the abstraction happy
*
diff --git a/src/graphics/images.h b/src/graphics/images.h
index 5c6fb4275..d4c738610 100644
--- a/src/graphics/images.h
+++ b/src/graphics/images.h
@@ -14,6 +14,12 @@ const uint8_t imgUser[] PROGMEM = {0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3
const uint8_t imgPositionEmpty[] PROGMEM = {0x20, 0x30, 0x28, 0x24, 0x42, 0xFF};
const uint8_t imgPositionSolid[] PROGMEM = {0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF};
+#ifdef T_WATCH_S3
+const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0xe3, 0x1f,
+ 0xf3, 0x3f, 0x33, 0x30, 0x33, 0x33, 0x33, 0x33, 0x03, 0x33, 0xff, 0x33,
+ 0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f};
+#endif
+
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS) || \
ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
@@ -31,4 +37,171 @@ const uint8_t imgQuestion[] PROGMEM = {0xbf, 0x41, 0xc0, 0x8b, 0xdb, 0x70, 0xa1,
const uint8_t imgSF[] PROGMEM = {0xd2, 0xb7, 0xad, 0xbb, 0x92, 0x01, 0xfd, 0xfd, 0x15, 0x85, 0xf5};
#endif
+#ifndef EXCLUDE_EMOJI
+#define thumbs_height 25
+#define thumbs_width 25
+static unsigned char thumbup[] PROGMEM = {
+ 0x00, 0x1C, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x80, 0x09, 0x00, 0x00,
+ 0xC0, 0x08, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00,
+ 0x0C, 0xCE, 0x7F, 0x00, 0x04, 0x20, 0x80, 0x00, 0x02, 0x20, 0x80, 0x00, 0x02, 0x60, 0xC0, 0x00, 0x01, 0xF8, 0xFF, 0x01,
+ 0x01, 0x08, 0x00, 0x01, 0x01, 0x08, 0x00, 0x01, 0x01, 0xF8, 0xFF, 0x00, 0x01, 0x10, 0x80, 0x00, 0x01, 0x18, 0x80, 0x00,
+ 0x02, 0x30, 0xC0, 0x00, 0x06, 0xE0, 0x3F, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x38, 0x20, 0x10, 0x00, 0xE0, 0xCF, 0x1F, 0x00,
+};
+
+static unsigned char thumbdown[] PROGMEM = {
+ 0xE0, 0xCF, 0x1F, 0x00, 0x38, 0x20, 0x10, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x06, 0xE0, 0x3F, 0x00, 0x02, 0x30, 0xC0, 0x00,
+ 0x01, 0x18, 0x80, 0x00, 0x01, 0x10, 0x80, 0x00, 0x01, 0xF8, 0xFF, 0x00, 0x01, 0x08, 0x00, 0x01, 0x01, 0x08, 0x00, 0x01,
+ 0x01, 0xF8, 0xFF, 0x01, 0x02, 0x60, 0xC0, 0x00, 0x02, 0x20, 0x80, 0x00, 0x04, 0x20, 0x80, 0x00, 0x0C, 0xCE, 0x7F, 0x00,
+ 0x18, 0x02, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0xC0, 0x08, 0x00, 0x00,
+ 0x80, 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
+};
+
+#define question_height 25
+#define question_width 25
+static unsigned char question[] PROGMEM = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, 0xFF, 0x01, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0xE0, 0xFF, 0x07, 0x00,
+ 0xE0, 0xC3, 0x0F, 0x00, 0xF0, 0x81, 0x0F, 0x00, 0xF0, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x80, 0x0F, 0x00,
+ 0x00, 0xC0, 0x0F, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x7C, 0x00, 0x00,
+ 0x00, 0x3C, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+#define bang_height 30
+#define bang_width 30
+static unsigned char bang[] PROGMEM = {
+ 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x07, 0xF8, 0x3F, 0xFF, 0x07, 0xF8, 0x3F,
+ 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F,
+ 0xFE, 0x03, 0xF0, 0x1F, 0xFE, 0x03, 0xF0, 0x1F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F,
+ 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x01, 0xE0, 0x0F, 0xFC, 0x01, 0xE0, 0x0F,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xC0, 0x03, 0xFC, 0x03, 0xF0, 0x0F, 0xFE, 0x03, 0xF0, 0x1F,
+ 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFC, 0x03, 0xF0, 0x0F, 0xF8, 0x01, 0xE0, 0x07,
+};
+
+#define haha_height 30
+#define haha_width 30
+static unsigned char haha[] PROGMEM = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00,
+ 0x00, 0xFC, 0x0F, 0x00, 0x00, 0x1F, 0x3E, 0x00, 0x80, 0x03, 0x70, 0x00, 0xC0, 0x01, 0xE0, 0x00, 0xC0, 0x00, 0xC2, 0x00,
+ 0x60, 0x00, 0x03, 0x00, 0x60, 0x00, 0xC1, 0x1F, 0x60, 0x80, 0x8F, 0x31, 0x30, 0x0E, 0x80, 0x31, 0x30, 0x10, 0x30, 0x1F,
+ 0x30, 0x08, 0x58, 0x00, 0x30, 0x04, 0x6C, 0x03, 0x60, 0x00, 0xF3, 0x01, 0x60, 0xC0, 0xFC, 0x01, 0x80, 0x38, 0xBF, 0x01,
+ 0xE0, 0xC5, 0xDF, 0x00, 0xB0, 0xF9, 0xEF, 0x00, 0x30, 0xF1, 0x73, 0x00, 0xB0, 0x1D, 0x3E, 0x00, 0xF0, 0xFD, 0x0F, 0x00,
+ 0xE0, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+#define wave_icon_height 30
+#define wave_icon_width 30
+static unsigned char wave_icon[] PROGMEM = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xC0, 0x00,
+ 0x00, 0x0C, 0x9C, 0x01, 0x80, 0x17, 0x20, 0x01, 0x80, 0x26, 0x46, 0x02, 0x80, 0x44, 0x88, 0x02, 0xC0, 0x89, 0x8A, 0x02,
+ 0x40, 0x93, 0x8B, 0x02, 0x40, 0x26, 0x13, 0x00, 0x80, 0x44, 0x16, 0x00, 0xC0, 0x89, 0x24, 0x00, 0x40, 0x93, 0x60, 0x00,
+ 0x40, 0x26, 0x40, 0x00, 0x80, 0x0C, 0x80, 0x00, 0x00, 0x09, 0x80, 0x00, 0x00, 0x02, 0x80, 0x00, 0x40, 0x06, 0x80, 0x00,
+ 0x50, 0x0C, 0x80, 0x00, 0x50, 0x08, 0x40, 0x00, 0x90, 0x10, 0x20, 0x00, 0xB0, 0x21, 0x10, 0x00, 0x20, 0x47, 0x18, 0x00,
+ 0x40, 0x80, 0x0F, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+#define cowboy_height 30
+#define cowboy_width 30
+static unsigned char cowboy[] PROGMEM = {
+ 0x00, 0xF0, 0x03, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x00, 0xFE, 0x1F, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x3C, 0xFE, 0x1F, 0x0F,
+ 0xFE, 0xFE, 0xDF, 0x1F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F,
+ 0x3E, 0xC0, 0x00, 0x1F, 0x1E, 0x00, 0x00, 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x08, 0x0E, 0x1C, 0x04, 0x00, 0x0E, 0x1C, 0x00,
+ 0x04, 0x0E, 0x1C, 0x08, 0x04, 0x0E, 0x1C, 0x08, 0x04, 0x04, 0x08, 0x08, 0x04, 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x08,
+ 0x8C, 0x07, 0x70, 0x0C, 0x88, 0xFC, 0x4F, 0x04, 0x88, 0x01, 0x40, 0x04, 0x90, 0xFF, 0x7F, 0x02, 0x30, 0x03, 0x30, 0x03,
+ 0x60, 0x0E, 0x9C, 0x01, 0xC0, 0xF8, 0xC7, 0x00, 0x80, 0x01, 0x60, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00, 0xF8, 0x07, 0x00,
+};
+
+#define deadmau5_height 30
+#define deadmau5_width 60
+static unsigned char deadmau5[] PROGMEM = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00,
+ 0x00, 0xFC, 0x03, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x80, 0xFF, 0x0F, 0x00, 0xC0, 0xFF, 0xFF, 0x00, 0xE0, 0xFF, 0x3F, 0x00,
+ 0xE0, 0xFF, 0xFF, 0x01, 0xF0, 0xFF, 0x7F, 0x00, 0xF0, 0xFF, 0xFF, 0x03, 0xF8, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x07,
+ 0xFC, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0xFE, 0xFF, 0xFF, 0x00,
+ 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xE0, 0xFF, 0x3F, 0xFC,
+ 0x0F, 0xFF, 0x7F, 0x00, 0xC0, 0xFF, 0x1F, 0xF8, 0x0F, 0xFC, 0x3F, 0x00, 0x80, 0xFF, 0x0F, 0xF8, 0x1F, 0xFC, 0x1F, 0x00,
+ 0x00, 0xFF, 0x0F, 0xFC, 0x3F, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x1F, 0xFF, 0xFF, 0xFE, 0x01, 0x00, 0x00, 0x00, 0xFC, 0xFF,
+ 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,
+ 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0xC0, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0x07, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+#define sun_width 30
+#define sun_height 30
+static unsigned char sun[] PROGMEM = {
+ 0x00, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x30, 0xC0, 0x00, 0x03,
+ 0x70, 0x00, 0x80, 0x03, 0xF0, 0x00, 0xC0, 0x03, 0xF0, 0xF8, 0xC7, 0x03, 0xE0, 0xFC, 0xCF, 0x01, 0x00, 0xFE, 0x1F, 0x00,
+ 0x00, 0xFF, 0x3F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x8E, 0xFF, 0x7F, 0x1C, 0x9F, 0xFF, 0x7F, 0x3E,
+ 0x9F, 0xFF, 0x7F, 0x3E, 0x8E, 0xFF, 0x7F, 0x1C, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0x3F, 0x00,
+ 0x00, 0xFE, 0x1F, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0xC0, 0xF9, 0xE7, 0x00, 0xE0, 0x01, 0xE0, 0x01, 0xF0, 0x01, 0xE0, 0x03,
+ 0xF0, 0xC0, 0xC0, 0x03, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x00, 0x00,
+};
+
+#define rain_width 30
+#define rain_height 30
+static unsigned char rain[] PROGMEM = {
+ 0xC0, 0x0F, 0xC0, 0x00, 0x40, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x03, 0x38, 0x00,
+ 0x00, 0x0E, 0x0C, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0x20,
+ 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x30, 0x02, 0x00,
+ 0x00, 0x10, 0x06, 0x00, 0x00, 0x08, 0xFC, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x01, 0x80, 0x00, 0x01, 0x00,
+ 0xC0, 0xC0, 0x81, 0x03, 0xA0, 0x60, 0xC1, 0x03, 0x90, 0x20, 0x41, 0x01, 0xF0, 0xE0, 0xC0, 0x01, 0x60, 0x4C,
+ 0x98, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00, 0x0B, 0x12, 0x00, 0x00, 0x09, 0x1A, 0x00, 0x00, 0x06, 0x0E, 0x00,
+};
+
+#define cloud_height 30
+#define cloud_width 30
+static unsigned char cloud[] PROGMEM = {
+ 0x00, 0x80, 0x07, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0x70, 0x30, 0x00, 0x00, 0x10, 0x60, 0x00, 0x80, 0x1F, 0x40, 0x00,
+ 0xC0, 0x0F, 0xC0, 0x00, 0xC0, 0x00, 0x80, 0x00, 0x60, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x01,
+ 0x20, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x08, 0x06, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x10,
+ 0x02, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20,
+ 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x10,
+ 0x02, 0x00, 0x00, 0x10, 0x06, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x0C, 0xFC, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x03,
+};
+
+#define fog_height 25
+#define fog_width 25
+static unsigned char fog[] PROGMEM = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x3C, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0x87, 0xC7, 0xC3, 0x01, 0x03, 0xFE, 0x80, 0x01,
+ 0x00, 0x38, 0x00, 0x00, 0xFC, 0x00, 0x7E, 0x00, 0xFF, 0x83, 0xFF, 0x01, 0x03, 0xFF, 0x81, 0x01, 0x00, 0x7C, 0x00, 0x00,
+ 0xF8, 0x00, 0x3E, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0x87, 0xC7, 0xC3, 0x01, 0x03, 0xFE, 0x80, 0x01, 0x00, 0x38, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+#define devil_height 30
+#define devil_width 30
+static unsigned char devil[] PROGMEM = {
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x10, 0x03, 0xC0, 0x01, 0x38, 0x07, 0x7C, 0x0F, 0x38, 0x1F, 0x03, 0x30, 0x1E,
+ 0xFE, 0x01, 0xE0, 0x1F, 0x7E, 0x00, 0x80, 0x1F, 0x3C, 0x00, 0x00, 0x0F, 0x1C, 0x00, 0x00, 0x0E, 0x18, 0x00, 0x00, 0x06,
+ 0x08, 0x00, 0x00, 0x04, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x0E, 0x1C, 0x0C,
+ 0x0C, 0x18, 0x06, 0x0C, 0x0C, 0x1C, 0x06, 0x0C, 0x0C, 0x1C, 0x0E, 0x0C, 0x0C, 0x1C, 0x0E, 0x0C, 0x0C, 0x0C, 0x06, 0x0C,
+ 0x08, 0x00, 0x00, 0x06, 0x18, 0x02, 0x10, 0x06, 0x10, 0x0C, 0x0C, 0x03, 0x30, 0xF8, 0x07, 0x03, 0x60, 0xE0, 0x80, 0x01,
+ 0xC0, 0x00, 0xC0, 0x00, 0x80, 0x01, 0x70, 0x00, 0x00, 0x06, 0x1C, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+#define heart_height 30
+#define heart_width 30
+static unsigned char heart[] PROGMEM = {
+ 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0xF0, 0x00, 0xF8, 0x0F, 0xFC, 0x07, 0xFC, 0x1F, 0x06, 0x0E, 0xFE, 0x3F, 0x03, 0x18,
+ 0xFE, 0xFF, 0x7F, 0x10, 0xFF, 0xFF, 0xFF, 0x31, 0xFF, 0xFF, 0xFF, 0x33, 0xFF, 0xFF, 0xFF, 0x37, 0xFF, 0xFF, 0xFF, 0x37,
+ 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFE, 0xFF, 0xFF, 0x1F, 0xFE, 0xFF, 0xFF, 0x1F,
+ 0xFC, 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x03, 0xF0, 0xFF, 0xFF, 0x03,
+ 0xE0, 0xFF, 0xFF, 0x01, 0xC0, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x00, 0xFE, 0x1F, 0x00,
+ 0x00, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x07, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00,
+};
+
+#define poo_width 30
+#define poo_height 30
+static unsigned char poo[] PROGMEM = {
+ 0x00, 0x1C, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0xEC, 0x01, 0x00, 0x00, 0x8C, 0x07, 0x00, 0x00, 0x0C, 0x06, 0x00,
+ 0x00, 0x24, 0x0C, 0x00, 0x00, 0x34, 0x08, 0x00, 0x00, 0x1F, 0x08, 0x00, 0xC0, 0x0F, 0x08, 0x00, 0xC0, 0x00, 0x3C, 0x00,
+ 0x60, 0x00, 0x7C, 0x00, 0x60, 0x00, 0xC6, 0x00, 0x20, 0x00, 0xCB, 0x00, 0xA0, 0xC7, 0xFF, 0x00, 0xE0, 0x7F, 0xF7, 0x00,
+ 0xF0, 0x18, 0xE3, 0x03, 0x78, 0x18, 0x41, 0x03, 0x6C, 0x9B, 0x5D, 0x06, 0x64, 0x9B, 0x5D, 0x04, 0x44, 0x1A, 0x41, 0x04,
+ 0x4C, 0xD8, 0x63, 0x06, 0xF8, 0xFC, 0x36, 0x06, 0xFE, 0x0F, 0x9C, 0x1F, 0x07, 0x03, 0xC0, 0x30, 0x03, 0x00, 0x78, 0x20,
+ 0x01, 0x00, 0x1F, 0x20, 0x03, 0xE0, 0x03, 0x20, 0x07, 0x7E, 0x04, 0x30, 0xFE, 0x0F, 0xFC, 0x1F, 0xF0, 0x00, 0xF0, 0x0F,
+};
+#endif
+
#include "img/icon.xbm"
diff --git a/src/graphics/mesh_bus_spi.cpp b/src/graphics/mesh_bus_spi.cpp
deleted file mode 100644
index a9536d490..000000000
--- a/src/graphics/mesh_bus_spi.cpp
+++ /dev/null
@@ -1,188 +0,0 @@
-// This code has been copied from LovyanGFX to make the SPI device selectable for touchscreens.
-// Ideally this could eventually be an inherited class from BUS_SPI,
-// but currently too many internal objects are set private.
-
-#include "configuration.h"
-#if ARCH_PORTDUINO
-#include "lgfx/v1/misc/pixelcopy.hpp"
-#include "main.h"
-#include "mesh_bus_spi.h"
-#include
-#include
-
-namespace lgfx
-{
-inline namespace v1
-{
-//----------------------------------------------------------------------------
-
-void Mesh_Bus_SPI::config(const config_t &config)
-{
- _cfg = config;
-
- if (_cfg.pin_dc >= 0) {
- pinMode(_cfg.pin_dc, pin_mode_t::output);
- gpio_hi(_cfg.pin_dc);
- }
-}
-
-bool Mesh_Bus_SPI::init(void)
-{
- dc_h();
- pinMode(_cfg.pin_dc, pin_mode_t::output);
- if (SPIName != "")
- PrivateSPI->begin(SPIName.c_str());
- else
- PrivateSPI->begin();
- return true;
-}
-
-void Mesh_Bus_SPI::release(void)
-{
- PrivateSPI->end();
-}
-
-void Mesh_Bus_SPI::spi_device(HardwareSPI *newSPI, std::string newSPIName)
-{
- PrivateSPI = newSPI;
- SPIName = newSPIName;
-}
-void Mesh_Bus_SPI::beginTransaction(void)
-{
- dc_h();
- SPISettings setting(_cfg.freq_write, MSBFIRST, _cfg.spi_mode);
- PrivateSPI->beginTransaction(setting);
-}
-
-void Mesh_Bus_SPI::endTransaction(void)
-{
- PrivateSPI->endTransaction();
- dc_h();
-}
-
-void Mesh_Bus_SPI::beginRead(void)
-{
- PrivateSPI->endTransaction();
- // SPISettings setting(_cfg.freq_read, BitOrder::MSBFIRST, _cfg.spi_mode, false);
- SPISettings setting(_cfg.freq_read, MSBFIRST, _cfg.spi_mode);
- PrivateSPI->beginTransaction(setting);
-}
-
-void Mesh_Bus_SPI::endRead(void)
-{
- PrivateSPI->endTransaction();
- beginTransaction();
-}
-
-void Mesh_Bus_SPI::wait(void) {}
-
-bool Mesh_Bus_SPI::busy(void) const
-{
- return false;
-}
-
-bool Mesh_Bus_SPI::writeCommand(uint32_t data, uint_fast8_t bit_length)
-{
- dc_l();
- PrivateSPI->transfer((uint8_t *)&data, bit_length >> 3);
- dc_h();
- return true;
-}
-
-void Mesh_Bus_SPI::writeData(uint32_t data, uint_fast8_t bit_length)
-{
- PrivateSPI->transfer((uint8_t *)&data, bit_length >> 3);
-}
-
-void Mesh_Bus_SPI::writeDataRepeat(uint32_t data, uint_fast8_t bit_length, uint32_t length)
-{
- const uint8_t dst_bytes = bit_length >> 3;
- uint32_t limit = (dst_bytes == 3) ? 12 : 16;
- auto buf = _flip_buffer.getBuffer(512);
- size_t fillpos = 0;
- reinterpret_cast(buf)[0] = data;
- fillpos += dst_bytes;
- uint32_t len;
- do {
- len = ((length - 1) % limit) + 1;
- if (limit <= 64)
- limit <<= 1;
-
- while (fillpos < len * dst_bytes) {
- memcpy(&buf[fillpos], buf, fillpos);
- fillpos += fillpos;
- }
-
- PrivateSPI->transfer(buf, len * dst_bytes);
- } while (length -= len);
-}
-
-void Mesh_Bus_SPI::writePixels(pixelcopy_t *param, uint32_t length)
-{
- const uint8_t dst_bytes = param->dst_bits >> 3;
- uint32_t limit = (dst_bytes == 3) ? 12 : 16;
- uint32_t len;
- do {
- len = ((length - 1) % limit) + 1;
- if (limit <= 32)
- limit <<= 1;
- auto buf = _flip_buffer.getBuffer(len * dst_bytes);
- param->fp_copy(buf, 0, len, param);
- PrivateSPI->transfer(buf, len * dst_bytes);
- } while (length -= len);
-}
-
-void Mesh_Bus_SPI::writeBytes(const uint8_t *data, uint32_t length, bool dc, bool use_dma)
-{
- if (dc)
- dc_h();
- else
- dc_l();
- PrivateSPI->transfer(const_cast(data), length);
- if (!dc)
- dc_h();
-}
-
-uint32_t Mesh_Bus_SPI::readData(uint_fast8_t bit_length)
-{
- uint32_t res = 0;
- bit_length >>= 3;
- if (!bit_length)
- return res;
- int idx = 0;
- do {
- res |= PrivateSPI->transfer(0) << idx;
- idx += 8;
- } while (--bit_length);
- return res;
-}
-
-bool Mesh_Bus_SPI::readBytes(uint8_t *dst, uint32_t length, bool use_dma)
-{
- do {
- dst[0] = PrivateSPI->transfer(0);
- ++dst;
- } while (--length);
- return true;
-}
-
-void Mesh_Bus_SPI::readPixels(void *dst, pixelcopy_t *param, uint32_t length)
-{
- uint32_t bytes = param->src_bits >> 3;
- uint32_t dstindex = 0;
- uint32_t len = 4;
- uint8_t buf[24];
- param->src_data = buf;
- do {
- if (len > length)
- len = length;
- readBytes((uint8_t *)buf, len * bytes, true);
- param->src_x = 0;
- dstindex = param->fp_copy(dst, dstindex, dstindex + len, param);
- length -= len;
- } while (length);
-}
-
-} // namespace v1
-} // namespace lgfx
-#endif
\ No newline at end of file
diff --git a/src/graphics/mesh_bus_spi.h b/src/graphics/mesh_bus_spi.h
deleted file mode 100644
index 903f7ad9d..000000000
--- a/src/graphics/mesh_bus_spi.h
+++ /dev/null
@@ -1,100 +0,0 @@
-#if ARCH_PORTDUINO
-/*----------------------------------------------------------------------------/
- Lovyan GFX - Graphics library for embedded devices.
-
-Original Source:
- https://github.com/lovyan03/LovyanGFX/
-
-Licence:
- [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
-
-Author:
- [lovyan03](https://twitter.com/lovyan03)
-
-Contributors:
- [ciniml](https://github.com/ciniml)
- [mongonta0716](https://github.com/mongonta0716)
- [tobozo](https://github.com/tobozo)
-/----------------------------------------------------------------------------*/
-#pragma once
-
-#include
-
-#include "lgfx/v1/Bus.hpp"
-#include "lgfx/v1/platforms/common.hpp"
-
-namespace lgfx
-{
-inline namespace v1
-{
-//----------------------------------------------------------------------------
-
-class Mesh_Bus_SPI : public IBus
-{
- public:
- struct config_t {
- uint32_t freq_write = 16000000;
- uint32_t freq_read = 8000000;
- // bool spi_3wire = true;
- // bool use_lock = true;
- int16_t pin_sclk = -1;
- int16_t pin_miso = -1;
- int16_t pin_mosi = -1;
- int16_t pin_dc = -1;
- uint8_t spi_mode = 0;
- };
-
- const config_t &config(void) const { return _cfg; }
-
- void config(const config_t &config);
-
- bus_type_t busType(void) const override { return bus_type_t::bus_spi; }
-
- bool init(void) override;
- void release(void) override;
- void spi_device(HardwareSPI *newSPI, std::string newSPIName);
-
- void beginTransaction(void) override;
- void endTransaction(void) override;
- void wait(void) override;
- bool busy(void) const override;
-
- bool writeCommand(uint32_t data, uint_fast8_t bit_length) override;
- void writeData(uint32_t data, uint_fast8_t bit_length) override;
- void writeDataRepeat(uint32_t data, uint_fast8_t bit_length, uint32_t count) override;
- void writePixels(pixelcopy_t *param, uint32_t length) override;
- void writeBytes(const uint8_t *data, uint32_t length, bool dc, bool use_dma) override;
-
- void initDMA(void) {}
- void flush(void) {}
- void addDMAQueue(const uint8_t *data, uint32_t length) override { writeBytes(data, length, true, true); }
- void execDMAQueue(void) {}
- uint8_t *getDMABuffer(uint32_t length) override { return _flip_buffer.getBuffer(length); }
-
- void beginRead(void) override;
- void endRead(void) override;
- uint32_t readData(uint_fast8_t bit_length) override;
- bool readBytes(uint8_t *dst, uint32_t length, bool use_dma) override;
- void readPixels(void *dst, pixelcopy_t *param, uint32_t length) override;
-
- private:
- HardwareSPI *PrivateSPI;
- std::string SPIName;
- __attribute__((always_inline)) inline void dc_h(void) { gpio_hi(_cfg.pin_dc); }
- __attribute__((always_inline)) inline void dc_l(void) { gpio_lo(_cfg.pin_dc); }
-
- config_t _cfg;
- FlipBuffer _flip_buffer;
- bool _need_wait;
- uint32_t _mask_reg_dc;
- uint32_t _last_apb_freq = -1;
- uint32_t _clkdiv_write;
- uint32_t _clkdiv_read;
- volatile uint32_t *_gpio_reg_dc_h;
- volatile uint32_t *_gpio_reg_dc_l;
-};
-
-//----------------------------------------------------------------------------
-} // namespace v1
-} // namespace lgfx
-#endif
\ No newline at end of file
diff --git a/src/input/InputBroker.cpp b/src/input/InputBroker.cpp
index b06c7400f..cb73e32ba 100644
--- a/src/input/InputBroker.cpp
+++ b/src/input/InputBroker.cpp
@@ -1,7 +1,7 @@
#include "InputBroker.h"
#include "PowerFSM.h" // needed for event trigger
-InputBroker *inputBroker;
+InputBroker *inputBroker = nullptr;
InputBroker::InputBroker(){};
diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h
index d73e6657a..57c25af4b 100644
--- a/src/input/InputBroker.h
+++ b/src/input/InputBroker.h
@@ -8,6 +8,8 @@ typedef struct _InputEvent {
const char *source;
char inputEvent;
char kbchar;
+ uint16_t touchX;
+ uint16_t touchY;
} InputEvent;
class InputBroker : public Observable
{
diff --git a/src/input/LinuxInput.cpp b/src/input/LinuxInput.cpp
index 1ace2044c..6194195ed 100644
--- a/src/input/LinuxInput.cpp
+++ b/src/input/LinuxInput.cpp
@@ -155,6 +155,9 @@ int32_t LinuxInput::runOnce()
case KEY_ENTER: // Enter
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
break;
+ case KEY_POWER:
+ system("poweroff");
+ break;
default: // all other keys
if (keymap[code]) {
e.inputEvent = ANYKEY;
diff --git a/src/input/TouchScreenBase.h b/src/input/TouchScreenBase.h
index a68c23e99..0b2002551 100644
--- a/src/input/TouchScreenBase.h
+++ b/src/input/TouchScreenBase.h
@@ -3,6 +3,7 @@
#include "InputBroker.h"
#include "concurrency/OSThread.h"
#include "mesh/NodeDB.h"
+#include "time.h"
typedef struct _TouchEvent {
const char *source;
diff --git a/src/input/TouchScreenImpl1.cpp b/src/input/TouchScreenImpl1.cpp
index c863ead69..20196278d 100644
--- a/src/input/TouchScreenImpl1.cpp
+++ b/src/input/TouchScreenImpl1.cpp
@@ -49,6 +49,10 @@ void TouchScreenImpl1::onEvent(const TouchEvent &event)
{
InputEvent e;
e.source = event.source;
+
+ e.touchX = event.x;
+ e.touchY = event.y;
+
switch (event.touchEvent) {
case TOUCH_ACTION_LEFT: {
e.inputEvent = static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT);
diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp
index 0533235cd..75adcc856 100644
--- a/src/input/cardKbI2cImpl.cpp
+++ b/src/input/cardKbI2cImpl.cpp
@@ -1,5 +1,7 @@
#include "cardKbI2cImpl.h"
#include "InputBroker.h"
+#include "detect/ScanI2CTwoWire.h"
+#include "main.h"
CardKbI2cImpl *cardKbI2cImpl;
@@ -7,10 +9,60 @@ CardKbI2cImpl::CardKbI2cImpl() : KbI2cBase("cardKB") {}
void CardKbI2cImpl::init()
{
+<<<<<<< HEAD
if ((cardkb_found.address == 0x00) || (kb_model == 0x12)) {
+=======
+ if (kb_model == 0x12) {
disable();
return;
}
+#ifndef ARCH_PORTDUINO
+ if (cardkb_found.address == 0x00) {
+ LOG_DEBUG("Rescanning for I2C keyboard\n");
+ uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR};
+ uint8_t i2caddr_asize = 3;
+ auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire());
+#if defined(I2C_SDA1)
+ i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize);
+#endif
+ i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize);
+ auto kb_info = i2cScanner->firstKeyboard();
+
+ if (kb_info.type != ScanI2C::DeviceType::NONE) {
+ cardkb_found = kb_info.address;
+ switch (kb_info.type) {
+ case ScanI2C::DeviceType::RAK14004:
+ kb_model = 0x02;
+ break;
+ case ScanI2C::DeviceType::CARDKB:
+ kb_model = 0x00;
+ break;
+ case ScanI2C::DeviceType::TDECKKB:
+ // assign an arbitrary value to distinguish from other models
+ kb_model = 0x10;
+ break;
+ case ScanI2C::DeviceType::BBQ10KB:
+ // assign an arbitrary value to distinguish from other models
+ kb_model = 0x11;
+ break;
+ default:
+ // use this as default since it's also just zero
+ LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00\n", kb_info.type);
+ kb_model = 0x00;
+ }
+ }
+ if (cardkb_found.address == 0x00) {
+ disable();
+ return;
+ }
+ }
+#else
+ if (cardkb_found.address == 0x00) {
+>>>>>>> 96bcc781ee31f68e5eb7b778306525c630435bdd
+ disable();
+ return;
+ }
+#endif
inputBroker->registerSource(this);
}
diff --git a/src/input/cardKbI2cImpl.h b/src/input/cardKbI2cImpl.h
index 1e6e87dfd..811a0558c 100644
--- a/src/input/cardKbI2cImpl.h
+++ b/src/input/cardKbI2cImpl.h
@@ -1,6 +1,5 @@
#pragma once
#include "kbI2cBase.h"
-#include "main.h"
/**
* @brief The idea behind this class to have static methods for the event handlers.
diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp
index 74a6c718d..024b16b9e 100644
--- a/src/input/kbI2cBase.cpp
+++ b/src/input/kbI2cBase.cpp
@@ -1,7 +1,7 @@
#include "kbI2cBase.h"
-
#include "configuration.h"
#include "detect/ScanI2C.h"
+#include "detect/ScanI2CTwoWire.h"
extern ScanI2C::DeviceAddress cardkb_found;
extern uint8_t kb_model;
@@ -30,11 +30,6 @@ uint8_t read_from_14004(TwoWire *i2cBus, uint8_t reg, uint8_t *data, uint8_t len
int32_t KbI2cBase::runOnce()
{
- if (cardkb_found.address == 0x00) {
- // Input device is not detected.
- return INT32_MAX;
- }
-
if (!i2cBus) {
switch (cardkb_found.port) {
case ScanI2C::WIRE1:
@@ -138,6 +133,9 @@ int32_t KbI2cBase::runOnce()
break;
case 0x13: // Code scanner says the SYM key is 0x13
is_sym = !is_sym;
+ e.inputEvent = ANYKEY;
+ e.kbchar =
+ is_sym ? 0xf1 : 0xf2; // send 0xf1 to tell CannedMessages to display that the modifier key is active
break;
case 0x0a: // apparently Enter on Q10 is a line feed instead of carriage return
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
@@ -193,6 +191,75 @@ int32_t KbI2cBase::runOnce()
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
e.source = this->_originName;
switch (c) {
+ case 0x71: // This is the button q. If modifier and q pressed, it cancels the input
+ if (is_sym) {
+ is_sym = false;
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL;
+ } else {
+ e.inputEvent = ANYKEY;
+ e.kbchar = c;
+ }
+ break;
+ case 0x74: // letter t. if modifier and t pressed call 'tab'
+ if (is_sym) {
+ is_sym = false;
+ e.inputEvent = ANYKEY;
+ e.kbchar = 0x09; // TAB Scancode
+ } else {
+ e.inputEvent = ANYKEY;
+ e.kbchar = c;
+ }
+ break;
+ case 0x6d: // letter m. Modifier makes it mute notifications
+ if (is_sym) {
+ is_sym = false;
+ e.inputEvent = ANYKEY;
+ e.kbchar = 0xac; // mute notifications
+ } else {
+ e.inputEvent = ANYKEY;
+ e.kbchar = c;
+ }
+ break;
+ case 0x6f: // letter o(+). Modifier makes screen increase in brightness
+ if (is_sym) {
+ is_sym = false;
+ e.inputEvent = ANYKEY;
+ e.kbchar = 0x11; // Increase Brightness code
+ } else {
+ e.inputEvent = ANYKEY;
+ e.kbchar = c;
+ }
+ break;
+ case 0x69: // letter i(-). Modifier makes screen decrease in brightness
+ if (is_sym) {
+ is_sym = false;
+ e.inputEvent = ANYKEY;
+ e.kbchar = 0x12; // Decrease Brightness code
+ } else {
+ e.inputEvent = ANYKEY;
+ e.kbchar = c;
+ }
+ break;
+ case 0x20: // Space. Send network ping like double press does
+ if (is_sym) {
+ is_sym = false;
+ e.inputEvent = ANYKEY;
+ e.kbchar = 0xaf; // (fn + space)
+ } else {
+ e.inputEvent = ANYKEY;
+ e.kbchar = c;
+ }
+ break;
+ case 0x67: // letter g. toggle gps
+ if (is_sym) {
+ is_sym = false;
+ e.inputEvent = ANYKEY;
+ e.kbchar = 0x9e;
+ } else {
+ e.inputEvent = ANYKEY;
+ e.kbchar = c;
+ }
+ break;
case 0x1b: // ESC
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL;
break;
@@ -216,6 +283,12 @@ int32_t KbI2cBase::runOnce()
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT;
e.kbchar = 0xb7;
break;
+ case 0xc: // Modifier key: 0xc is alt+c (Other options could be: 0xea = shift+mic button or 0x4 shift+$(speaker))
+ // toggle moddifiers button.
+ is_sym = !is_sym;
+ e.inputEvent = ANYKEY;
+ e.kbchar = is_sym ? 0xf1 : 0xf2; // send 0xf1 to tell CannedMessages to display that the modifier key is active
+ break;
case 0x90: // fn+r
case 0x91: // fn+t
case 0x9b: // fn+s
@@ -239,6 +312,7 @@ int32_t KbI2cBase::runOnce()
}
e.inputEvent = ANYKEY;
e.kbchar = c;
+ is_sym = false;
break;
}
diff --git a/src/main.cpp b/src/main.cpp
index 5f5e454ea..06df3abe0 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -41,13 +41,14 @@
#endif
#if !MESHTASTIC_EXCLUDE_BLUETOOTH
#include "nimble/NimbleBluetooth.h"
-NimbleBluetooth *nimbleBluetooth;
+NimbleBluetooth *nimbleBluetooth = nullptr;
#endif
#endif
#ifdef ARCH_NRF52
#include "NRF52Bluetooth.h"
-NRF52Bluetooth *nrf52Bluetooth;
+NRF52Bluetooth *nrf52Bluetooth = nullptr;
+;
#endif
#if HAS_WIFI
@@ -65,6 +66,8 @@ NRF52Bluetooth *nrf52Bluetooth;
#endif
#include "LLCC68Interface.h"
+#include "LR1110Interface.h"
+#include "LR1120Interface.h"
#include "RF95Interface.h"
#include "SX1262Interface.h"
#include "SX1268Interface.h"
@@ -92,22 +95,23 @@ NRF52Bluetooth *nrf52Bluetooth;
#include "ButtonThread.h"
#endif
+#include "AmbientLightingThread.h"
#include "PowerFSMThread.h"
-#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
+#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "AccelerometerThread.h"
-#include "AmbientLightingThread.h"
+AccelerometerThread *accelerometerThread = nullptr;
#endif
#ifdef HAS_I2S
#include "AudioThread.h"
-AudioThread *audioThread;
+AudioThread *audioThread = nullptr;
#endif
using namespace concurrency;
// We always create a screen object, but we only init it if we find the hardware
-graphics::Screen *screen;
+graphics::Screen *screen = nullptr;
// Global power status
meshtastic::PowerStatus *powerStatus = new meshtastic::PowerStatus();
@@ -197,7 +201,6 @@ uint32_t timeLastPowered = 0;
static Periodic *ledPeriodic;
static OSThread *powerFSMthread;
-static OSThread *accelerometerThread;
static OSThread *ambientLightingThread;
SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
@@ -342,7 +345,7 @@ void setup()
Wire.begin(I2C_SDA, I2C_SCL);
#elif defined(ARCH_PORTDUINO)
if (settingsStrings[i2cdev] != "") {
- LOG_INFO("Using %s as I2C device.\n", settingsStrings[i2cdev]);
+ LOG_INFO("Using %s as I2C device.\n", settingsStrings[i2cdev].c_str());
Wire.begin(settingsStrings[i2cdev].c_str());
} else {
LOG_INFO("No I2C device configured, skipping.\n");
@@ -360,18 +363,11 @@ void setup()
delay(1);
#endif
-#ifdef RAK4630
-#ifdef PIN_3V3_EN
- // We need to enable 3.3V periphery in order to scan it
- pinMode(PIN_3V3_EN, OUTPUT);
- digitalWrite(PIN_3V3_EN, HIGH);
-#endif
#ifdef AQ_SET_PIN
// RAK-12039 set pin for Air quality sensor
pinMode(AQ_SET_PIN, OUTPUT);
digitalWrite(AQ_SET_PIN, HIGH);
#endif
-#endif
#ifdef T_DECK
// enable keyboard
@@ -426,10 +422,6 @@ void setup()
auto i2cCount = i2cScanner->countDevices();
if (i2cCount == 0) {
LOG_INFO("No I2C devices found\n");
- Wire.end();
-#ifdef I2C_SDA1
- Wire1.end();
-#endif
} else {
LOG_INFO("%i I2C devices found\n", i2cCount);
}
@@ -541,6 +533,14 @@ void setup()
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I)
+ SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620)
+ SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::VEML7700, meshtastic_TelemetrySensorType_VEML7700)
+ SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::TSL2591, meshtastic_TelemetrySensorType_TSL25911FN)
+ SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::OPT3001, meshtastic_TelemetrySensorType_OPT3001)
+ SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MLX90632, meshtastic_TelemetrySensorType_MLX90632)
+ SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X)
+ SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10)
+ SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::DFROBOT_LARK, meshtastic_TelemetrySensorType_DFROBOT_LARK)
i2cScanner.reset();
@@ -548,10 +548,6 @@ void setup()
setupSDCard();
#endif
-#ifdef RAK4630
- // scanEInkDevice();
-#endif
-
// LED init
#ifdef LED_PIN
@@ -607,7 +603,7 @@ void setup()
screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64
#endif
-#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
+#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
if (acc_info.type != ScanI2C::DeviceType::NONE) {
config.display.wake_on_tap_or_motion = true;
moduleConfig.external_notification.enabled = true;
@@ -615,7 +611,9 @@ void setup()
}
#endif
-#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
+#if defined(HAS_NEOPIXEL) || defined(UNPHONE) || defined(RGBLED_RED)
+ ambientLightingThread = new AmbientLightingThread(ScanI2C::DeviceType::NONE);
+#elif !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
if (rgb_found.type != ScanI2C::DeviceType::NONE) {
ambientLightingThread = new AmbientLightingThread(rgb_found.type);
}
@@ -694,6 +692,12 @@ void setup()
// Now that the mesh service is created, create any modules
setupModules();
+#ifdef LED_PIN
+ // Turn LED off after boot, if heartbeat by config
+ if (config.device.led_heartbeat_disabled)
+ digitalWrite(LED_PIN, LOW ^ LED_INVERTED);
+#endif
+
// Do this after service.init (because that clears error_code)
#ifdef HAS_PMU
if (!pmu_found)
@@ -730,7 +734,8 @@ void setup()
if (settingsMap[use_sx1262]) {
if (!rIf) {
LOG_DEBUG("Attempting to activate sx1262 radio on SPI port %s\n", settingsStrings[spidev].c_str());
- LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings);
+ LockingArduinoHal *RadioLibHAL =
+ new LockingArduinoHal(SPI, spiSettings, (settingsMap[ch341Quirk] ? settingsMap[busy] : RADIOLIB_NC));
rIf = new SX1262Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
@@ -744,7 +749,8 @@ void setup()
} else if (settingsMap[use_rf95]) {
if (!rIf) {
LOG_DEBUG("Attempting to activate rf95 radio on SPI port %s\n", settingsStrings[spidev].c_str());
- LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings);
+ LockingArduinoHal *RadioLibHAL =
+ new LockingArduinoHal(SPI, spiSettings, (settingsMap[ch341Quirk] ? settingsMap[busy] : RADIOLIB_NC));
rIf = new RF95Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
@@ -759,7 +765,7 @@ void setup()
} else if (settingsMap[use_sx1280]) {
if (!rIf) {
LOG_DEBUG("Attempting to activate sx1280 radio on SPI port %s\n", settingsStrings[spidev].c_str());
- LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings);
+ LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
rIf = new SX1280Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
@@ -771,6 +777,21 @@ void setup()
LOG_INFO("SX1280 Radio init succeeded, using SX1280 radio\n");
}
}
+ } else if (settingsMap[use_sx1268]) {
+ if (!rIf) {
+ LOG_DEBUG("Attempting to activate sx1268 radio on SPI port %s\n", settingsStrings[spidev].c_str());
+ LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
+ rIf = new SX1268Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
+ settingsMap[busy]);
+ if (!rIf->init()) {
+ LOG_ERROR("Failed to find SX1268 radio\n");
+ delete rIf;
+ rIf = NULL;
+ exit(EXIT_FAILURE);
+ } else {
+ LOG_INFO("SX1268 Radio init succeeded, using SX1268 radio\n");
+ }
+ }
}
#elif defined(HW_SPI1_DEVICE)
@@ -864,6 +885,32 @@ void setup()
}
#endif
+#if defined(USE_LR1110)
+ if (!rIf) {
+ rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESER_PIN, LR1110_BUSY_PIN);
+ if (!rIf->init()) {
+ LOG_WARN("Failed to find LR1110 radio\n");
+ delete rIf;
+ rIf = NULL;
+ } else {
+ LOG_INFO("LR1110 Radio init succeeded, using LR1110 radio\n");
+ }
+ }
+#endif
+
+#if defined(USE_LR1120)
+ if (!rIf) {
+ rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESER_PIN, LR1120_BUSY_PIN);
+ if (!rIf->init()) {
+ LOG_WARN("Failed to find LR1120 radio\n");
+ delete rIf;
+ rIf = NULL;
+ } else {
+ LOG_INFO("LR1120 Radio init succeeded, using LR1120 radio\n");
+ }
+ }
+#endif
+
#if defined(USE_SX1280)
if (!rIf) {
rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY);
@@ -1009,4 +1056,4 @@ void loop()
mainDelay.delay(delayMsec);
}
// if (didWake) LOG_DEBUG("wake!\n");
-}
+}
\ No newline at end of file
diff --git a/src/main.h b/src/main.h
index bb812b7b6..2ef7edb3a 100644
--- a/src/main.h
+++ b/src/main.h
@@ -53,13 +53,18 @@ extern Adafruit_DRV2605 drv;
extern AudioThread *audioThread;
#endif
+// Global Screen singleton.
+extern graphics::Screen *screen;
+
+#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+#include "AccelerometerThread.h"
+extern AccelerometerThread *accelerometerThread;
+#endif
+
extern bool isVibrating;
extern int TCPPort; // set by Portduino
-// Global Screen singleton.
-extern graphics::Screen *screen;
-
// extern Observable newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class
// extern meshtastic::PowerStatus *powerStatus;
diff --git a/src/memGet.cpp b/src/memGet.cpp
index 0575da279..e982ef7ee 100644
--- a/src/memGet.cpp
+++ b/src/memGet.cpp
@@ -22,6 +22,8 @@ uint32_t MemGet::getFreeHeap()
return ESP.getFreeHeap();
#elif defined(ARCH_NRF52)
return dbgHeapFree();
+#elif defined(ARCH_RP2040)
+ return rp2040.getFreeHeap();
#else
// this platform does not have heap management function implemented
return UINT32_MAX;
@@ -38,6 +40,8 @@ uint32_t MemGet::getHeapSize()
return ESP.getHeapSize();
#elif defined(ARCH_NRF52)
return dbgHeapTotal();
+#elif defined(ARCH_RP2040)
+ return rp2040.getTotalHeap();
#else
// this platform does not have heap management function implemented
return UINT32_MAX;
diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index 4cfe982d8..dd547a6f1 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -35,11 +35,10 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
{
- bool isAck =
- ((c && c->error_reason == meshtastic_Routing_Error_NONE)); // consider only ROUTING_APP message without error as ACK
- if (isAck && p->to != getNodeNum()) {
- // do not flood direct message that is ACKed
- LOG_DEBUG("Receiving an ACK not for me, but don't need to rebroadcast this direct message anymore.\n");
+ bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) && (p->decoded.request_id != 0);
+ if (isAckorReply && p->to != getNodeNum() && p->to != NODENUM_BROADCAST) {
+ // do not flood direct message that is ACKed or replied to
+ LOG_DEBUG("Receiving an ACK or reply not for me, but don't need to rebroadcast this direct message anymore.\n");
Router::cancelSending(p->to, p->decoded.request_id); // cancel rebroadcast for this DM
}
if ((p->to != getNodeNum()) && (p->hop_limit > 0) && (getFrom(p) != getNodeNum())) {
diff --git a/src/mesh/InterfacesTemplates.cpp b/src/mesh/InterfacesTemplates.cpp
index c732829e9..f2cac8028 100644
--- a/src/mesh/InterfacesTemplates.cpp
+++ b/src/mesh/InterfacesTemplates.cpp
@@ -1,3 +1,5 @@
+#include "LR11x0Interface.cpp"
+#include "LR11x0Interface.h"
#include "SX126xInterface.cpp"
#include "SX126xInterface.h"
#include "SX128xInterface.cpp"
@@ -10,6 +12,8 @@ template class SX126xInterface;
template class SX126xInterface;
template class SX126xInterface;
template class SX128xInterface;
+template class LR11x0Interface;
+template class LR11x0Interface;
#ifdef ARCH_STM32WL
template class SX126xInterface;
#endif
diff --git a/src/mesh/LR1110Interface.cpp b/src/mesh/LR1110Interface.cpp
new file mode 100644
index 000000000..c000bd838
--- /dev/null
+++ b/src/mesh/LR1110Interface.cpp
@@ -0,0 +1,9 @@
+#include "LR1110Interface.h"
+#include "configuration.h"
+#include "error.h"
+
+LR1110Interface::LR1110Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
+ RADIOLIB_PIN_TYPE busy)
+ : LR11x0Interface(hal, cs, irq, rst, busy)
+{
+}
\ No newline at end of file
diff --git a/src/mesh/LR1110Interface.h b/src/mesh/LR1110Interface.h
new file mode 100644
index 000000000..79e7c36ca
--- /dev/null
+++ b/src/mesh/LR1110Interface.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "LR11x0Interface.h"
+
+/**
+ * Our adapter for LR1110 radios
+ */
+class LR1110Interface : public LR11x0Interface
+{
+ public:
+ LR1110Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
+ RADIOLIB_PIN_TYPE busy);
+};
\ No newline at end of file
diff --git a/src/mesh/LR1120Interface.cpp b/src/mesh/LR1120Interface.cpp
new file mode 100644
index 000000000..94f3568f7
--- /dev/null
+++ b/src/mesh/LR1120Interface.cpp
@@ -0,0 +1,9 @@
+#include "LR1120Interface.h"
+#include "configuration.h"
+#include "error.h"
+
+LR1120Interface::LR1120Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
+ RADIOLIB_PIN_TYPE busy)
+ : LR11x0Interface(hal, cs, irq, rst, busy)
+{
+}
\ No newline at end of file
diff --git a/src/mesh/LR1120Interface.h b/src/mesh/LR1120Interface.h
new file mode 100644
index 000000000..fc59293ec
--- /dev/null
+++ b/src/mesh/LR1120Interface.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "LR11x0Interface.h"
+
+/**
+ * Our adapter for LR1120 wideband radios
+ */
+class LR1120Interface : public LR11x0Interface
+{
+ public:
+ LR1120Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
+ RADIOLIB_PIN_TYPE busy);
+};
\ No newline at end of file
diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp
new file mode 100644
index 000000000..bffca0c44
--- /dev/null
+++ b/src/mesh/LR11x0Interface.cpp
@@ -0,0 +1,299 @@
+#include "LR11x0Interface.h"
+#include "configuration.h"
+#include "error.h"
+#include "mesh/NodeDB.h"
+#ifdef ARCH_PORTDUINO
+#include "PortduinoGlue.h"
+#endif
+
+// Particular boards might define a different max power based on what their hardware can do, default to max power output if not
+// specified (may be dangerous if using external PA and LR11x0 power config forgotten)
+#ifndef LR11X0_MAX_POWER
+#define LR11X0_MAX_POWER 22
+#endif
+
+template
+LR11x0Interface::LR11x0Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
+ RADIOLIB_PIN_TYPE busy)
+ : RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module)
+{
+ LOG_WARN("LR11x0Interface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
+}
+
+/// Initialise the Driver transport hardware and software.
+/// Make sure the Driver is properly configured before calling init().
+/// \return true if initialisation succeeded.
+template bool LR11x0Interface::init()
+{
+#ifdef LR11X0_POWER_EN
+ pinMode(LR11X0_POWER_EN, OUTPUT);
+ digitalWrite(LR11X0_POWER_EN, HIGH);
+#endif
+
+// FIXME: correct logic to default to not using TCXO if no voltage is specified for LR11x0_DIO3_TCXO_VOLTAGE
+#if !defined(LR11X0_DIO3_TCXO_VOLTAGE)
+ float tcxoVoltage =
+ 0; // "TCXO reference voltage to be set on DIO3. Defaults to 1.6 V, set to 0 to skip." per
+ // https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/LR11x0/LR11x0.h#L471C26-L471C104
+ // (DIO3 is free to be used as an IRQ)
+ LOG_DEBUG("LR11X0_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage\n");
+#else
+ float tcxoVoltage = LR11X0_DIO3_TCXO_VOLTAGE;
+ LOG_DEBUG("LR11X0_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V\n", LR11X0_DIO3_TCXO_VOLTAGE);
+ // (DIO3 is not free to be used as an IRQ)
+#endif
+
+ RadioLibInterface::init();
+
+ if (power > LR11X0_MAX_POWER) // Clamp power to maximum defined level
+ power = LR11X0_MAX_POWER;
+
+ limitPower();
+
+ // set RF switch configuration for Wio WM1110
+ // Wio WM1110 uses DIO5 and DIO6 for RF switching
+ // NOTE: other boards may be different. If you are
+ // using a different board, you may need to wrap
+ // this in a conditional.
+
+ static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC,
+ RADIOLIB_NC};
+
+ static const Module::RfSwitchMode_t rfswitch_table[] = {
+ // mode DIO5 DIO6
+ {LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}},
+ {LR11x0::MODE_TX, {HIGH, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}},
+ {LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}},
+ {LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE,
+ };
+
+// We need to do this before begin() call
+#ifdef LR11X0_DIO_AS_RF_SWITCH
+ LOG_DEBUG("Setting DIO RF switch\n");
+ bool dioAsRfSwitch = true;
+#elif defined(ARCH_PORTDUINO)
+ bool dioAsRfSwitch = false;
+ if (settingsMap[dio2_as_rf_switch]) {
+ LOG_DEBUG("Setting DIO RF switch\n");
+ dioAsRfSwitch = true;
+ }
+#else
+ bool dioAsRfSwitch = false;
+#endif
+
+ if (dioAsRfSwitch)
+ lora.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table);
+
+ int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
+ // \todo Display actual typename of the adapter, not just `LR11x0`
+ LOG_INFO("LR11x0 init result %d\n", res);
+ if (res == RADIOLIB_ERR_CHIP_NOT_FOUND)
+ return false;
+
+ LOG_INFO("Frequency set to %f\n", getFreq());
+ LOG_INFO("Bandwidth set to %f\n", bw);
+ LOG_INFO("Power output set to %d\n", power);
+
+ if (res == RADIOLIB_ERR_NONE)
+ res = lora.setCRC(2);
+
+ // FIXME: May want to set depending on a definition, currently all LR1110 variant files use the DC-DC regulator option
+ if (res == RADIOLIB_ERR_NONE)
+ res = lora.setRegulatorDCDC();
+
+ if (res == RADIOLIB_ERR_NONE) {
+ if (config.lora.sx126x_rx_boosted_gain) { // the name is unfortunate but historically accurate
+ res = lora.setRxBoostedGainMode(true);
+ LOG_INFO("Set RX gain to boosted mode; result: %d\n", res);
+ } else {
+ res = lora.setRxBoostedGainMode(false);
+ LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d\n", res);
+ }
+ }
+
+ if (res == RADIOLIB_ERR_NONE)
+ startReceive(); // start receiving
+
+ return res == RADIOLIB_ERR_NONE;
+}
+
+template bool LR11x0Interface::reconfigure()
+{
+ RadioLibInterface::reconfigure();
+
+ // set mode to standby
+ setStandby();
+
+ // configure publicly accessible settings
+ int err = lora.setSpreadingFactor(sf);
+ if (err != RADIOLIB_ERR_NONE)
+ RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
+
+ err = lora.setBandwidth(bw);
+ if (err != RADIOLIB_ERR_NONE)
+ RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
+
+ err = lora.setCodingRate(cr);
+ if (err != RADIOLIB_ERR_NONE)
+ RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
+
+ // Hmm - seems to lower SNR when the signal levels are high. Leaving off for now...
+ // TODO: Confirm gain registers are okay now
+ // err = lora.setRxGain(true);
+ // assert(err == RADIOLIB_ERR_NONE);
+
+ err = lora.setSyncWord(syncWord);
+ assert(err == RADIOLIB_ERR_NONE);
+
+ err = lora.setPreambleLength(preambleLength);
+ assert(err == RADIOLIB_ERR_NONE);
+
+ err = lora.setFrequency(getFreq());
+ if (err != RADIOLIB_ERR_NONE)
+ RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
+
+ if (power > LR11X0_MAX_POWER) // This chip has lower power limits than some
+ power = LR11X0_MAX_POWER;
+
+ err = lora.setOutputPower(power);
+ assert(err == RADIOLIB_ERR_NONE);
+
+ startReceive(); // restart receiving
+
+ return RADIOLIB_ERR_NONE;
+}
+
+template void INTERRUPT_ATTR LR11x0Interface::disableInterrupt()
+{
+ lora.clearIrqAction();
+}
+
+template void LR11x0Interface::setStandby()
+{
+ checkNotification(); // handle any pending interrupts before we force standby
+
+ int err = lora.standby();
+
+ if (err != RADIOLIB_ERR_NONE) {
+ LOG_DEBUG("LR11x0 standby failed with error %d\n", err);
+ }
+
+ assert(err == RADIOLIB_ERR_NONE);
+
+ isReceiving = false; // If we were receiving, not any more
+ activeReceiveStart = 0;
+ disableInterrupt();
+ completeSending(); // If we were sending, not anymore
+}
+
+/**
+ * Add SNR data to received messages
+ */
+template void LR11x0Interface::addReceiveMetadata(meshtastic_MeshPacket *mp)
+{
+ // LOG_DEBUG("PacketStatus %x\n", lora.getPacketStatus());
+ mp->rx_snr = lora.getSNR();
+ mp->rx_rssi = lround(lora.getRSSI());
+}
+
+/** We override to turn on transmitter power as needed.
+ */
+template void LR11x0Interface::configHardwareForSend()
+{
+ RadioLibInterface::configHardwareForSend();
+}
+
+// For power draw measurements, helpful to force radio to stay sleeping
+// #define SLEEP_ONLY
+
+template void LR11x0Interface::startReceive()
+{
+#ifdef SLEEP_ONLY
+ sleep();
+#else
+
+ setStandby();
+
+ lora.setPreambleLength(preambleLength); // Solve RX ack fail after direct message sent. Not sure why this is needed.
+
+ // We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly.
+ // Furthermore, we need the PREAMBLE_DETECTED and HEADER_VALID IRQ flag to detect whether we are actively receiving
+ int err = lora.startReceive(
+ RADIOLIB_LR11X0_RX_TIMEOUT_INF, RADIOLIB_LR11X0_IRQ_RX_DONE,
+ 0); // only RX_DONE IRQ is needed, we'll check for PREAMBLE_DETECTED and HEADER_VALID in isActivelyReceiving
+ assert(err == RADIOLIB_ERR_NONE);
+
+ isReceiving = true;
+
+ // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits
+ enableInterrupt(isrRxLevel0);
+#endif
+}
+
+/** Is the channel currently active? */
+template bool LR11x0Interface::isChannelActive()
+{
+ // check if we can detect a LoRa preamble on the current channel
+ int16_t result;
+
+ setStandby();
+ result = lora.scanChannel();
+ if (result == RADIOLIB_LORA_DETECTED)
+ return true;
+
+ assert(result != RADIOLIB_ERR_WRONG_MODEM);
+
+ return false;
+}
+
+/** Could we send right now (i.e. either not actively receiving or transmitting)? */
+template bool LR11x0Interface::isActivelyReceiving()
+{
+ // The IRQ status will be cleared when we start our read operation. Check if we've started a header, but haven't yet
+ // received and handled the interrupt for reading the packet/handling errors.
+
+ uint16_t irq = lora.getIrqStatus();
+ bool detected = (irq & (RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID | RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED));
+ // Handle false detections
+ if (detected) {
+ uint32_t now = millis();
+ if (!activeReceiveStart) {
+ activeReceiveStart = now;
+ } else if ((now - activeReceiveStart > 2 * preambleTimeMsec) && !(irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID)) {
+ // The HEADER_VALID flag should be set by now if it was really a packet, so ignore PREAMBLE_DETECTED flag
+ activeReceiveStart = 0;
+ LOG_DEBUG("Ignore false preamble detection.\n");
+ return false;
+ } else if (now - activeReceiveStart > maxPacketTimeMsec) {
+ // We should have gotten an RX_DONE IRQ by now if it was really a packet, so ignore HEADER_VALID flag
+ activeReceiveStart = 0;
+ LOG_DEBUG("Ignore false header detection.\n");
+ return false;
+ }
+ }
+
+ // if (detected) LOG_DEBUG("rx detected\n");
+ return detected;
+}
+
+template bool LR11x0Interface::sleep()
+{
+ // Not keeping config is busted - next time nrf52 board boots lora sending fails tcxo related? - see datasheet
+ // \todo Display actual typename of the adapter, not just `LR11x0`
+ LOG_DEBUG("LR11x0 entering sleep mode (FIXME, don't keep config)\n");
+ setStandby(); // Stop any pending operations
+
+ // turn off TCXO if it was powered
+ // FIXME - this isn't correct
+ // lora.setTCXO(0);
+
+ // put chipset into sleep mode (we've already disabled interrupts by now)
+ bool keepConfig = true;
+ lora.sleep(keepConfig, 0); // Note: we do not keep the config, full reinit will be needed
+
+#ifdef LR11X0_POWER_EN
+ digitalWrite(LR11X0_POWER_EN, LOW);
+#endif
+
+ return true;
+}
\ No newline at end of file
diff --git a/src/mesh/LR11x0Interface.h b/src/mesh/LR11x0Interface.h
new file mode 100644
index 000000000..11a389d25
--- /dev/null
+++ b/src/mesh/LR11x0Interface.h
@@ -0,0 +1,71 @@
+#pragma once
+
+#include "RadioLibInterface.h"
+
+/**
+ * \brief Adapter for LR11x0 radio family. Implements common logic for child classes.
+ * \tparam T RadioLib module type for LR11x0: SX1262, SX1268.
+ */
+template class LR11x0Interface : public RadioLibInterface
+{
+ public:
+ LR11x0Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
+ RADIOLIB_PIN_TYPE busy);
+
+ /// Initialise the Driver transport hardware and software.
+ /// Make sure the Driver is properly configured before calling init().
+ /// \return true if initialisation succeeded.
+ virtual bool init() override;
+
+ /// Apply any radio provisioning changes
+ /// Make sure the Driver is properly configured before calling init().
+ /// \return true if initialisation succeeded.
+ virtual bool reconfigure() override;
+
+ /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep.
+ virtual bool sleep() override;
+
+ bool isIRQPending() override { return lora.getIrqStatus() != 0; }
+
+ protected:
+ /**
+ * Specific module instance
+ */
+ T lora;
+
+ /**
+ * Glue functions called from ISR land
+ */
+ virtual void disableInterrupt() override;
+
+ /**
+ * Enable a particular ISR callback glue function
+ */
+ virtual void enableInterrupt(void (*callback)()) { lora.setIrqAction(callback); }
+
+ /** can we detect a LoRa preamble on the current channel? */
+ virtual bool isChannelActive() override;
+
+ /** are we actively receiving a packet (only called during receiving state) */
+ virtual bool isActivelyReceiving() override;
+
+ /**
+ * Start waiting to receive a message
+ */
+ virtual void startReceive() override;
+
+ /**
+ * We override to turn on transmitter power as needed.
+ */
+ virtual void configHardwareForSend() override;
+
+ /**
+ * Add SNR data to received messages
+ */
+ virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
+
+ virtual void setStandby() override;
+
+ private:
+ uint32_t activeReceiveStart = 0;
+};
diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp
index 2ef46e4db..04fa250bf 100644
--- a/src/mesh/MeshModule.cpp
+++ b/src/mesh/MeshModule.cpp
@@ -62,7 +62,10 @@ meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, Nod
meshtastic_MeshPacket *MeshModule::allocErrorResponse(meshtastic_Routing_Error err, const meshtastic_MeshPacket *p)
{
- auto r = allocAckNak(err, getFrom(p), p->id, p->channel);
+ // If the original packet couldn't be decoded, use the primary channel
+ uint8_t channelIndex =
+ p->which_payload_variant == meshtastic_MeshPacket_decoded_tag ? p->channel : channels.getPrimaryIndex();
+ auto r = allocAckNak(err, getFrom(p), p->id, channelIndex);
setReplyTo(r, *p);
@@ -114,13 +117,13 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
/// Also: if a packet comes in on the local PC interface, we don't check for bound channels, because it is TRUSTED and
/// it needs to to be able to fetch the initial admin packets without yet knowing any channels.
- bool rxChannelOk = !pi.boundChannel || (mp.from == 0) || (strcasecmp(ch->settings.name, pi.boundChannel) == 0);
+ bool rxChannelOk = !pi.boundChannel || (mp.from == 0) || (ch && strcasecmp(ch->settings.name, pi.boundChannel) == 0);
if (!rxChannelOk) {
// no one should have already replied!
assert(!currentReply);
- if (mp.decoded.want_response) {
+ if (isDecoded && mp.decoded.want_response) {
printPacket("packet on wrong channel, returning error", &mp);
currentReply = pi.allocErrorResponse(meshtastic_Routing_Error_NOT_AUTHORIZED, &mp);
} else
@@ -138,7 +141,8 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
// because currently when the phone sends things, it sends things using the local node ID as the from address. A
// better solution (FIXME) would be to let phones have their own distinct addresses and we 'route' to them like
// any other node.
- if (mp.decoded.want_response && toUs && (getFrom(&mp) != ourNodeNum || mp.to == ourNodeNum) && !currentReply) {
+ if (isDecoded && mp.decoded.want_response && toUs && (getFrom(&mp) != ourNodeNum || mp.to == ourNodeNum) &&
+ !currentReply) {
pi.sendResponse(mp);
ignoreRequest = ignoreRequest || pi.ignoreRequest; // If at least one module asks it, we may ignore a request
LOG_INFO("Asked module '%s' to send a response\n", pi.name);
@@ -163,7 +167,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
pi.currentRequest = NULL;
}
- if (mp.decoded.want_response && toUs) {
+ if (isDecoded && mp.decoded.want_response && toUs) {
if (currentReply) {
printPacket("Sending response", currentReply);
service.sendToMesh(currentReply);
@@ -183,7 +187,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
}
}
- if (!moduleFound) {
+ if (!moduleFound && isDecoded) {
LOG_DEBUG("No modules interested in portnum=%d, src=%s\n", mp.decoded.portnum,
(src == RX_SRC_LOCAL) ? "LOCAL" : "REMOTE");
}
diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp
index 2c1969e30..2cfb4843c 100644
--- a/src/mesh/MeshService.cpp
+++ b/src/mesh/MeshService.cpp
@@ -192,12 +192,7 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
return;
}
#endif
- if (p.from != 0) { // We don't let phones assign nodenums to their sent messages
- LOG_WARN("phone tried to pick a nodenum, we don't allow that.\n");
- p.from = 0;
- } else {
- // p.from = nodeDB->getNodeNum();
- }
+ p.from = 0; // We don't let phones assign nodenums to their sent messages
if (p.id == 0)
p.id = generatePacketId(); // If the phone didn't supply one, then pick one
@@ -262,12 +257,12 @@ void MeshService::sendToMesh(meshtastic_MeshPacket *p, RxSource src, bool ccToPh
LOG_DEBUG("Can't send status to phone");
}
- if (ccToPhone) {
+ if (res == ERRNO_OK && ccToPhone) { // Check if p is not released in case it couldn't be sent
sendToPhone(packetPool.allocCopy(*p));
}
}
-void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
+bool MeshService::trySendPosition(NodeNum dest, bool wantReplies)
{
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
@@ -278,6 +273,7 @@ void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
if (positionModule) {
LOG_INFO("Sending position ping to 0x%x, wantReplies=%d, channel=%d\n", dest, wantReplies, node->channel);
positionModule->sendOurPosition(dest, wantReplies, node->channel);
+ return true;
}
} else {
#endif
@@ -286,6 +282,7 @@ void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
nodeInfoModule->sendOurNodeInfo(dest, wantReplies, node->channel);
}
}
+ return false;
}
void MeshService::sendToPhone(meshtastic_MeshPacket *p)
diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h
index 8d1434030..d777b7a01 100644
--- a/src/mesh/MeshService.h
+++ b/src/mesh/MeshService.h
@@ -108,8 +108,9 @@ class MeshService
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
- void sendNetworkPing(NodeNum dest, bool wantReplies = false);
+ /// sends our nodeinfo
+ /// returns true if we sent a position
+ bool trySendPosition(NodeNum dest, bool wantReplies = false);
/// Send a packet into the mesh - note p must have been allocated from packetPool. We will return it to that pool after
/// sending. This is the ONLY function you should use for sending messages into the mesh, because it also updates the nodedb
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 39422b454..cf576e94f 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -56,7 +56,7 @@ meshtastic_OEMStore oemStore;
bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field)
{
if (ostream) {
- std::vector *vec = (std::vector *)field->pData;
+ std::vector const *vec = (std::vector *)field->pData;
for (auto item : *vec) {
if (!pb_encode_tag_for_field(ostream, field))
return false;
@@ -269,11 +269,11 @@ void NodeDB::installDefaultConfig()
config.device.node_info_broadcast_secs = default_node_info_broadcast_secs;
config.device.serial_enabled = true;
resetRadioConfig();
- strncpy(config.network.ntp_server, "0.pool.ntp.org", 32);
+ strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
// FIXME: Default to bluetooth capability of platform as default
config.bluetooth.enabled = true;
config.bluetooth.fixed_pin = defaultBLEPin;
-#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS)
+#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS)
bool hasScreen = true;
#elif ARCH_PORTDUINO
bool hasScreen = false;
@@ -292,6 +292,9 @@ void NodeDB::installDefaultConfig()
meshtastic_Config_PositionConfig_PositionFlags_SPEED | meshtastic_Config_PositionConfig_PositionFlags_HEADING |
meshtastic_Config_PositionConfig_PositionFlags_DOP | meshtastic_Config_PositionConfig_PositionFlags_SATINVIEW);
+#ifdef RADIOMASTER_900_BANDIT_NANO
+ config.display.flip_screen = true;
+#endif
#ifdef T_WATCH_S3
config.display.screen_on_secs = 30;
config.display.wake_on_tap_or_motion = true;
@@ -352,9 +355,6 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.alert_message = true;
moduleConfig.external_notification.output_ms = 100;
moduleConfig.external_notification.active = true;
-#endif
-#ifdef TTGO_T_ECHO
- config.display.wake_on_tap_or_motion = true; // Enable touch button for screen-on / refresh
#endif
moduleConfig.has_canned_message = true;
@@ -444,15 +444,15 @@ void NodeDB::installDefaultChannels()
void NodeDB::resetNodes()
{
+ clearLocalPosition();
numMeshNodes = 1;
std::fill(devicestate.node_db_lite.begin() + 1, devicestate.node_db_lite.end(), meshtastic_NodeInfoLite());
- clearLocalPosition();
saveDeviceStateToDisk();
if (neighborInfoModule && moduleConfig.neighbor_info.enabled)
neighborInfoModule->resetNeighbors();
}
-void NodeDB::removeNodeByNum(uint nodeNum)
+void NodeDB::removeNodeByNum(NodeNum nodeNum)
{
int newPos = 0, removed = 0;
for (int i = 0; i < numMeshNodes; i++) {
diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h
index 4946672ec..e9e36cc61 100644
--- a/src/mesh/NodeDB.h
+++ b/src/mesh/NodeDB.h
@@ -124,7 +124,7 @@ class NodeDB
*/
size_t getNumOnlineMeshNodes(bool localOnly = false);
- void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(uint nodeNum);
+ void initConfigIntervals(), initModuleConfigIntervals(), resetNodes(), removeNodeByNum(NodeNum nodeNum);
bool factoryReset();
diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index 2a69d6d56..26d0d9525 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -140,16 +140,18 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
*
* We assume buf is at least FromRadio_size bytes long.
*
- * Our sending states progress in the following sequence (the client app ASSUMES THIS SEQUENCE, DO NOT CHANGE IT):
- * STATE_SEND_MY_INFO, // send our my info record
- * STATE_SEND_CHANNELS
- * STATE_SEND_NODEINFO, // states progress in this order as the device sends to the client
- STATE_SEND_CONFIG,
- STATE_SEND_MODULE_CONFIG,
- STATE_SEND_METADATA,
- STATE_SEND_COMPLETE_ID,
- STATE_SEND_PACKETS // send packets or debug strings
+ * Our sending states progress in the following sequence (the client apps ASSUME THIS SEQUENCE, DO NOT CHANGE IT):
+ STATE_SEND_MY_INFO, // send our my info record
+ STATE_SEND_OWN_NODEINFO,
+ STATE_SEND_METADATA,
+ STATE_SEND_CHANNELS
+ STATE_SEND_CONFIG,
+ STATE_SEND_MODULE_CONFIG,
+ STATE_SEND_OTHER_NODEINFOS, // states progress in this order as the device sends to the client
+ STATE_SEND_COMPLETE_ID,
+ STATE_SEND_PACKETS // send packets or debug strings
*/
+
size_t PhoneAPI::getFromRadio(uint8_t *buf)
{
if (!available()) {
@@ -171,37 +173,32 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// app not to send locations on our behalf.
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_my_info_tag;
fromRadioScratch.my_info = myNodeInfo;
- state = STATE_SEND_METADATA;
+ state = STATE_SEND_OWN_NODEINFO;
service.refreshLocalMeshNode(); // Update my NodeInfo because the client will be asking for it soon.
break;
+ case STATE_SEND_OWN_NODEINFO: {
+ LOG_INFO("getFromRadio=STATE_SEND_OWN_NODEINFO\n");
+ auto us = nodeDB->readNextMeshNode(readIndex);
+ if (us) {
+ nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(us);
+ fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag;
+ fromRadioScratch.node_info = nodeInfoForPhone;
+ // Should allow us to resume sending NodeInfo in STATE_SEND_OTHER_NODEINFOS
+ nodeInfoForPhone.num = 0;
+ }
+ state = STATE_SEND_METADATA;
+ break;
+ }
+
case STATE_SEND_METADATA:
LOG_INFO("getFromRadio=STATE_SEND_METADATA\n");
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_metadata_tag;
fromRadioScratch.metadata = getDeviceMetadata();
- state = STATE_SEND_NODEINFO;
+ state = STATE_SEND_CHANNELS;
break;
- case STATE_SEND_NODEINFO: {
- LOG_INFO("getFromRadio=STATE_SEND_NODEINFO\n");
-
- if (nodeInfoForPhone.num != 0) {
- LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
- nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name);
- fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag;
- fromRadioScratch.node_info = nodeInfoForPhone;
- // Stay in current state until done sending nodeinfos
- nodeInfoForPhone.num = 0; // We just consumed a nodeinfo, will need a new one next time
- } else {
- LOG_INFO("Done sending nodeinfos\n");
- state = STATE_SEND_CHANNELS;
- // Go ahead and send that ID right now
- return getFromRadio(buf);
- }
- break;
- }
-
case STATE_SEND_CHANNELS:
LOG_INFO("getFromRadio=STATE_SEND_CHANNELS\n");
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_channel_tag;
@@ -325,11 +322,30 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
config_state++;
// Advance when we have sent all of our ModuleConfig objects
if (config_state > (_meshtastic_AdminMessage_ModuleConfigType_MAX + 1)) {
- state = STATE_SEND_COMPLETE_ID;
+ // Clients sending special nonce don't want to see other nodeinfos
+ state = config_nonce == SPECIAL_NONCE ? STATE_SEND_COMPLETE_ID : STATE_SEND_OTHER_NODEINFOS;
config_state = 0;
}
break;
+ case STATE_SEND_OTHER_NODEINFOS: {
+ LOG_INFO("getFromRadio=STATE_SEND_OTHER_NODEINFOS\n");
+ if (nodeInfoForPhone.num != 0) {
+ LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
+ nodeInfoForPhone.user.id, nodeInfoForPhone.user.long_name);
+ fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag;
+ fromRadioScratch.node_info = nodeInfoForPhone;
+ // Stay in current state until done sending nodeinfos
+ nodeInfoForPhone.num = 0; // We just consumed a nodeinfo, will need a new one next time
+ } else {
+ LOG_INFO("Done sending nodeinfos\n");
+ state = STATE_SEND_COMPLETE_ID;
+ // Go ahead and send that ID right now
+ return getFromRadio(buf);
+ }
+ break;
+ }
+
case STATE_SEND_COMPLETE_ID:
LOG_INFO("getFromRadio=STATE_SEND_COMPLETE_ID\n");
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_complete_id_tag;
@@ -422,10 +438,11 @@ bool PhoneAPI::available()
case STATE_SEND_CONFIG:
case STATE_SEND_MODULECONFIG:
case STATE_SEND_METADATA:
+ case STATE_SEND_OWN_NODEINFO:
case STATE_SEND_COMPLETE_ID:
return true;
- case STATE_SEND_NODEINFO:
+ case STATE_SEND_OTHER_NODEINFOS:
if (nodeInfoForPhone.num == 0) {
auto nextNode = nodeDB->readNextMeshNode(readIndex);
if (nextNode) {
diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h
index 450649d7b..49bf0e292 100644
--- a/src/mesh/PhoneAPI.h
+++ b/src/mesh/PhoneAPI.h
@@ -6,6 +6,7 @@
// Make sure that we never let our packets grow too large for one BLE packet
#define MAX_TO_FROM_RADIO_SIZE 512
+#define SPECIAL_NONCE 69420
/**
* Provides our protobuf based API which phone/PC clients can use to talk to our device
@@ -20,13 +21,14 @@ class PhoneAPI
: public Observer // FIXME, we shouldn't be inheriting from Observer, instead use CallbackObserver as a member
{
enum State {
- STATE_SEND_NOTHING, // Initial state, don't send anything until the client starts asking for config
- STATE_SEND_MY_INFO, // send our my info record
- STATE_SEND_NODEINFO, // states progress in this order as the device sends to to the client
- STATE_SEND_CHANNELS, // Send all channels
- STATE_SEND_CONFIG, // Replacement for the old Radioconfig
- STATE_SEND_MODULECONFIG, // Send Module specific config
+ STATE_SEND_NOTHING, // Initial state, don't send anything until the client starts asking for config
+ STATE_SEND_MY_INFO, // send our my info record
+ STATE_SEND_OWN_NODEINFO,
STATE_SEND_METADATA,
+ STATE_SEND_CHANNELS, // Send all channels
+ STATE_SEND_CONFIG, // Replacement for the old Radioconfig
+ STATE_SEND_MODULECONFIG, // Send Module specific config
+ STATE_SEND_OTHER_NODEINFOS, // states progress in this order as the device sends to to the client
STATE_SEND_COMPLETE_ID,
STATE_SEND_PACKETS // send packets or debug strings
};
diff --git a/src/mesh/ProtobufModule.h b/src/mesh/ProtobufModule.h
index a2e89e98a..0d3da9568 100644
--- a/src/mesh/ProtobufModule.h
+++ b/src/mesh/ProtobufModule.h
@@ -95,12 +95,11 @@ template class ProtobufModule : protected SinglePortModule
*/
virtual void alterReceived(meshtastic_MeshPacket &mp) override
{
- auto &p = mp.decoded;
-
T scratch;
T *decoded = NULL;
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.decoded.portnum == ourPortNum) {
memset(&scratch, 0, sizeof(scratch));
+ auto &p = mp.decoded;
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch)) {
decoded = &scratch;
} else {
diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp
index 8c6c349fd..c5356ad3b 100644
--- a/src/mesh/RF95Interface.cpp
+++ b/src/mesh/RF95Interface.cpp
@@ -8,12 +8,57 @@
#include "PortduinoGlue.h"
#endif
-#define MAX_POWER 20
+#ifndef RF95_MAX_POWER
+#define RF95_MAX_POWER 20
+#endif
+
// if we use 20 we are limited to 1% duty cycle or hw might overheat. For continuous operation set a limit of 17
// In theory up to 27 dBm is possible, but the modules installed in most radios can cope with a max of 20. So BIG WARNING
// if you set power to something higher than 17 or 20 you might fry your board.
#define POWER_DEFAULT 17 // How much power to use if the user hasn't set a power level
+#ifdef RADIOMASTER_900_BANDIT_NANO
+// Structure to hold DAC and DB values
+typedef struct {
+ uint8_t dac;
+ uint8_t db;
+} DACDB;
+
+// Interpolation function
+DACDB interpolate(uint8_t dbm, uint8_t dbm1, uint8_t dbm2, DACDB val1, DACDB val2) {
+ DACDB result;
+ double fraction = (double)(dbm - dbm1) / (dbm2 - dbm1);
+ result.dac = (uint8_t)(val1.dac + fraction * (val2.dac - val1.dac));
+ result.db = (uint8_t)(val1.db + fraction * (val2.db - val1.db));
+ return result;
+}
+
+// Function to find the correct DAC and DB values based on dBm using interpolation
+DACDB getDACandDB(uint8_t dbm) {
+ // Predefined values
+ static const struct {
+ uint8_t dbm;
+ DACDB values;
+ } dbmToDACDB[] = {
+ {20, {168, 2}}, // 100mW
+ {24, {148, 6}}, // 250mW
+ {27, {128, 9}}, // 500mW
+ {30, {90, 12}} // 1000mW
+ };
+ const int numValues = sizeof(dbmToDACDB) / sizeof(dbmToDACDB[0]);
+
+ // Find the interval dbm falls within and interpolate
+ for (int i = 0; i < numValues - 1; i++) {
+ if (dbm >= dbmToDACDB[i].dbm && dbm <= dbmToDACDB[i + 1].dbm) {
+ return interpolate(dbm, dbmToDACDB[i].dbm, dbmToDACDB[i + 1].dbm, dbmToDACDB[i].values, dbmToDACDB[i + 1].values);
+ }
+ }
+
+ // Return a default value if no match is found and default to 100mW
+ DACDB defaultValue = {168, 2};
+ return defaultValue;
+}
+#endif
RF95Interface::RF95Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
RADIOLIB_PIN_TYPE busy)
@@ -49,9 +94,16 @@ bool RF95Interface::init()
{
RadioLibInterface::init();
- if (power > MAX_POWER) // This chip has lower power limits than some
- power = MAX_POWER;
+#ifdef RADIOMASTER_900_BANDIT_NANO
+ // DAC and DB values based on dBm using interpolation
+ DACDB dacDbValues = getDACandDB(power);
+ int8_t powerDAC = dacDbValues.dac;
+ power = dacDbValues.db;
+#endif
+ if (power > RF95_MAX_POWER) // This chip has lower power limits than some
+ power = RF95_MAX_POWER;
+
limitPower();
iface = lora = new RadioLibRF95(&module);
@@ -61,6 +113,19 @@ bool RF95Interface::init()
digitalWrite(RF95_TCXO, 1);
#endif
+ // enable PA
+#ifdef RF95_PA_EN
+#if defined(RF95_PA_DAC_EN)
+ #ifdef RADIOMASTER_900_BANDIT_NANO
+ // Use calculated DAC value
+ dacWrite(RF95_PA_EN, powerDAC);
+ #else
+ // Use Value set in /*/variant.h
+ dacWrite(RF95_PA_EN, RF95_PA_LEVEL);
+ #endif
+#endif
+#endif
+
/*
#define RF95_TXEN (22) // If defined, this pin should be set high prior to transmit (controls an external analog switch)
#define RF95_RXEN (23) // If defined, this pin should be set high prior to receive (controls an external analog switch)
@@ -71,6 +136,11 @@ bool RF95Interface::init()
digitalWrite(RF95_TXEN, 0);
#endif
+#ifdef RF95_FAN_EN
+ pinMode(RF95_FAN_EN, OUTPUT);
+ digitalWrite(RF95_FAN_EN, 1);
+#endif
+
#ifdef RF95_RXEN
pinMode(RF95_RXEN, OUTPUT);
digitalWrite(RF95_RXEN, 1);
@@ -92,6 +162,9 @@ bool RF95Interface::init()
LOG_INFO("Frequency set to %f\n", getFreq());
LOG_INFO("Bandwidth set to %f\n", bw);
LOG_INFO("Power output set to %d\n", power);
+#ifdef RADIOMASTER_900_BANDIT_NANO
+ LOG_INFO("DAC output set to %d\n", powerDAC);
+#endif
if (res == RADIOLIB_ERR_NONE)
res = lora->setCRC(RADIOLIB_SX126X_LORA_CRC_ON);
@@ -146,10 +219,14 @@ bool RF95Interface::reconfigure()
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
- if (power > MAX_POWER) // This chip has lower power limits than some
- power = MAX_POWER;
+ if (power > RF95_MAX_POWER) // This chip has lower power limits than some
+ power = RF95_MAX_POWER;
+#ifdef USE_RF95_RFO
+ err = lora->setOutputPower(power, true);
+#else
err = lora->setOutputPower(power);
+#endif
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
@@ -235,5 +312,9 @@ bool RF95Interface::sleep()
setStandby(); // First cancel any active receiving/sending
lora->sleep();
+#ifdef RF95_FAN_EN
+ digitalWrite(RF95_FAN_EN, 0);
+#endif
+
return true;
-}
\ No newline at end of file
+}
diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index 4fa0bef7a..eb86f4267 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -31,18 +31,18 @@ const RegionInfo regions[] = {
RDEF(EU_433, 433.0f, 434.0f, 10, 0, 12, true, false, false),
/*
- https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/
- https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/
- https://www.legislation.gov.uk/uksi/1999/930/schedule/6/part/III/made/data.xht?view=snippet&wrap=true
+ https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/
+ https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/
+ https://www.legislation.gov.uk/uksi/1999/930/schedule/6/part/III/made/data.xht?view=snippet&wrap=true
- audio_permitted = false per regulation
+ audio_permitted = false per regulation
- Special Note:
- The link above describes LoRaWAN's band plan, stating a power limit of 16 dBm. This is their own suggested specification,
- we do not need to follow it. The European Union regulations clearly state that the power limit for this frequency range is
- 500 mW, or 27 dBm. It also states that we can use interference avoidance and spectrum access techniques to avoid a duty
- cycle. (Please refer to section 4.21 in the following document)
- https://ec.europa.eu/growth/tools-databases/tris/index.cfm/ro/search/?trisaction=search.detail&year=2021&num=528&dLang=EN
+ Special Note:
+ The link above describes LoRaWAN's band plan, stating a power limit of 16 dBm. This is their own suggested specification,
+ we do not need to follow it. The European Union regulations clearly state that the power limit for this frequency range is
+ 500 mW, or 27 dBm. It also states that we can use interference avoidance and spectrum access techniques (such as LBT +
+ AFA) to avoid a duty cycle. (Please refer to line P page 22 of this document.)
+ https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.01.01_60/en_30022002v030101p.pdf
*/
RDEF(EU_868, 869.4f, 869.65f, 10, 0, 27, false, false, false),
@@ -76,9 +76,12 @@ const RegionInfo regions[] = {
RDEF(KR, 920.0f, 923.0f, 100, 0, 0, true, false, false),
/*
- ???
+ Taiwan, 920-925Mhz, limited to 0.5W indoor or coastal, 1.0W outdoor.
+ 5.8.1 in the Low-power Radio-frequency Devices Technical Regulations
+ https://www.ncc.gov.tw/english/files/23070/102_5190_230703_1_doc_C.PDF
+ https://gazette.nat.gov.tw/egFront/e_detail.do?metaid=147283
*/
- RDEF(TW, 920.0f, 925.0f, 100, 0, 0, true, false, false),
+ RDEF(TW, 920.0f, 925.0f, 100, 0, 27, true, false, false),
/*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
@@ -492,7 +495,7 @@ void RadioInterface::applyModemConfig()
// If user has manually specified a channel num, then use that, otherwise generate one by hashing the name
const char *channelName = channels.getName(channels.getPrimaryIndex());
// channel_num is actually (channel_num - 1), since modulus (%) returns values from 0 to (numChannels - 1)
- uint channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels;
+ uint32_t channel_num = (loraConfig.channel_num ? loraConfig.channel_num - 1 : hash(channelName)) % numChannels;
// Check if we use the default frequency slot
RadioInterface::uses_default_frequency_slot =
@@ -586,4 +589,4 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
sendingPacket = p;
return p->encrypted.size + sizeof(PacketHeader);
-}
\ No newline at end of file
+}
diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp
index fc1563ee3..a4ceac9f1 100644
--- a/src/mesh/RadioLibInterface.cpp
+++ b/src/mesh/RadioLibInterface.cpp
@@ -25,7 +25,31 @@ void LockingArduinoHal::spiEndTransaction()
#if ARCH_PORTDUINO
void LockingArduinoHal::spiTransfer(uint8_t *out, size_t len, uint8_t *in)
{
- spi->transfer(out, in, len);
+ if (busy == RADIOLIB_NC) {
+ spi->transfer(out, in, len);
+ } else {
+ uint16_t offset = 0;
+
+ while (len) {
+ uint8_t block_size = (len < 20 ? len : 20);
+ spi->transfer((out != NULL ? out + offset : NULL), (in != NULL ? in + offset : NULL), block_size);
+ if (block_size == len)
+ return;
+
+ // ensure GPIO is low
+
+ uint32_t start = millis();
+ while (digitalRead(busy)) {
+ if (millis() - start >= 2000) {
+ LOG_ERROR("GPIO mid-transfer timeout, is it connected?");
+ return;
+ }
+ }
+
+ offset += block_size;
+ len -= block_size;
+ }
+ }
}
#endif
@@ -414,4 +438,4 @@ void RadioLibInterface::startSend(meshtastic_MeshPacket *txp)
// bits
enableInterrupt(isrTxLevel0);
}
-}
+}
\ No newline at end of file
diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h
index 62720cfc9..2c841a19e 100644
--- a/src/mesh/RadioLibInterface.h
+++ b/src/mesh/RadioLibInterface.h
@@ -21,12 +21,20 @@
class LockingArduinoHal : public ArduinoHal
{
public:
- LockingArduinoHal(SPIClass &spi, SPISettings spiSettings) : ArduinoHal(spi, spiSettings){};
+ LockingArduinoHal(SPIClass &spi, SPISettings spiSettings, RADIOLIB_PIN_TYPE _busy = RADIOLIB_NC)
+ : ArduinoHal(spi, spiSettings)
+ {
+#if ARCH_PORTDUINO
+ busy = _busy;
+#endif
+ };
void spiBeginTransaction() override;
void spiEndTransaction() override;
#if ARCH_PORTDUINO
+ RADIOLIB_PIN_TYPE busy;
void spiTransfer(uint8_t *out, size_t len, uint8_t *in) override;
+
#endif
};
@@ -179,4 +187,4 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) = 0;
virtual void setStandby() = 0;
-};
+};
\ No newline at end of file
diff --git a/src/mesh/RadioLibRF95.cpp b/src/mesh/RadioLibRF95.cpp
index 1fe7869a3..a202d4f4d 100644
--- a/src/mesh/RadioLibRF95.cpp
+++ b/src/mesh/RadioLibRF95.cpp
@@ -42,7 +42,11 @@ int16_t RadioLibRF95::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_
state = setCodingRate(cr);
RADIOLIB_ASSERT(state);
+#ifdef USE_RF95_RFO
+ state = setOutputPower(power, true);
+#else
state = setOutputPower(power);
+#endif
RADIOLIB_ASSERT(state);
state = setGain(gain);
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 4189bca66..3141d986b 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -311,7 +311,10 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
if (channels.decryptForHash(chIndex, p->channel)) {
// Try to decrypt the packet if we can
size_t rawSize = p->encrypted.size;
- assert(rawSize <= sizeof(bytes));
+ if (rawSize > sizeof(bytes)) {
+ LOG_ERROR("Packet too large to attempt decription! (rawSize=%d > 256)\n", rawSize);
+ return false;
+ }
memcpy(bytes, p->encrypted.bytes,
rawSize); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf
crypto->decrypt(p->from, p->id, rawSize, bytes);
diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index d692a3f30..d0e643dff 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -75,7 +75,7 @@ typedef struct _meshtastic_HamParameters {
Ensure your radio is capable of operating of the selected frequency before setting this. */
float frequency;
/* Optional short name of user */
- char short_name[6];
+ char short_name[5];
} meshtastic_HamParameters;
/* Response envelope for node_remote_hardware_pins */
@@ -135,6 +135,8 @@ typedef struct _meshtastic_AdminMessage {
bool enter_dfu_mode_request;
/* Delete the file by the specified path from the device */
char delete_file_request[201];
+ /* Set zero and offset for scale chips */
+ uint32_t set_scale;
/* Set the owner for this node */
meshtastic_User set_owner;
/* Set channels (using the new API).
@@ -238,6 +240,7 @@ extern "C" {
#define meshtastic_AdminMessage_get_node_remote_hardware_pins_response_tag 20
#define meshtastic_AdminMessage_enter_dfu_mode_request_tag 21
#define meshtastic_AdminMessage_delete_file_request_tag 22
+#define meshtastic_AdminMessage_set_scale_tag 23
#define meshtastic_AdminMessage_set_owner_tag 32
#define meshtastic_AdminMessage_set_channel_tag 33
#define meshtastic_AdminMessage_set_config_tag 34
@@ -281,6 +284,7 @@ X(a, STATIC, ONEOF, BOOL, (payload_variant,get_node_remote_hardware_pin
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,get_node_remote_hardware_pins_response,get_node_remote_hardware_pins_response), 20) \
X(a, STATIC, ONEOF, BOOL, (payload_variant,enter_dfu_mode_request,enter_dfu_mode_request), 21) \
X(a, STATIC, ONEOF, STRING, (payload_variant,delete_file_request,delete_file_request), 22) \
+X(a, STATIC, ONEOF, UINT32, (payload_variant,set_scale,set_scale), 23) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_owner,set_owner), 32) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_channel,set_channel), 33) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,set_config,set_config), 34) \
@@ -342,7 +346,7 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size
#define meshtastic_AdminMessage_size 500
-#define meshtastic_HamParameters_size 32
+#define meshtastic_HamParameters_size 31
#define meshtastic_NodeRemoteHardwarePinsResponse_size 496
#ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/apponly.pb.h b/src/mesh/generated/meshtastic/apponly.pb.h
index 54629f522..ba9f90873 100644
--- a/src/mesh/generated/meshtastic/apponly.pb.h
+++ b/src/mesh/generated/meshtastic/apponly.pb.h
@@ -55,7 +55,7 @@ extern const pb_msgdesc_t meshtastic_ChannelSet_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_APPONLY_PB_H_MAX_SIZE meshtastic_ChannelSet_size
-#define meshtastic_ChannelSet_size 658
+#define meshtastic_ChannelSet_size 674
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/atak.pb.h b/src/mesh/generated/meshtastic/atak.pb.h
index c094727ed..5fd18f963 100644
--- a/src/mesh/generated/meshtastic/atak.pb.h
+++ b/src/mesh/generated/meshtastic/atak.pb.h
@@ -73,6 +73,9 @@ typedef struct _meshtastic_GeoChat {
/* Uid recipient of the message */
bool has_to;
char to[120];
+ /* Callsign of the recipient for the message */
+ bool has_to_callsign;
+ char to_callsign[120];
} meshtastic_GeoChat;
/* ATAK Group
@@ -164,13 +167,13 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_TAKPacket_init_default {0, false, meshtastic_Contact_init_default, false, meshtastic_Group_init_default, false, meshtastic_Status_init_default, 0, {meshtastic_PLI_init_default}}
-#define meshtastic_GeoChat_init_default {"", false, ""}
+#define meshtastic_GeoChat_init_default {"", false, "", false, ""}
#define meshtastic_Group_init_default {_meshtastic_MemberRole_MIN, _meshtastic_Team_MIN}
#define meshtastic_Status_init_default {0}
#define meshtastic_Contact_init_default {"", ""}
#define meshtastic_PLI_init_default {0, 0, 0, 0, 0}
#define meshtastic_TAKPacket_init_zero {0, false, meshtastic_Contact_init_zero, false, meshtastic_Group_init_zero, false, meshtastic_Status_init_zero, 0, {meshtastic_PLI_init_zero}}
-#define meshtastic_GeoChat_init_zero {"", false, ""}
+#define meshtastic_GeoChat_init_zero {"", false, "", false, ""}
#define meshtastic_Group_init_zero {_meshtastic_MemberRole_MIN, _meshtastic_Team_MIN}
#define meshtastic_Status_init_zero {0}
#define meshtastic_Contact_init_zero {"", ""}
@@ -179,6 +182,7 @@ extern "C" {
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_GeoChat_message_tag 1
#define meshtastic_GeoChat_to_tag 2
+#define meshtastic_GeoChat_to_callsign_tag 3
#define meshtastic_Group_role_tag 1
#define meshtastic_Group_team_tag 2
#define meshtastic_Status_battery_tag 1
@@ -214,7 +218,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,chat,payload_variant.chat),
#define meshtastic_GeoChat_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, STRING, message, 1) \
-X(a, STATIC, OPTIONAL, STRING, to, 2)
+X(a, STATIC, OPTIONAL, STRING, to, 2) \
+X(a, STATIC, OPTIONAL, STRING, to_callsign, 3)
#define meshtastic_GeoChat_CALLBACK NULL
#define meshtastic_GeoChat_DEFAULT NULL
@@ -262,11 +267,11 @@ extern const pb_msgdesc_t meshtastic_PLI_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_ATAK_PB_H_MAX_SIZE meshtastic_TAKPacket_size
#define meshtastic_Contact_size 242
-#define meshtastic_GeoChat_size 323
+#define meshtastic_GeoChat_size 444
#define meshtastic_Group_size 4
#define meshtastic_PLI_size 31
#define meshtastic_Status_size 3
-#define meshtastic_TAKPacket_size 584
+#define meshtastic_TAKPacket_size 705
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/channel.pb.h b/src/mesh/generated/meshtastic/channel.pb.h
index 185a47a98..d9c7d4ffa 100644
--- a/src/mesh/generated/meshtastic/channel.pb.h
+++ b/src/mesh/generated/meshtastic/channel.pb.h
@@ -34,6 +34,9 @@ typedef enum _meshtastic_Channel_Role {
typedef struct _meshtastic_ModuleSettings {
/* Bits of precision for the location sent in position packets. */
uint32_t position_precision;
+ /* Controls whether or not the phone / clients should mute the current channel
+ Useful for noisy public channels you don't necessarily want to disable */
+ bool is_client_muted;
} meshtastic_ModuleSettings;
typedef PB_BYTES_ARRAY_T(32) meshtastic_ChannelSettings_psk_t;
@@ -126,14 +129,15 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_ChannelSettings_init_default {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_default}
-#define meshtastic_ModuleSettings_init_default {0}
+#define meshtastic_ModuleSettings_init_default {0, 0}
#define meshtastic_Channel_init_default {0, false, meshtastic_ChannelSettings_init_default, _meshtastic_Channel_Role_MIN}
#define meshtastic_ChannelSettings_init_zero {0, {0, {0}}, "", 0, 0, 0, false, meshtastic_ModuleSettings_init_zero}
-#define meshtastic_ModuleSettings_init_zero {0}
+#define meshtastic_ModuleSettings_init_zero {0, 0}
#define meshtastic_Channel_init_zero {0, false, meshtastic_ChannelSettings_init_zero, _meshtastic_Channel_Role_MIN}
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_ModuleSettings_position_precision_tag 1
+#define meshtastic_ModuleSettings_is_client_muted_tag 2
#define meshtastic_ChannelSettings_channel_num_tag 1
#define meshtastic_ChannelSettings_psk_tag 2
#define meshtastic_ChannelSettings_name_tag 3
@@ -159,7 +163,8 @@ X(a, STATIC, OPTIONAL, MESSAGE, module_settings, 7)
#define meshtastic_ChannelSettings_module_settings_MSGTYPE meshtastic_ModuleSettings
#define meshtastic_ModuleSettings_FIELDLIST(X, a) \
-X(a, STATIC, SINGULAR, UINT32, position_precision, 1)
+X(a, STATIC, SINGULAR, UINT32, position_precision, 1) \
+X(a, STATIC, SINGULAR, BOOL, is_client_muted, 2)
#define meshtastic_ModuleSettings_CALLBACK NULL
#define meshtastic_ModuleSettings_DEFAULT NULL
@@ -182,9 +187,9 @@ extern const pb_msgdesc_t meshtastic_Channel_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_CHANNEL_PB_H_MAX_SIZE meshtastic_Channel_size
-#define meshtastic_ChannelSettings_size 70
-#define meshtastic_Channel_size 85
-#define meshtastic_ModuleSettings_size 6
+#define meshtastic_ChannelSettings_size 72
+#define meshtastic_Channel_size 87
+#define meshtastic_ModuleSettings_size 8
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/config.pb.cpp b/src/mesh/generated/meshtastic/config.pb.cpp
index f05e47573..bb82198c0 100644
--- a/src/mesh/generated/meshtastic/config.pb.cpp
+++ b/src/mesh/generated/meshtastic/config.pb.cpp
@@ -46,3 +46,4 @@ PB_BIND(meshtastic_Config_BluetoothConfig, meshtastic_Config_BluetoothConfig, AU
+
diff --git a/src/mesh/generated/meshtastic/config.pb.h b/src/mesh/generated/meshtastic/config.pb.h
index 0830ed851..5a78f1366 100644
--- a/src/mesh/generated/meshtastic/config.pb.h
+++ b/src/mesh/generated/meshtastic/config.pb.h
@@ -182,6 +182,25 @@ typedef enum _meshtastic_Config_DisplayConfig_DisplayMode {
meshtastic_Config_DisplayConfig_DisplayMode_COLOR = 3
} meshtastic_Config_DisplayConfig_DisplayMode;
+typedef enum _meshtastic_Config_DisplayConfig_CompassOrientation {
+ /* The compass and the display are in the same orientation. */
+ meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0 = 0,
+ /* Rotate the compass by 90 degrees. */
+ meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90 = 1,
+ /* Rotate the compass by 180 degrees. */
+ meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180 = 2,
+ /* Rotate the compass by 270 degrees. */
+ meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270 = 3,
+ /* Don't rotate the compass, but invert the result. */
+ meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0_INVERTED = 4,
+ /* Rotate the compass by 90 degrees and invert. */
+ meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90_INVERTED = 5,
+ /* Rotate the compass by 180 degrees and invert. */
+ meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180_INVERTED = 6,
+ /* Rotate the compass by 270 degrees and invert. */
+ meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED = 7
+} meshtastic_Config_DisplayConfig_CompassOrientation;
+
typedef enum _meshtastic_Config_LoRaConfig_RegionCode {
/* Region is not set */
meshtastic_Config_LoRaConfig_RegionCode_UNSET = 0,
@@ -413,6 +432,8 @@ typedef struct _meshtastic_Config_DisplayConfig {
bool heading_bold;
/* Should we wake the screen up on accelerometer detected motion or tap */
bool wake_on_tap_or_motion;
+ /* Indicates how to rotate or invert the compass output to accurate display on the display. */
+ meshtastic_Config_DisplayConfig_CompassOrientation compass_orientation;
} meshtastic_Config_DisplayConfig;
/* Lora Config */
@@ -490,6 +511,8 @@ typedef struct _meshtastic_Config_BluetoothConfig {
meshtastic_Config_BluetoothConfig_PairingMode mode;
/* Specified PIN for PairingMode.FixedPin */
uint32_t fixed_pin;
+ /* Enables device (serial style logs) over Bluetooth */
+ bool device_logging_enabled;
} meshtastic_Config_BluetoothConfig;
typedef struct _meshtastic_Config {
@@ -547,6 +570,10 @@ extern "C" {
#define _meshtastic_Config_DisplayConfig_DisplayMode_MAX meshtastic_Config_DisplayConfig_DisplayMode_COLOR
#define _meshtastic_Config_DisplayConfig_DisplayMode_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DisplayMode)(meshtastic_Config_DisplayConfig_DisplayMode_COLOR+1))
+#define _meshtastic_Config_DisplayConfig_CompassOrientation_MIN meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0
+#define _meshtastic_Config_DisplayConfig_CompassOrientation_MAX meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED
+#define _meshtastic_Config_DisplayConfig_CompassOrientation_ARRAYSIZE ((meshtastic_Config_DisplayConfig_CompassOrientation)(meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED+1))
+
#define _meshtastic_Config_LoRaConfig_RegionCode_MIN meshtastic_Config_LoRaConfig_RegionCode_UNSET
#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_SG_923
#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_SG_923+1))
@@ -573,6 +600,7 @@ extern "C" {
#define meshtastic_Config_DisplayConfig_units_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayUnits
#define meshtastic_Config_DisplayConfig_oled_ENUMTYPE meshtastic_Config_DisplayConfig_OledType
#define meshtastic_Config_DisplayConfig_displaymode_ENUMTYPE meshtastic_Config_DisplayConfig_DisplayMode
+#define meshtastic_Config_DisplayConfig_compass_orientation_ENUMTYPE meshtastic_Config_DisplayConfig_CompassOrientation
#define meshtastic_Config_LoRaConfig_modem_preset_ENUMTYPE meshtastic_Config_LoRaConfig_ModemPreset
#define meshtastic_Config_LoRaConfig_region_ENUMTYPE meshtastic_Config_LoRaConfig_RegionCode
@@ -587,18 +615,18 @@ extern "C" {
#define meshtastic_Config_PowerConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Config_NetworkConfig_init_default {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_default, ""}
#define meshtastic_Config_NetworkConfig_IpV4Config_init_default {0, 0, 0, 0}
-#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0}
+#define meshtastic_Config_DisplayConfig_init_default {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN}
#define meshtastic_Config_LoRaConfig_init_default {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0}
-#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
+#define meshtastic_Config_BluetoothConfig_init_default {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0}
#define meshtastic_Config_init_zero {0, {meshtastic_Config_DeviceConfig_init_zero}}
#define meshtastic_Config_DeviceConfig_init_zero {_meshtastic_Config_DeviceConfig_Role_MIN, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_RebroadcastMode_MIN, 0, 0, 0, 0, "", 0}
#define meshtastic_Config_PositionConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, _meshtastic_Config_PositionConfig_GpsMode_MIN}
#define meshtastic_Config_PowerConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Config_NetworkConfig_init_zero {0, "", "", "", 0, _meshtastic_Config_NetworkConfig_AddressMode_MIN, false, meshtastic_Config_NetworkConfig_IpV4Config_init_zero, ""}
#define meshtastic_Config_NetworkConfig_IpV4Config_init_zero {0, 0, 0, 0}
-#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0}
+#define meshtastic_Config_DisplayConfig_init_zero {0, _meshtastic_Config_DisplayConfig_GpsCoordinateFormat_MIN, 0, 0, 0, _meshtastic_Config_DisplayConfig_DisplayUnits_MIN, _meshtastic_Config_DisplayConfig_OledType_MIN, _meshtastic_Config_DisplayConfig_DisplayMode_MIN, 0, 0, _meshtastic_Config_DisplayConfig_CompassOrientation_MIN}
#define meshtastic_Config_LoRaConfig_init_zero {0, _meshtastic_Config_LoRaConfig_ModemPreset_MIN, 0, 0, 0, 0, _meshtastic_Config_LoRaConfig_RegionCode_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0}
-#define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0}
+#define meshtastic_Config_BluetoothConfig_init_zero {0, _meshtastic_Config_BluetoothConfig_PairingMode_MIN, 0, 0}
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_Config_DeviceConfig_role_tag 1
@@ -656,6 +684,7 @@ extern "C" {
#define meshtastic_Config_DisplayConfig_displaymode_tag 8
#define meshtastic_Config_DisplayConfig_heading_bold_tag 9
#define meshtastic_Config_DisplayConfig_wake_on_tap_or_motion_tag 10
+#define meshtastic_Config_DisplayConfig_compass_orientation_tag 11
#define meshtastic_Config_LoRaConfig_use_preset_tag 1
#define meshtastic_Config_LoRaConfig_modem_preset_tag 2
#define meshtastic_Config_LoRaConfig_bandwidth_tag 3
@@ -675,6 +704,7 @@ extern "C" {
#define meshtastic_Config_BluetoothConfig_enabled_tag 1
#define meshtastic_Config_BluetoothConfig_mode_tag 2
#define meshtastic_Config_BluetoothConfig_fixed_pin_tag 3
+#define meshtastic_Config_BluetoothConfig_device_logging_enabled_tag 4
#define meshtastic_Config_device_tag 1
#define meshtastic_Config_position_tag 2
#define meshtastic_Config_power_tag 3
@@ -778,7 +808,8 @@ X(a, STATIC, SINGULAR, UENUM, units, 6) \
X(a, STATIC, SINGULAR, UENUM, oled, 7) \
X(a, STATIC, SINGULAR, UENUM, displaymode, 8) \
X(a, STATIC, SINGULAR, BOOL, heading_bold, 9) \
-X(a, STATIC, SINGULAR, BOOL, wake_on_tap_or_motion, 10)
+X(a, STATIC, SINGULAR, BOOL, wake_on_tap_or_motion, 10) \
+X(a, STATIC, SINGULAR, UENUM, compass_orientation, 11)
#define meshtastic_Config_DisplayConfig_CALLBACK NULL
#define meshtastic_Config_DisplayConfig_DEFAULT NULL
@@ -805,7 +836,8 @@ X(a, STATIC, SINGULAR, BOOL, ignore_mqtt, 104)
#define meshtastic_Config_BluetoothConfig_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
X(a, STATIC, SINGULAR, UENUM, mode, 2) \
-X(a, STATIC, SINGULAR, UINT32, fixed_pin, 3)
+X(a, STATIC, SINGULAR, UINT32, fixed_pin, 3) \
+X(a, STATIC, SINGULAR, BOOL, device_logging_enabled, 4)
#define meshtastic_Config_BluetoothConfig_CALLBACK NULL
#define meshtastic_Config_BluetoothConfig_DEFAULT NULL
@@ -832,9 +864,9 @@ extern const pb_msgdesc_t meshtastic_Config_BluetoothConfig_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_CONFIG_PB_H_MAX_SIZE meshtastic_Config_size
-#define meshtastic_Config_BluetoothConfig_size 10
+#define meshtastic_Config_BluetoothConfig_size 12
#define meshtastic_Config_DeviceConfig_size 100
-#define meshtastic_Config_DisplayConfig_size 28
+#define meshtastic_Config_DisplayConfig_size 30
#define meshtastic_Config_LoRaConfig_size 80
#define meshtastic_Config_NetworkConfig_IpV4Config_size 20
#define meshtastic_Config_NetworkConfig_size 196
diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h
index 2506ec647..5e291ee94 100644
--- a/src/mesh/generated/meshtastic/deviceonly.pb.h
+++ b/src/mesh/generated/meshtastic/deviceonly.pb.h
@@ -306,9 +306,9 @@ extern const pb_msgdesc_t meshtastic_OEMStore_msg;
/* Maximum encoded size of messages (where known) */
/* meshtastic_DeviceState_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_OEMStore_size
-#define meshtastic_ChannelFile_size 702
+#define meshtastic_ChannelFile_size 718
#define meshtastic_NodeInfoLite_size 166
-#define meshtastic_OEMStore_size 3346
+#define meshtastic_OEMStore_size 3372
#define meshtastic_PositionLite_size 28
#ifdef __cplusplus
diff --git a/src/mesh/generated/meshtastic/localonly.pb.h b/src/mesh/generated/meshtastic/localonly.pb.h
index 1799f49da..96a9976f0 100644
--- a/src/mesh/generated/meshtastic/localonly.pb.h
+++ b/src/mesh/generated/meshtastic/localonly.pb.h
@@ -181,8 +181,8 @@ extern const pb_msgdesc_t meshtastic_LocalModuleConfig_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_LOCALONLY_PB_H_MAX_SIZE meshtastic_LocalModuleConfig_size
-#define meshtastic_LocalConfig_size 537
-#define meshtastic_LocalModuleConfig_size 663
+#define meshtastic_LocalConfig_size 541
+#define meshtastic_LocalModuleConfig_size 685
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp
index 4907affc6..46d59d609 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.cpp
+++ b/src/mesh/generated/meshtastic/mesh.pb.cpp
@@ -66,6 +66,15 @@ PB_BIND(meshtastic_Heartbeat, meshtastic_Heartbeat, AUTO)
PB_BIND(meshtastic_NodeRemoteHardwarePin, meshtastic_NodeRemoteHardwarePin, AUTO)
+PB_BIND(meshtastic_ChunkedPayload, meshtastic_ChunkedPayload, AUTO)
+
+
+PB_BIND(meshtastic_resend_chunks, meshtastic_resend_chunks, AUTO)
+
+
+PB_BIND(meshtastic_ChunkedPayloadResponse, meshtastic_ChunkedPayloadResponse, AUTO)
+
+
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index 67b2edd15..064115815 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -63,6 +63,14 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_NANO_G2_ULTRA = 18,
/* LoRAType device: https://loratype.org/ */
meshtastic_HardwareModel_LORA_TYPE = 19,
+ /* wiphone https://www.wiphone.io/ */
+ meshtastic_HardwareModel_WIPHONE = 20,
+ /* WIO Tracker WM1110 family from Seeed Studio. Includes wio-1110-tracker and wio-1110-sdk */
+ meshtastic_HardwareModel_WIO_WM1110 = 21,
+ /* RAK2560 Solar base station based on RAK4630 */
+ meshtastic_HardwareModel_RAK2560 = 22,
+ /* Heltec HRU-3601: https://heltec.org/project/hru-3601/ */
+ meshtastic_HardwareModel_HELTEC_HRU_3601 = 23,
/* B&Q Consulting Station Edition G1: https://uniteng.com/wiki/doku.php?id=meshtastic:station */
meshtastic_HardwareModel_STATION_G1 = 25,
/* RAK11310 (RP2040 + SX1262) */
@@ -148,6 +156,17 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_TD_LORAC = 60,
/* CDEBYTE EoRa-S3 board using their own MM modules, clone of LILYGO T3S3 */
meshtastic_HardwareModel_CDEBYTE_EORA_S3 = 61,
+ /* TWC_MESH_V4
+ Adafruit NRF52840 feather express with SX1262, SSD1306 OLED and NEO6M GPS */
+ meshtastic_HardwareModel_TWC_MESH_V4 = 62,
+ /* NRF52_PROMICRO_DIY
+ Promicro NRF52840 with SX1262/LLCC68, SSD1306 OLED and NEO6M GPS */
+ meshtastic_HardwareModel_NRF52_PROMICRO_DIY = 63,
+ /* RadioMaster 900 Bandit Nano, https://www.radiomasterrc.com/products/bandit-nano-expresslrs-rf-module
+ ESP32-D0WDQ6 With SX1276/SKY66122, SSD1306 OLED and No GPS */
+ meshtastic_HardwareModel_RADIOMASTER_900_BANDIT_NANO = 64,
+ /* Heltec Capsule Sensor V3 with ESP32-S3 CPU, Portable LoRa device that can replace GNSS modules or sensors */
+ meshtastic_HardwareModel_HELTEC_CAPSULE_SENSOR_V3 = 65,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */
@@ -842,6 +861,38 @@ typedef struct _meshtastic_NodeRemoteHardwarePin {
meshtastic_RemoteHardwarePin pin;
} meshtastic_NodeRemoteHardwarePin;
+typedef PB_BYTES_ARRAY_T(228) meshtastic_ChunkedPayload_payload_chunk_t;
+typedef struct _meshtastic_ChunkedPayload {
+ /* The ID of the entire payload */
+ uint32_t payload_id;
+ /* The total number of chunks in the payload */
+ uint16_t chunk_count;
+ /* The current chunk index in the total */
+ uint16_t chunk_index;
+ /* The binary data of the current chunk */
+ meshtastic_ChunkedPayload_payload_chunk_t payload_chunk;
+} meshtastic_ChunkedPayload;
+
+/* Wrapper message for broken repeated oneof support */
+typedef struct _meshtastic_resend_chunks {
+ pb_callback_t chunks;
+} meshtastic_resend_chunks;
+
+/* Responses to a ChunkedPayload request */
+typedef struct _meshtastic_ChunkedPayloadResponse {
+ /* The ID of the entire payload */
+ uint32_t payload_id;
+ pb_size_t which_payload_variant;
+ union {
+ /* Request to transfer chunked payload */
+ bool request_transfer;
+ /* Accept the transfer chunked payload */
+ bool accept_transfer;
+ /* Request missing indexes in the chunked payload */
+ meshtastic_resend_chunks resend_chunks;
+ } payload_variant;
+} meshtastic_ChunkedPayloadResponse;
+
#ifdef __cplusplus
extern "C" {
@@ -917,6 +968,9 @@ extern "C" {
+
+
+
/* Initializer values for message structs */
#define meshtastic_Position_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_User_init_default {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN}
@@ -938,6 +992,9 @@ extern "C" {
#define meshtastic_DeviceMetadata_init_default {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0}
#define meshtastic_Heartbeat_init_default {0}
#define meshtastic_NodeRemoteHardwarePin_init_default {0, false, meshtastic_RemoteHardwarePin_init_default}
+#define meshtastic_ChunkedPayload_init_default {0, 0, 0, {0, {0}}}
+#define meshtastic_resend_chunks_init_default {{{NULL}, NULL}}
+#define meshtastic_ChunkedPayloadResponse_init_default {0, 0, {0}}
#define meshtastic_Position_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN, _meshtastic_Position_AltSource_MIN, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_User_init_zero {"", "", "", {0}, _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN}
#define meshtastic_RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}}
@@ -958,6 +1015,9 @@ extern "C" {
#define meshtastic_DeviceMetadata_init_zero {"", 0, 0, 0, 0, 0, _meshtastic_Config_DeviceConfig_Role_MIN, 0, _meshtastic_HardwareModel_MIN, 0}
#define meshtastic_Heartbeat_init_zero {0}
#define meshtastic_NodeRemoteHardwarePin_init_zero {0, false, meshtastic_RemoteHardwarePin_init_zero}
+#define meshtastic_ChunkedPayload_init_zero {0, 0, 0, {0, {0}}}
+#define meshtastic_resend_chunks_init_zero {{{NULL}, NULL}}
+#define meshtastic_ChunkedPayloadResponse_init_zero {0, 0, {0}}
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_Position_latitude_i_tag 1
@@ -1092,6 +1152,15 @@ extern "C" {
#define meshtastic_ToRadio_heartbeat_tag 7
#define meshtastic_NodeRemoteHardwarePin_node_num_tag 1
#define meshtastic_NodeRemoteHardwarePin_pin_tag 2
+#define meshtastic_ChunkedPayload_payload_id_tag 1
+#define meshtastic_ChunkedPayload_chunk_count_tag 2
+#define meshtastic_ChunkedPayload_chunk_index_tag 3
+#define meshtastic_ChunkedPayload_payload_chunk_tag 4
+#define meshtastic_resend_chunks_chunks_tag 1
+#define meshtastic_ChunkedPayloadResponse_payload_id_tag 1
+#define meshtastic_ChunkedPayloadResponse_request_transfer_tag 2
+#define meshtastic_ChunkedPayloadResponse_accept_transfer_tag 3
+#define meshtastic_ChunkedPayloadResponse_resend_chunks_tag 4
/* Struct field encoding specification for nanopb */
#define meshtastic_Position_FIELDLIST(X, a) \
@@ -1330,6 +1399,28 @@ X(a, STATIC, OPTIONAL, MESSAGE, pin, 2)
#define meshtastic_NodeRemoteHardwarePin_DEFAULT NULL
#define meshtastic_NodeRemoteHardwarePin_pin_MSGTYPE meshtastic_RemoteHardwarePin
+#define meshtastic_ChunkedPayload_FIELDLIST(X, a) \
+X(a, STATIC, SINGULAR, UINT32, payload_id, 1) \
+X(a, STATIC, SINGULAR, UINT32, chunk_count, 2) \
+X(a, STATIC, SINGULAR, UINT32, chunk_index, 3) \
+X(a, STATIC, SINGULAR, BYTES, payload_chunk, 4)
+#define meshtastic_ChunkedPayload_CALLBACK NULL
+#define meshtastic_ChunkedPayload_DEFAULT NULL
+
+#define meshtastic_resend_chunks_FIELDLIST(X, a) \
+X(a, CALLBACK, REPEATED, UINT32, chunks, 1)
+#define meshtastic_resend_chunks_CALLBACK pb_default_field_callback
+#define meshtastic_resend_chunks_DEFAULT NULL
+
+#define meshtastic_ChunkedPayloadResponse_FIELDLIST(X, a) \
+X(a, STATIC, SINGULAR, UINT32, payload_id, 1) \
+X(a, STATIC, ONEOF, BOOL, (payload_variant,request_transfer,payload_variant.request_transfer), 2) \
+X(a, STATIC, ONEOF, BOOL, (payload_variant,accept_transfer,payload_variant.accept_transfer), 3) \
+X(a, STATIC, ONEOF, MESSAGE, (payload_variant,resend_chunks,payload_variant.resend_chunks), 4)
+#define meshtastic_ChunkedPayloadResponse_CALLBACK NULL
+#define meshtastic_ChunkedPayloadResponse_DEFAULT NULL
+#define meshtastic_ChunkedPayloadResponse_payload_variant_resend_chunks_MSGTYPE meshtastic_resend_chunks
+
extern const pb_msgdesc_t meshtastic_Position_msg;
extern const pb_msgdesc_t meshtastic_User_msg;
extern const pb_msgdesc_t meshtastic_RouteDiscovery_msg;
@@ -1350,6 +1441,9 @@ extern const pb_msgdesc_t meshtastic_Neighbor_msg;
extern const pb_msgdesc_t meshtastic_DeviceMetadata_msg;
extern const pb_msgdesc_t meshtastic_Heartbeat_msg;
extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg;
+extern const pb_msgdesc_t meshtastic_ChunkedPayload_msg;
+extern const pb_msgdesc_t meshtastic_resend_chunks_msg;
+extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define meshtastic_Position_fields &meshtastic_Position_msg
@@ -1372,9 +1466,15 @@ extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePin_msg;
#define meshtastic_DeviceMetadata_fields &meshtastic_DeviceMetadata_msg
#define meshtastic_Heartbeat_fields &meshtastic_Heartbeat_msg
#define meshtastic_NodeRemoteHardwarePin_fields &meshtastic_NodeRemoteHardwarePin_msg
+#define meshtastic_ChunkedPayload_fields &meshtastic_ChunkedPayload_msg
+#define meshtastic_resend_chunks_fields &meshtastic_resend_chunks_msg
+#define meshtastic_ChunkedPayloadResponse_fields &meshtastic_ChunkedPayloadResponse_msg
/* Maximum encoded size of messages (where known) */
+/* meshtastic_resend_chunks_size depends on runtime parameters */
+/* meshtastic_ChunkedPayloadResponse_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_MESH_PB_H_MAX_SIZE meshtastic_FromRadio_size
+#define meshtastic_ChunkedPayload_size 245
#define meshtastic_Compressed_size 243
#define meshtastic_Data_size 270
#define meshtastic_DeviceMetadata_size 46
diff --git a/src/mesh/generated/meshtastic/module_config.pb.h b/src/mesh/generated/meshtastic/module_config.pb.h
index ffda48704..f3c48ee6d 100644
--- a/src/mesh/generated/meshtastic/module_config.pb.h
+++ b/src/mesh/generated/meshtastic/module_config.pb.h
@@ -188,6 +188,10 @@ typedef struct _meshtastic_ModuleConfig_PaxcounterConfig {
/* Enable the Paxcounter Module */
bool enabled;
uint32_t paxcounter_update_interval;
+ /* WiFi RSSI threshold. Defaults to -80 */
+ int32_t wifi_threshold;
+ /* BLE RSSI threshold. Defaults to -80 */
+ int32_t ble_threshold;
} meshtastic_ModuleConfig_PaxcounterConfig;
/* Serial Config */
@@ -467,7 +471,7 @@ extern "C" {
#define meshtastic_ModuleConfig_NeighborInfoConfig_init_default {0, 0}
#define meshtastic_ModuleConfig_DetectionSensorConfig_init_default {0, 0, 0, 0, "", 0, 0, 0}
#define meshtastic_ModuleConfig_AudioConfig_init_default {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0}
-#define meshtastic_ModuleConfig_PaxcounterConfig_init_default {0, 0}
+#define meshtastic_ModuleConfig_PaxcounterConfig_init_default {0, 0, 0, 0}
#define meshtastic_ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0}
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0}
@@ -483,7 +487,7 @@ extern "C" {
#define meshtastic_ModuleConfig_NeighborInfoConfig_init_zero {0, 0}
#define meshtastic_ModuleConfig_DetectionSensorConfig_init_zero {0, 0, 0, 0, "", 0, 0, 0}
#define meshtastic_ModuleConfig_AudioConfig_init_zero {0, 0, _meshtastic_ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0}
-#define meshtastic_ModuleConfig_PaxcounterConfig_init_zero {0, 0}
+#define meshtastic_ModuleConfig_PaxcounterConfig_init_zero {0, 0, 0, 0}
#define meshtastic_ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _meshtastic_ModuleConfig_SerialConfig_Serial_Mode_MIN, 0}
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0}
@@ -526,6 +530,8 @@ extern "C" {
#define meshtastic_ModuleConfig_AudioConfig_i2s_sck_tag 7
#define meshtastic_ModuleConfig_PaxcounterConfig_enabled_tag 1
#define meshtastic_ModuleConfig_PaxcounterConfig_paxcounter_update_interval_tag 2
+#define meshtastic_ModuleConfig_PaxcounterConfig_wifi_threshold_tag 3
+#define meshtastic_ModuleConfig_PaxcounterConfig_ble_threshold_tag 4
#define meshtastic_ModuleConfig_SerialConfig_enabled_tag 1
#define meshtastic_ModuleConfig_SerialConfig_echo_tag 2
#define meshtastic_ModuleConfig_SerialConfig_rxd_tag 3
@@ -695,7 +701,9 @@ X(a, STATIC, SINGULAR, UINT32, i2s_sck, 7)
#define meshtastic_ModuleConfig_PaxcounterConfig_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, enabled, 1) \
-X(a, STATIC, SINGULAR, UINT32, paxcounter_update_interval, 2)
+X(a, STATIC, SINGULAR, UINT32, paxcounter_update_interval, 2) \
+X(a, STATIC, SINGULAR, INT32, wifi_threshold, 3) \
+X(a, STATIC, SINGULAR, INT32, ble_threshold, 4)
#define meshtastic_ModuleConfig_PaxcounterConfig_CALLBACK NULL
#define meshtastic_ModuleConfig_PaxcounterConfig_DEFAULT NULL
@@ -836,7 +844,7 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
#define meshtastic_ModuleConfig_MQTTConfig_size 254
#define meshtastic_ModuleConfig_MapReportSettings_size 12
#define meshtastic_ModuleConfig_NeighborInfoConfig_size 8
-#define meshtastic_ModuleConfig_PaxcounterConfig_size 8
+#define meshtastic_ModuleConfig_PaxcounterConfig_size 30
#define meshtastic_ModuleConfig_RangeTestConfig_size 10
#define meshtastic_ModuleConfig_RemoteHardwareConfig_size 96
#define meshtastic_ModuleConfig_SerialConfig_size 28
diff --git a/src/mesh/generated/meshtastic/telemetry.pb.cpp b/src/mesh/generated/meshtastic/telemetry.pb.cpp
index 6388e37a0..c93483a15 100644
--- a/src/mesh/generated/meshtastic/telemetry.pb.cpp
+++ b/src/mesh/generated/meshtastic/telemetry.pb.cpp
@@ -21,5 +21,8 @@ PB_BIND(meshtastic_AirQualityMetrics, meshtastic_AirQualityMetrics, AUTO)
PB_BIND(meshtastic_Telemetry, meshtastic_Telemetry, AUTO)
+PB_BIND(meshtastic_Nau7802Config, meshtastic_Nau7802Config, AUTO)
+
+
diff --git a/src/mesh/generated/meshtastic/telemetry.pb.h b/src/mesh/generated/meshtastic/telemetry.pb.h
index e670dd340..28d368754 100644
--- a/src/mesh/generated/meshtastic/telemetry.pb.h
+++ b/src/mesh/generated/meshtastic/telemetry.pb.h
@@ -45,7 +45,25 @@ typedef enum _meshtastic_TelemetrySensorType {
/* BMP085/BMP180 High accuracy temperature and pressure (older Version of BMP280) */
meshtastic_TelemetrySensorType_BMP085 = 15,
/* RCWL-9620 Doppler Radar Distance Sensor, used for water level detection */
- meshtastic_TelemetrySensorType_RCWL9620 = 16
+ meshtastic_TelemetrySensorType_RCWL9620 = 16,
+ /* Sensirion High accuracy temperature and humidity */
+ meshtastic_TelemetrySensorType_SHT4X = 17,
+ /* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. */
+ meshtastic_TelemetrySensorType_VEML7700 = 18,
+ /* MLX90632 non-contact IR temperature sensor. */
+ meshtastic_TelemetrySensorType_MLX90632 = 19,
+ /* TI OPT3001 Ambient Light Sensor */
+ meshtastic_TelemetrySensorType_OPT3001 = 20,
+ /* Lite On LTR-390UV-01 UV Light Sensor */
+ meshtastic_TelemetrySensorType_LTR390UV = 21,
+ /* AMS TSL25911FN RGB Light Sensor */
+ meshtastic_TelemetrySensorType_TSL25911FN = 22,
+ /* AHT10 Integrated temperature and humidity sensor */
+ meshtastic_TelemetrySensorType_AHT10 = 23,
+ /* DFRobot Lark Weather station (temperature, humidity, pressure, wind speed and direction) */
+ meshtastic_TelemetrySensorType_DFROBOT_LARK = 24,
+ /* NAU7802 Scale Chip or compatible */
+ meshtastic_TelemetrySensorType_NAU7802 = 25
} meshtastic_TelemetrySensorType;
/* Struct definitions */
@@ -82,6 +100,21 @@ typedef struct _meshtastic_EnvironmentMetrics {
uint16_t iaq;
/* RCWL9620 Doppler Radar Distance Sensor, used for water level detection. Float value in mm. */
float distance;
+ /* VEML7700 high accuracy ambient light(Lux) digital 16-bit resolution sensor. */
+ float lux;
+ /* VEML7700 high accuracy white light(irradiance) not calibrated digital 16-bit resolution sensor. */
+ float white_lux;
+ /* Infrared lux */
+ float ir_lux;
+ /* Ultraviolet lux */
+ float uv_lux;
+ /* Wind direction in degrees
+ 0 degrees = North, 90 = East, etc... */
+ uint16_t wind_direction;
+ /* Wind speed in m/s */
+ float wind_speed;
+ /* Weight in KG */
+ float weight;
} meshtastic_EnvironmentMetrics;
/* Power Metrics (voltage / current / etc) */
@@ -145,6 +178,14 @@ typedef struct _meshtastic_Telemetry {
} variant;
} meshtastic_Telemetry;
+/* NAU7802 Telemetry configuration, for saving to flash */
+typedef struct _meshtastic_Nau7802Config {
+ /* The offset setting for the NAU7802 */
+ int32_t zeroOffset;
+ /* The calibration factor for the NAU7802 */
+ float calibrationFactor;
+} meshtastic_Nau7802Config;
+
#ifdef __cplusplus
extern "C" {
@@ -152,8 +193,9 @@ extern "C" {
/* Helper constants for enums */
#define _meshtastic_TelemetrySensorType_MIN meshtastic_TelemetrySensorType_SENSOR_UNSET
-#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_RCWL9620
-#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_RCWL9620+1))
+#define _meshtastic_TelemetrySensorType_MAX meshtastic_TelemetrySensorType_NAU7802
+#define _meshtastic_TelemetrySensorType_ARRAYSIZE ((meshtastic_TelemetrySensorType)(meshtastic_TelemetrySensorType_NAU7802+1))
+
@@ -163,15 +205,17 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_DeviceMetrics_init_default {0, 0, 0, 0, 0}
-#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0}
+#define meshtastic_EnvironmentMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_PowerMetrics_init_default {0, 0, 0, 0, 0, 0}
#define meshtastic_AirQualityMetrics_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Telemetry_init_default {0, 0, {meshtastic_DeviceMetrics_init_default}}
+#define meshtastic_Nau7802Config_init_default {0, 0}
#define meshtastic_DeviceMetrics_init_zero {0, 0, 0, 0, 0}
-#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0}
+#define meshtastic_EnvironmentMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_PowerMetrics_init_zero {0, 0, 0, 0, 0, 0}
#define meshtastic_AirQualityMetrics_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define meshtastic_Telemetry_init_zero {0, 0, {meshtastic_DeviceMetrics_init_zero}}
+#define meshtastic_Nau7802Config_init_zero {0, 0}
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_DeviceMetrics_battery_level_tag 1
@@ -187,6 +231,13 @@ extern "C" {
#define meshtastic_EnvironmentMetrics_current_tag 6
#define meshtastic_EnvironmentMetrics_iaq_tag 7
#define meshtastic_EnvironmentMetrics_distance_tag 8
+#define meshtastic_EnvironmentMetrics_lux_tag 9
+#define meshtastic_EnvironmentMetrics_white_lux_tag 10
+#define meshtastic_EnvironmentMetrics_ir_lux_tag 11
+#define meshtastic_EnvironmentMetrics_uv_lux_tag 12
+#define meshtastic_EnvironmentMetrics_wind_direction_tag 13
+#define meshtastic_EnvironmentMetrics_wind_speed_tag 14
+#define meshtastic_EnvironmentMetrics_weight_tag 15
#define meshtastic_PowerMetrics_ch1_voltage_tag 1
#define meshtastic_PowerMetrics_ch1_current_tag 2
#define meshtastic_PowerMetrics_ch2_voltage_tag 3
@@ -210,6 +261,8 @@ extern "C" {
#define meshtastic_Telemetry_environment_metrics_tag 3
#define meshtastic_Telemetry_air_quality_metrics_tag 4
#define meshtastic_Telemetry_power_metrics_tag 5
+#define meshtastic_Nau7802Config_zeroOffset_tag 1
+#define meshtastic_Nau7802Config_calibrationFactor_tag 2
/* Struct field encoding specification for nanopb */
#define meshtastic_DeviceMetrics_FIELDLIST(X, a) \
@@ -229,7 +282,14 @@ X(a, STATIC, SINGULAR, FLOAT, gas_resistance, 4) \
X(a, STATIC, SINGULAR, FLOAT, voltage, 5) \
X(a, STATIC, SINGULAR, FLOAT, current, 6) \
X(a, STATIC, SINGULAR, UINT32, iaq, 7) \
-X(a, STATIC, SINGULAR, FLOAT, distance, 8)
+X(a, STATIC, SINGULAR, FLOAT, distance, 8) \
+X(a, STATIC, SINGULAR, FLOAT, lux, 9) \
+X(a, STATIC, SINGULAR, FLOAT, white_lux, 10) \
+X(a, STATIC, SINGULAR, FLOAT, ir_lux, 11) \
+X(a, STATIC, SINGULAR, FLOAT, uv_lux, 12) \
+X(a, STATIC, SINGULAR, UINT32, wind_direction, 13) \
+X(a, STATIC, SINGULAR, FLOAT, wind_speed, 14) \
+X(a, STATIC, SINGULAR, FLOAT, weight, 15)
#define meshtastic_EnvironmentMetrics_CALLBACK NULL
#define meshtastic_EnvironmentMetrics_DEFAULT NULL
@@ -272,11 +332,18 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,power_metrics,variant.power_metrics)
#define meshtastic_Telemetry_variant_air_quality_metrics_MSGTYPE meshtastic_AirQualityMetrics
#define meshtastic_Telemetry_variant_power_metrics_MSGTYPE meshtastic_PowerMetrics
+#define meshtastic_Nau7802Config_FIELDLIST(X, a) \
+X(a, STATIC, SINGULAR, INT32, zeroOffset, 1) \
+X(a, STATIC, SINGULAR, FLOAT, calibrationFactor, 2)
+#define meshtastic_Nau7802Config_CALLBACK NULL
+#define meshtastic_Nau7802Config_DEFAULT NULL
+
extern const pb_msgdesc_t meshtastic_DeviceMetrics_msg;
extern const pb_msgdesc_t meshtastic_EnvironmentMetrics_msg;
extern const pb_msgdesc_t meshtastic_PowerMetrics_msg;
extern const pb_msgdesc_t meshtastic_AirQualityMetrics_msg;
extern const pb_msgdesc_t meshtastic_Telemetry_msg;
+extern const pb_msgdesc_t meshtastic_Nau7802Config_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define meshtastic_DeviceMetrics_fields &meshtastic_DeviceMetrics_msg
@@ -284,14 +351,16 @@ extern const pb_msgdesc_t meshtastic_Telemetry_msg;
#define meshtastic_PowerMetrics_fields &meshtastic_PowerMetrics_msg
#define meshtastic_AirQualityMetrics_fields &meshtastic_AirQualityMetrics_msg
#define meshtastic_Telemetry_fields &meshtastic_Telemetry_msg
+#define meshtastic_Nau7802Config_fields &meshtastic_Nau7802Config_msg
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_TELEMETRY_PB_H_MAX_SIZE meshtastic_Telemetry_size
#define meshtastic_AirQualityMetrics_size 72
#define meshtastic_DeviceMetrics_size 27
-#define meshtastic_EnvironmentMetrics_size 39
+#define meshtastic_EnvironmentMetrics_size 73
+#define meshtastic_Nau7802Config_size 16
#define meshtastic_PowerMetrics_size 30
-#define meshtastic_Telemetry_size 79
+#define meshtastic_Telemetry_size 80
#ifdef __cplusplus
} /* extern "C" */
diff --git a/src/mesh/wifi/WiFiAPClient.cpp b/src/mesh/wifi/WiFiAPClient.cpp
index 1de4d7669..ffb16bd3e 100644
--- a/src/mesh/wifi/WiFiAPClient.cpp
+++ b/src/mesh/wifi/WiFiAPClient.cpp
@@ -108,8 +108,10 @@ static void onNetworkConnected()
}
// FIXME this is kinda yucky, instead we should just have an observable for 'wifireconnected'
+#ifndef MESHTASTIC_EXCLUDE_MQTT
if (mqtt)
mqtt->reconnect();
+#endif
}
static int32_t reconnectWiFi()
@@ -225,10 +227,16 @@ bool initWifi()
WiFi.mode(WIFI_STA);
WiFi.setHostname(ourHost);
+
if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_STATIC &&
config.network.ipv4_config.ip != 0) {
+#ifndef ARCH_RP2040
WiFi.config(config.network.ipv4_config.ip, config.network.ipv4_config.gateway, config.network.ipv4_config.subnet,
config.network.ipv4_config.dns);
+#else
+ WiFi.config(config.network.ipv4_config.ip, config.network.ipv4_config.dns, config.network.ipv4_config.gateway,
+ config.network.ipv4_config.subnet);
+#endif
}
#ifndef ARCH_RP2040
WiFi.onEvent(WiFiEvent);
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index 54eb577f7..091586462 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -23,6 +23,18 @@
#include "mqtt/MQTT.h"
#endif
+#if !MESHTASTIC_EXCLUDE_GPS
+#include "GPS.h"
+#endif
+
+#if MESHTASTIC_EXCLUDE_GPS
+#include "modules/PositionModule.h"
+#endif
+
+#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+#include "AccelerometerThread.h"
+#endif
+
AdminModule *adminModule;
bool hasOpenEditTransaction;
@@ -217,6 +229,12 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
nodeDB->setLocalPosition(r->set_fixed_position);
config.position.fixed_position = true;
saveChanges(SEGMENT_DEVICESTATE | SEGMENT_CONFIG, false);
+#if !MESHTASTIC_EXCLUDE_GPS
+ if (gps != nullptr)
+ gps->enable();
+#endif
+ // Send our new fixed position to the mesh for good measure
+ positionModule->sendOurPosition();
}
break;
}
@@ -342,6 +360,26 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
case meshtastic_Config_device_tag:
LOG_INFO("Setting config: Device\n");
config.has_device = true;
+#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+ if (config.device.double_tap_as_button_press == false && c.payload_variant.device.double_tap_as_button_press == true) {
+ accelerometerThread->start();
+ }
+#endif
+#ifdef LED_PIN
+ // Turn LED off if heartbeat by config
+ if (c.payload_variant.device.led_heartbeat_disabled) {
+ digitalWrite(LED_PIN, LOW ^ LED_INVERTED);
+ }
+#endif
+ if (config.device.button_gpio == c.payload_variant.device.button_gpio &&
+ config.device.buzzer_gpio == c.payload_variant.device.buzzer_gpio &&
+ config.device.debug_log_enabled == c.payload_variant.device.debug_log_enabled &&
+ config.device.serial_enabled == c.payload_variant.device.serial_enabled &&
+ config.device.role == c.payload_variant.device.role &&
+ config.device.disable_triple_click == c.payload_variant.device.disable_triple_click &&
+ config.device.rebroadcast_mode == c.payload_variant.device.rebroadcast_mode) {
+ requiresReboot = false;
+ }
config.device = c.payload_variant.device;
// If we're setting router role for the first time, install its intervals
if (existingRole != c.payload_variant.device.role)
@@ -361,6 +399,16 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
case meshtastic_Config_power_tag:
LOG_INFO("Setting config: Power\n");
config.has_power = true;
+ // Really just the adc override is the only thing that can change without a reboot
+ if (config.power.device_battery_ina_address == c.payload_variant.power.device_battery_ina_address &&
+ config.power.is_power_saving == c.payload_variant.power.is_power_saving &&
+ config.power.ls_secs == c.payload_variant.power.ls_secs &&
+ config.power.min_wake_secs == c.payload_variant.power.min_wake_secs &&
+ config.power.on_battery_shutdown_after_secs == c.payload_variant.power.on_battery_shutdown_after_secs &&
+ config.power.sds_secs == c.payload_variant.power.sds_secs &&
+ config.power.wait_bluetooth_secs == c.payload_variant.power.wait_bluetooth_secs) {
+ requiresReboot = false;
+ }
config.power = c.payload_variant.power;
break;
case meshtastic_Config_network_tag:
@@ -371,6 +419,16 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c)
case meshtastic_Config_display_tag:
LOG_INFO("Setting config: Display\n");
config.has_display = true;
+ if (config.display.screen_on_secs == c.payload_variant.display.screen_on_secs &&
+ config.display.flip_screen == c.payload_variant.display.flip_screen &&
+ config.display.oled == c.payload_variant.display.oled) {
+ requiresReboot = false;
+ }
+#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+ if (config.display.wake_on_tap_or_motion == false && c.payload_variant.display.wake_on_tap_or_motion == true) {
+ accelerometerThread->start();
+ }
+#endif
config.display = c.payload_variant.display;
break;
case meshtastic_Config_lora_tag:
@@ -698,7 +756,9 @@ void AdminModule::handleGetDeviceConnectionStatus(const meshtastic_MeshPacket &r
if (conn.wifi.status.is_connected) {
conn.wifi.rssi = WiFi.RSSI();
conn.wifi.status.ip_address = WiFi.localIP();
+#ifndef MESHTASTIC_EXCLUDE_MQTT
conn.wifi.status.is_mqtt_connected = mqtt && mqtt->isConnectedDirectly();
+#endif
conn.wifi.status.is_syslog_connected = false; // FIXME wire this up
}
#endif
diff --git a/src/modules/AtakPluginModule.cpp b/src/modules/AtakPluginModule.cpp
index 64a85e2bf..59263415c 100644
--- a/src/modules/AtakPluginModule.cpp
+++ b/src/modules/AtakPluginModule.cpp
@@ -64,40 +64,41 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast
{
// From Phone (EUD)
if (mp.from == 0) {
- LOG_DEBUG("Received uncompressed TAK payload from phone with %d bytes\n", mp.decoded.payload.size);
+ LOG_DEBUG("Received uncompressed TAK payload from phone: %d bytes\n", mp.decoded.payload.size);
// Compress for LoRA transport
auto compressed = cloneTAKPacketData(t);
compressed.is_compressed = true;
if (t->has_contact) {
auto length = unishox2_compress_simple(t->contact.callsign, strlen(t->contact.callsign), compressed.contact.callsign);
- LOG_DEBUG("Uncompressed callsign '%s' - %d bytes\n", t->contact.callsign, strlen(t->contact.callsign));
- LOG_DEBUG("Compressed callsign '%s' - %d bytes\n", t->contact.callsign, length);
+ LOG_DEBUG("Compressed callsign: %d bytes\n", length);
length = unishox2_compress_simple(t->contact.device_callsign, strlen(t->contact.device_callsign),
compressed.contact.device_callsign);
- LOG_DEBUG("Uncompressed device_callsign '%s' - %d bytes\n", t->contact.device_callsign,
- strlen(t->contact.device_callsign));
- LOG_DEBUG("Compressed device_callsign '%s' - %d bytes\n", compressed.contact.device_callsign, length);
+ LOG_DEBUG("Compressed device_callsign: %d bytes\n", length);
}
if (t->which_payload_variant == meshtastic_TAKPacket_chat_tag) {
auto length = unishox2_compress_simple(t->payload_variant.chat.message, strlen(t->payload_variant.chat.message),
compressed.payload_variant.chat.message);
- LOG_DEBUG("Uncompressed chat message '%s' - %d bytes\n", t->payload_variant.chat.message,
- strlen(t->payload_variant.chat.message));
- LOG_DEBUG("Compressed chat message '%s' - %d bytes\n", compressed.payload_variant.chat.message, length);
+ LOG_DEBUG("Compressed chat message: %d bytes\n", length);
if (t->payload_variant.chat.has_to) {
compressed.payload_variant.chat.has_to = true;
length = unishox2_compress_simple(t->payload_variant.chat.to, strlen(t->payload_variant.chat.to),
compressed.payload_variant.chat.to);
- LOG_DEBUG("Uncompressed chat to '%s' - %d bytes\n", t->payload_variant.chat.to,
- strlen(t->payload_variant.chat.to));
- LOG_DEBUG("Compressed chat to '%s' - %d bytes\n", compressed.payload_variant.chat.to, length);
+ LOG_DEBUG("Compressed chat to: %d bytes\n", length);
+ }
+
+ if (t->payload_variant.chat.has_to_callsign) {
+ compressed.payload_variant.chat.has_to_callsign = true;
+ length =
+ unishox2_compress_simple(t->payload_variant.chat.to_callsign, strlen(t->payload_variant.chat.to_callsign),
+ compressed.payload_variant.chat.to_callsign);
+ LOG_DEBUG("Compressed chat to_callsign: %d bytes\n", length);
}
}
mp.decoded.payload.size = pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes),
meshtastic_TAKPacket_fields, &compressed);
- LOG_DEBUG("Final payload size of %d bytes\n", mp.decoded.payload.size);
+ LOG_DEBUG("Final payload: %d bytes\n", mp.decoded.payload.size);
} else {
if (!t->is_compressed) {
// Not compressed. Something is wrong
@@ -113,27 +114,31 @@ void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtast
auto length =
unishox2_decompress_simple(t->contact.callsign, strlen(t->contact.callsign), uncompressed.contact.callsign);
- LOG_DEBUG("Compressed callsign: %d bytes\n", strlen(t->contact.callsign));
- LOG_DEBUG("Decompressed callsign: '%s' @ %d bytes\n", uncompressed.contact.callsign, length);
+ LOG_DEBUG("Decompressed callsign: %d bytes\n", length);
length = unishox2_decompress_simple(t->contact.device_callsign, strlen(t->contact.device_callsign),
uncompressed.contact.device_callsign);
- LOG_DEBUG("Compressed device_callsign: %d bytes\n", strlen(t->contact.device_callsign));
- LOG_DEBUG("Decompressed device_callsign: '%s' @ %d bytes\n", uncompressed.contact.device_callsign, length);
+ LOG_DEBUG("Decompressed device_callsign: %d bytes\n", length);
}
if (uncompressed.which_payload_variant == meshtastic_TAKPacket_chat_tag) {
auto length = unishox2_decompress_simple(t->payload_variant.chat.message, strlen(t->payload_variant.chat.message),
uncompressed.payload_variant.chat.message);
- LOG_DEBUG("Compressed chat message: %d bytes\n", strlen(t->payload_variant.chat.message));
- LOG_DEBUG("Decompressed chat message: '%s' @ %d bytes\n", uncompressed.payload_variant.chat.message, length);
+ LOG_DEBUG("Decompressed chat message: %d bytes\n", length);
if (t->payload_variant.chat.has_to) {
uncompressed.payload_variant.chat.has_to = true;
length = unishox2_decompress_simple(t->payload_variant.chat.to, strlen(t->payload_variant.chat.to),
uncompressed.payload_variant.chat.to);
- LOG_DEBUG("Compressed chat to: %d bytes\n", strlen(t->payload_variant.chat.to));
- LOG_DEBUG("Decompressed chat to: '%s' @ %d bytes\n", uncompressed.payload_variant.chat.to, length);
+ LOG_DEBUG("Decompressed chat to: %d bytes\n", length);
+ }
+
+ if (t->payload_variant.chat.has_to_callsign) {
+ uncompressed.payload_variant.chat.has_to_callsign = true;
+ length =
+ unishox2_decompress_simple(t->payload_variant.chat.to_callsign, strlen(t->payload_variant.chat.to_callsign),
+ uncompressed.payload_variant.chat.to_callsign);
+ LOG_DEBUG("Decompressed chat to_callsign: %d bytes\n", length);
}
}
decompressedCopy->decoded.payload.size =
diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp
index cbd6fee72..f513e045f 100644
--- a/src/modules/CannedMessageModule.cpp
+++ b/src/modules/CannedMessageModule.cpp
@@ -47,6 +47,12 @@ CannedMessageModule::CannedMessageModule()
disable();
} else {
LOG_INFO("CannedMessageModule is enabled\n");
+
+ // T-Watch interface currently has no way to select destination type, so default to 'node'
+#if defined(T_WATCH_S3) || defined(RAK14014)
+ this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE;
+#endif
+
this->inputObserver.observe(inputBroker);
}
} else {
@@ -67,8 +73,16 @@ int CannedMessageModule::splitConfiguredMessages()
int messageIndex = 0;
int i = 0;
+ String messages = cannedMessageModuleConfig.messages;
+
+#if defined(T_WATCH_S3) || defined(RAK14014)
+ String separator = messages.length() ? "|" : "";
+
+ messages = "[---- Free Text ----]" + separator + messages;
+#endif
+
// collect all the message parts
- strncpy(this->messageStore, cannedMessageModuleConfig.messages, sizeof(this->messageStore));
+ strncpy(this->messageStore, messages.c_str(), sizeof(this->messageStore));
// The first message points to the beginning of the store.
this->messages[messageIndex++] = this->messageStore;
@@ -78,7 +92,6 @@ int CannedMessageModule::splitConfiguredMessages()
if (this->messageStore[i] == '|') {
// Message ending found, replace it with string-end character.
this->messageStore[i] = '\0';
- LOG_DEBUG("CannedMessage %d is: '%s'\n", messageIndex - 1, this->messages[messageIndex - 1]);
// hit our max messages, bail
if (messageIndex >= CANNED_MESSAGE_MODULE_MESSAGE_MAX_COUNT) {
@@ -119,20 +132,30 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event)
bool validEvent = false;
if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP)) {
if (this->messagesCount > 0) {
- // LOG_DEBUG("Canned message event UP\n");
this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_UP;
validEvent = true;
}
}
if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN)) {
if (this->messagesCount > 0) {
- // LOG_DEBUG("Canned message event DOWN\n");
this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_DOWN;
validEvent = true;
}
}
if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT)) {
- LOG_DEBUG("Canned message event Select\n");
+
+#if defined(T_WATCH_S3) || defined(RAK14014)
+ if (this->currentMessageIndex == 0) {
+ this->runState = CANNED_MESSAGE_RUN_STATE_FREETEXT;
+
+ UIFrameEvent e = {false, true};
+ e.frameChanged = true;
+ this->notifyObservers(&e);
+
+ return 0;
+ }
+#endif
+
// when inactive, call the onebutton shortpress instead. Activate Module only on up/down
if ((this->runState == CANNED_MESSAGE_RUN_STATE_INACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_DISABLED)) {
powerFSM.trigger(EVENT_PRESS);
@@ -143,51 +166,184 @@ int CannedMessageModule::handleInputEvent(const InputEvent *event)
}
}
if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL)) {
- LOG_DEBUG("Canned message event Cancel\n");
UIFrameEvent e = {false, true};
e.frameChanged = true;
this->currentMessageIndex = -1;
+
+#if !defined(T_WATCH_S3) && !defined(RAK14014)
this->freetext = ""; // clear freetext
this->cursor = 0;
this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE;
+#endif
+
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
this->notifyObservers(&e);
}
if ((event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK)) ||
(event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) ||
(event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT))) {
- // LOG_DEBUG("Canned message event (%x)\n", event->kbchar);
+
+#if defined(T_WATCH_S3) || defined(RAK14014)
+ if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) {
+ this->payload = 0xb4;
+ } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) {
+ this->payload = 0xb7;
+ }
+#else
// tweak for left/right events generated via trackball/touch with empty kbchar
if (!event->kbchar) {
if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) {
this->payload = 0xb4;
- this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE;
} else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) {
this->payload = 0xb7;
- this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NODE;
}
} else {
// pass the pressed key
this->payload = event->kbchar;
}
+#endif
+
this->lastTouchMillis = millis();
validEvent = true;
}
if (event->inputEvent == static_cast(ANYKEY)) {
- LOG_DEBUG("Canned message event any key pressed\n");
// when inactive, this will switch to the freetext mode
if ((this->runState == CANNED_MESSAGE_RUN_STATE_INACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) ||
(this->runState == CANNED_MESSAGE_RUN_STATE_DISABLED)) {
this->runState = CANNED_MESSAGE_RUN_STATE_FREETEXT;
}
- // pass the pressed key
- // LOG_DEBUG("Canned message ANYKEY (%x)\n", event->kbchar);
- this->payload = event->kbchar;
- this->lastTouchMillis = millis();
- validEvent = true;
+
+ validEvent = false; // If key is normal than it will be set to true.
+
+ // Run modifier key code below, (doesnt inturrupt typing or reset to start screen page)
+ switch (event->kbchar) {
+ case 0x11: // make screen brighter
+ if (screen)
+ screen->increaseBrightness();
+ LOG_DEBUG("increasing Screen Brightness\n");
+ break;
+ case 0x12: // make screen dimmer
+ if (screen)
+ screen->decreaseBrightness();
+ LOG_DEBUG("Decreasing Screen Brightness\n");
+ break;
+ case 0xf1: // draw modifier (function) symbal
+ if (screen)
+ screen->setFunctionSymbal("Fn");
+ break;
+ case 0xf2: // remove modifier (function) symbal
+ if (screen)
+ screen->removeFunctionSymbal("Fn");
+ break;
+ // mute (switch off/toggle) external notifications on fn+m
+ case 0xac:
+ if (moduleConfig.external_notification.enabled == true) {
+ if (externalNotificationModule->getMute()) {
+ externalNotificationModule->setMute(false);
+ showTemporaryMessage("Notifications \nEnabled");
+ if (screen)
+ screen->removeFunctionSymbal("M"); // remove the mute symbol from the bottom right corner
+ } else {
+ externalNotificationModule->stopNow(); // this will turn off all GPIO and sounds and idle the loop
+ externalNotificationModule->setMute(true);
+ showTemporaryMessage("Notifications \nDisabled");
+ if (screen)
+ screen->setFunctionSymbal("M"); // add the mute symbol to the bottom right corner
+ }
+ }
+ break;
+ case 0x9e: // toggle GPS like triple press does
+#if !MESHTASTIC_EXCLUDE_GPS
+ if (gps != nullptr) {
+ gps->toggleGpsMode();
+ }
+ if (screen)
+ screen->forceDisplay();
+ showTemporaryMessage("GPS Toggled");
+#endif
+ break;
+ case 0xaf: // fn+space send network ping like double press does
+ service.refreshLocalMeshNode();
+ if (service.trySendPosition(NODENUM_BROADCAST, true)) {
+ showTemporaryMessage("Position \nUpdate Sent");
+ } else {
+ showTemporaryMessage("Node Info \nUpdate Sent");
+ }
+ break;
+ default:
+ // pass the pressed key
+ // LOG_DEBUG("Canned message ANYKEY (%x)\n", event->kbchar);
+ this->payload = event->kbchar;
+ this->lastTouchMillis = millis();
+ validEvent = true;
+ break;
+ }
+ if (screen && (event->kbchar != 0xf1)) {
+ screen->removeFunctionSymbal("Fn"); // remove modifier (function) symbal
+ }
}
+
+#if defined(T_WATCH_S3) || defined(RAK14014)
+ if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
+ String keyTapped = keyForCoordinates(event->touchX, event->touchY);
+
+ if (keyTapped == "⇧") {
+ this->highlight = -1;
+
+ this->payload = 0x00;
+
+ validEvent = true;
+
+ this->shift = !this->shift;
+ } else if (keyTapped == "⌫") {
+ this->highlight = keyTapped[0];
+
+ this->payload = 0x08;
+
+ validEvent = true;
+
+ this->shift = false;
+ } else if (keyTapped == "123" || keyTapped == "ABC") {
+ this->highlight = -1;
+
+ this->payload = 0x00;
+
+ this->charSet = this->charSet == 0 ? 1 : 0;
+
+ validEvent = true;
+ } else if (keyTapped == " ") {
+ this->highlight = keyTapped[0];
+
+ this->payload = keyTapped[0];
+
+ validEvent = true;
+
+ this->shift = false;
+ } else if (keyTapped == "↵") {
+ this->highlight = 0x00;
+
+ this->runState = CANNED_MESSAGE_RUN_STATE_ACTION_SELECT;
+
+ this->payload = CANNED_MESSAGE_RUN_STATE_FREETEXT;
+
+ this->currentMessageIndex = event->kbchar - 1;
+
+ validEvent = true;
+
+ this->shift = false;
+ } else if (keyTapped != "") {
+ this->highlight = keyTapped[0];
+
+ this->payload = this->shift ? keyTapped[0] : std::tolower(keyTapped[0]);
+
+ validEvent = true;
+
+ this->shift = false;
+ }
+ }
+#endif
+
if (event->inputEvent == static_cast(MATRIXKEY)) {
- LOG_DEBUG("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 = MATRIXKEY;
@@ -233,29 +389,38 @@ int32_t CannedMessageModule::runOnce()
{
if (((!moduleConfig.canned_message.enabled) && !CANNED_MESSAGE_MODULE_ENABLE) ||
(this->runState == CANNED_MESSAGE_RUN_STATE_DISABLED) || (this->runState == CANNED_MESSAGE_RUN_STATE_INACTIVE)) {
+ temporaryMessage = "";
return INT32_MAX;
}
// LOG_DEBUG("Check status\n");
UIFrameEvent e = {false, true};
if ((this->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) ||
- (this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED)) {
- // TODO: might have some feedback of sendig state
+ (this->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) || (this->runState == CANNED_MESSAGE_RUN_STATE_MESSAGE)) {
+ // TODO: might have some feedback of sending state
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
+ temporaryMessage = "";
e.frameChanged = true;
this->currentMessageIndex = -1;
this->freetext = ""; // clear freetext
this->cursor = 0;
+
+#if !defined(T_WATCH_S3) && !defined(RAK14014)
this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE;
+#endif
+
this->notifyObservers(&e);
} else if (((this->runState == CANNED_MESSAGE_RUN_STATE_ACTIVE) || (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT)) &&
((millis() - this->lastTouchMillis) > INACTIVATE_AFTER_MS)) {
// Reset module
- LOG_DEBUG("Reset due to lack of activity.\n");
e.frameChanged = true;
this->currentMessageIndex = -1;
this->freetext = ""; // clear freetext
this->cursor = 0;
+
+#if !defined(T_WATCH_S3) && !defined(RAK14014)
this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE;
+#endif
+
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
this->notifyObservers(&e);
} else if (this->runState == CANNED_MESSAGE_RUN_STATE_ACTION_SELECT) {
@@ -264,7 +429,6 @@ int32_t CannedMessageModule::runOnce()
sendText(this->dest, indexChannels[this->channel], this->freetext.c_str(), true);
this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE;
} else {
- LOG_DEBUG("Reset message is empty.\n");
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
}
} else {
@@ -273,11 +437,15 @@ int32_t CannedMessageModule::runOnce()
powerFSM.trigger(EVENT_PRESS);
return INT32_MAX;
} else {
+#if defined(T_WATCH_S3) || defined(RAK14014)
+ sendText(this->dest, indexChannels[this->channel], this->messages[this->currentMessageIndex], true);
+#else
sendText(NODENUM_BROADCAST, channels.getPrimaryIndex(), this->messages[this->currentMessageIndex], true);
+#endif
}
this->runState = CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE;
} else {
- LOG_DEBUG("Reset message is empty.\n");
+ // LOG_DEBUG("Reset message is empty.\n");
this->runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
}
}
@@ -285,7 +453,11 @@ int32_t CannedMessageModule::runOnce()
this->currentMessageIndex = -1;
this->freetext = ""; // clear freetext
this->cursor = 0;
+
+#if !defined(T_WATCH_S3) && !defined(RAK14014)
this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE;
+#endif
+
this->notifyObservers(&e);
return 2000;
} else if ((this->runState != CANNED_MESSAGE_RUN_STATE_FREETEXT) && (this->currentMessageIndex == -1)) {
@@ -298,7 +470,11 @@ int32_t CannedMessageModule::runOnce()
this->currentMessageIndex = getPrevIndex();
this->freetext = ""; // clear freetext
this->cursor = 0;
+
+#if !defined(T_WATCH_S3) && !defined(RAK14014)
this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE;
+#endif
+
this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE;
LOG_DEBUG("MOVE UP (%d):%s\n", this->currentMessageIndex, this->getCurrentMessage());
}
@@ -307,7 +483,11 @@ int32_t CannedMessageModule::runOnce()
this->currentMessageIndex = this->getNextIndex();
this->freetext = ""; // clear freetext
this->cursor = 0;
+
+#if !defined(T_WATCH_S3) && !defined(RAK14014)
this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE;
+#endif
+
this->runState = CANNED_MESSAGE_RUN_STATE_ACTIVE;
LOG_DEBUG("MOVE DOWN (%d):%s\n", this->currentMessageIndex, this->getCurrentMessage());
}
@@ -388,9 +568,10 @@ int32_t CannedMessageModule::runOnce()
}
if (this->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
e.frameChanged = true;
- switch (this->payload) {
- case 0x08: // backspace
- if (this->freetext.length() > 0) {
+ switch (this->payload) { // code below all trigger the freetext window (where you type to send a message) or reset the
+ // display back to the default window
+ case 0x08: // backspace
+ if (this->freetext.length() > 0 && this->highlight == 0x00) {
if (this->cursor == this->freetext.length()) {
this->freetext = this->freetext.substring(0, this->freetext.length() - 1);
} else {
@@ -401,7 +582,6 @@ int32_t CannedMessageModule::runOnce()
}
break;
case 0x09: // tab
- case 0x91: // alt+t for T-Deck that doesn't have a tab key
if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL) {
this->destSelect = CANNED_MESSAGE_DESTINATION_TYPE_NONE;
} else if (this->destSelect == CANNED_MESSAGE_DESTINATION_TYPE_NODE) {
@@ -414,7 +594,7 @@ int32_t CannedMessageModule::runOnce()
case 0xb7: // right
// already handled above
break;
- // handle fn+s for shutdown
+ // handle fn+s for shutdown
case 0x9b:
if (screen)
screen->startShutdownScreen();
@@ -428,41 +608,20 @@ int32_t CannedMessageModule::runOnce()
rebootAtMsec = millis() + DEFAULT_REBOOT_SECONDS * 1000;
runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
break;
- case 0x9e: // toggle GPS like triple press does
- if (gps != nullptr) {
- gps->toggleGpsMode();
- }
- if (screen)
- screen->forceDisplay();
- runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
- break;
-
- // mute (switch off/toggle) external notifications on fn+m
- case 0xac:
- if (moduleConfig.external_notification.enabled == true) {
- if (externalNotificationModule->getMute()) {
- externalNotificationModule->setMute(false);
- runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
- } else {
- externalNotificationModule->stopNow(); // this will turn off all GPIO and sounds and idle the loop
- externalNotificationModule->setMute(true);
- runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
- }
- }
- break;
- case 0xaf: // fn+space send network ping like double press does
- service.refreshLocalMeshNode();
- service.sendNetworkPing(NODENUM_BROADCAST, true);
- runState = CANNED_MESSAGE_RUN_STATE_INACTIVE;
- break;
default:
+ if (this->highlight != 0x00) {
+ break;
+ }
+
if (this->cursor == this->freetext.length()) {
this->freetext += this->payload;
} else {
this->freetext =
this->freetext.substring(0, this->cursor) + this->payload + this->freetext.substring(this->cursor);
}
+
this->cursor += 1;
+
uint16_t maxChars = meshtastic_Constants_DATA_PAYLOAD_LEN - (moduleConfig.canned_message.send_bell ? 1 : 0);
if (this->freetext.length() > maxChars) {
this->cursor = maxChars;
@@ -470,6 +629,8 @@ int32_t CannedMessageModule::runOnce()
}
break;
}
+ if (screen)
+ screen->removeFunctionSymbal("Fn");
}
this->lastTouchMillis = millis();
@@ -542,12 +703,222 @@ int CannedMessageModule::getPrevIndex()
return this->currentMessageIndex - 1;
}
}
+void CannedMessageModule::showTemporaryMessage(const String &message)
+{
+ temporaryMessage = message;
+ UIFrameEvent e = {false, true};
+ e.frameChanged = true;
+ notifyObservers(&e);
+ runState = CANNED_MESSAGE_RUN_STATE_MESSAGE;
+ // run this loop again in 2 seconds, next iteration will clear the display
+ setIntervalFromNow(2000);
+}
+
+#if defined(T_WATCH_S3) || defined(RAK14014)
+
+String CannedMessageModule::keyForCoordinates(uint x, uint y)
+{
+ int outerSize = *(&this->keyboard[this->charSet] + 1) - this->keyboard[this->charSet];
+
+ for (int8_t outerIndex = 0; outerIndex < outerSize; outerIndex++) {
+ int innerSize = *(&this->keyboard[this->charSet][outerIndex] + 1) - this->keyboard[this->charSet][outerIndex];
+
+ for (int8_t innerIndex = 0; innerIndex < innerSize; innerIndex++) {
+ Letter letter = this->keyboard[this->charSet][outerIndex][innerIndex];
+
+ if (x > letter.rectX && x < (letter.rectX + letter.rectWidth) && y > letter.rectY &&
+ y < (letter.rectY + letter.rectHeight)) {
+ return letter.character;
+ }
+ }
+ }
+
+ return "";
+}
+
+void CannedMessageModule::drawKeyboard(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
+{
+ int outerSize = *(&this->keyboard[this->charSet] + 1) - this->keyboard[this->charSet];
+
+ int xOffset = 0;
+
+ int yOffset = 56;
+
+ display->setTextAlignment(TEXT_ALIGN_LEFT);
+
+ display->setFont(FONT_SMALL);
+
+ display->setColor(OLEDDISPLAY_COLOR::WHITE);
+
+ display->drawStringMaxWidth(0, 0, display->getWidth(),
+ cannedMessageModule->drawWithCursor(cannedMessageModule->freetext, cannedMessageModule->cursor));
+
+ display->setFont(FONT_MEDIUM);
+
+ int cellHeight = round((display->height() - 64) / outerSize);
+
+ int yCorrection = 8;
+
+ for (int8_t outerIndex = 0; outerIndex < outerSize; outerIndex++) {
+ yOffset += outerIndex > 0 ? cellHeight : 0;
+
+ int innerSizeBound = *(&this->keyboard[this->charSet][outerIndex] + 1) - this->keyboard[this->charSet][outerIndex];
+
+ int innerSize = 0;
+
+ for (int8_t innerIndex = 0; innerIndex < innerSizeBound; innerIndex++) {
+ if (this->keyboard[this->charSet][outerIndex][innerIndex].character != "") {
+ innerSize++;
+ }
+ }
+
+ int cellWidth = display->width() / innerSize;
+
+ for (int8_t innerIndex = 0; innerIndex < innerSize; innerIndex++) {
+ xOffset += innerIndex > 0 ? cellWidth : 0;
+
+ Letter letter = this->keyboard[this->charSet][outerIndex][innerIndex];
+
+ Letter updatedLetter = {letter.character, letter.width, xOffset, yOffset, cellWidth, cellHeight};
+
+ this->keyboard[this->charSet][outerIndex][innerIndex] = updatedLetter;
+
+ float characterOffset = ((cellWidth / 2) - (letter.width / 2));
+
+ if (letter.character == "⇧") {
+ if (this->shift) {
+ display->fillRect(xOffset, yOffset, cellWidth, cellHeight);
+
+ display->setColor(OLEDDISPLAY_COLOR::BLACK);
+
+ drawShiftIcon(display, xOffset + characterOffset, yOffset + yCorrection + 5, 1.2);
+
+ display->setColor(OLEDDISPLAY_COLOR::WHITE);
+ } else {
+ display->drawRect(xOffset, yOffset, cellWidth, cellHeight);
+
+ drawShiftIcon(display, xOffset + characterOffset, yOffset + yCorrection + 5, 1.2);
+ }
+ } else if (letter.character == "⌫") {
+ if (this->highlight == letter.character[0]) {
+ display->fillRect(xOffset, yOffset, cellWidth, cellHeight);
+
+ display->setColor(OLEDDISPLAY_COLOR::BLACK);
+
+ drawBackspaceIcon(display, xOffset + characterOffset, yOffset + yCorrection + 5, 1.2);
+
+ display->setColor(OLEDDISPLAY_COLOR::WHITE);
+
+ setIntervalFromNow(0);
+ } else {
+ display->drawRect(xOffset, yOffset, cellWidth, cellHeight);
+
+ drawBackspaceIcon(display, xOffset + characterOffset, yOffset + yCorrection + 5, 1.2);
+ }
+ } else if (letter.character == "↵") {
+ display->drawRect(xOffset, yOffset, cellWidth, cellHeight);
+
+ drawEnterIcon(display, xOffset + characterOffset, yOffset + yCorrection + 5, 1.7);
+ } else {
+ if (this->highlight == letter.character[0]) {
+ display->fillRect(xOffset, yOffset, cellWidth, cellHeight);
+
+ display->setColor(OLEDDISPLAY_COLOR::BLACK);
+
+ display->drawString(xOffset + characterOffset, yOffset + yCorrection,
+ letter.character == " " ? "space" : letter.character);
+
+ display->setColor(OLEDDISPLAY_COLOR::WHITE);
+
+ setIntervalFromNow(0);
+ } else {
+ display->drawRect(xOffset, yOffset, cellWidth, cellHeight);
+
+ display->drawString(xOffset + characterOffset, yOffset + yCorrection,
+ letter.character == " " ? "space" : letter.character);
+ }
+ }
+ }
+
+ xOffset = 0;
+ }
+
+ this->highlight = 0x00;
+}
+
+void CannedMessageModule::drawShiftIcon(OLEDDisplay *display, int x, int y, float scale)
+{
+ PointStruct shiftIcon[10] = {{8, 0}, {15, 7}, {15, 8}, {12, 8}, {12, 12}, {4, 12}, {4, 8}, {1, 8}, {1, 7}, {8, 0}};
+
+ int size = 10;
+
+ for (int i = 0; i < size - 1; i++) {
+ int x0 = x + (shiftIcon[i].x * scale);
+ int y0 = y + (shiftIcon[i].y * scale);
+ int x1 = x + (shiftIcon[i + 1].x * scale);
+ int y1 = y + (shiftIcon[i + 1].y * scale);
+
+ display->drawLine(x0, y0, x1, y1);
+ }
+}
+
+void CannedMessageModule::drawBackspaceIcon(OLEDDisplay *display, int x, int y, float scale)
+{
+ PointStruct backspaceIcon[6] = {{0, 7}, {5, 2}, {15, 2}, {15, 12}, {5, 12}, {0, 7}};
+
+ int size = 6;
+
+ for (int i = 0; i < size - 1; i++) {
+ int x0 = x + (backspaceIcon[i].x * scale);
+ int y0 = y + (backspaceIcon[i].y * scale);
+ int x1 = x + (backspaceIcon[i + 1].x * scale);
+ int y1 = y + (backspaceIcon[i + 1].y * scale);
+
+ display->drawLine(x0, y0, x1, y1);
+ }
+
+ PointStruct backspaceIconX[4] = {{7, 4}, {13, 10}, {7, 10}, {13, 4}};
+
+ size = 4;
+
+ for (int i = 0; i < size - 1; i++) {
+ int x0 = x + (backspaceIconX[i].x * scale);
+ int y0 = y + (backspaceIconX[i].y * scale);
+ int x1 = x + (backspaceIconX[i + 1].x * scale);
+ int y1 = y + (backspaceIconX[i + 1].y * scale);
+
+ display->drawLine(x0, y0, x1, y1);
+ }
+}
+
+void CannedMessageModule::drawEnterIcon(OLEDDisplay *display, int x, int y, float scale)
+{
+ PointStruct enterIcon[6] = {{0, 7}, {4, 3}, {4, 11}, {0, 7}, {15, 7}, {15, 0}};
+
+ int size = 6;
+
+ for (int i = 0; i < size - 1; i++) {
+ int x0 = x + (enterIcon[i].x * scale);
+ int y0 = y + (enterIcon[i].y * scale);
+ int x1 = x + (enterIcon[i + 1].x * scale);
+ int y1 = y + (enterIcon[i + 1].y * scale);
+
+ display->drawLine(x0, y0, x1, y1);
+ }
+}
+
+#endif
void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
char buffer[50];
- if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) {
+ if (temporaryMessage.length() != 0) {
+ LOG_DEBUG("Drawing temporary message: %s", temporaryMessage.c_str());
+ display->setTextAlignment(TEXT_ALIGN_CENTER);
+ display->setFont(FONT_MEDIUM);
+ display->drawString(display->getWidth() / 2 + x, 0 + y + 12, temporaryMessage);
+ } else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED) {
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_MEDIUM);
String displayString;
@@ -558,6 +929,16 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
}
display->drawStringf(display->getWidth() / 2 + x, 0 + y + 12, buffer, displayString,
cannedMessageModule->getNodeName(this->incoming));
+
+ display->setFont(FONT_SMALL);
+
+ String snrString = "Last Rx SNR: %f";
+ String rssiString = "Last Rx RSSI: %d";
+
+ if (this->ack) {
+ display->drawStringf(display->getWidth() / 2 + x, y + 100, buffer, snrString, this->lastRxSnr);
+ display->drawStringf(display->getWidth() / 2 + x, y + 130, buffer, rssiString, this->lastRxRssi);
+ }
} else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE) {
display->setTextAlignment(TEXT_ALIGN_CENTER);
display->setFont(FONT_MEDIUM);
@@ -567,6 +948,11 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
display->setFont(FONT_SMALL);
display->drawString(10 + x, 0 + y + FONT_HEIGHT_SMALL, "Canned Message\nModule disabled.");
} else if (cannedMessageModule->runState == CANNED_MESSAGE_RUN_STATE_FREETEXT) {
+
+#if defined(T_WATCH_S3) || defined(RAK14014)
+ drawKeyboard(display, state, 0, 0);
+#else
+
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
if (this->destSelect != CANNED_MESSAGE_DESTINATION_TYPE_NONE) {
@@ -607,6 +993,7 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
display->drawStringMaxWidth(
0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(),
cannedMessageModule->drawWithCursor(cannedMessageModule->freetext, cannedMessageModule->cursor));
+#endif
} else {
if (this->messagesCount > 0) {
display->setTextAlignment(TEXT_ALIGN_LEFT);
diff --git a/src/modules/CannedMessageModule.h b/src/modules/CannedMessageModule.h
index 4802be078..00e8c2bf9 100644
--- a/src/modules/CannedMessageModule.h
+++ b/src/modules/CannedMessageModule.h
@@ -10,6 +10,7 @@ enum cannedMessageModuleRunState {
CANNED_MESSAGE_RUN_STATE_FREETEXT,
CANNED_MESSAGE_RUN_STATE_SENDING_ACTIVE,
CANNED_MESSAGE_RUN_STATE_ACK_NACK_RECEIVED,
+ CANNED_MESSAGE_RUN_STATE_MESSAGE,
CANNED_MESSAGE_RUN_STATE_ACTION_SELECT,
CANNED_MESSAGE_RUN_STATE_ACTION_UP,
CANNED_MESSAGE_RUN_STATE_ACTION_DOWN,
@@ -21,6 +22,17 @@ enum cannedMessageDestinationType {
CANNED_MESSAGE_DESTINATION_TYPE_CHANNEL
};
+enum CannedMessageModuleIconType { shift, backspace, space, enter };
+
+struct Letter {
+ String character;
+ float width;
+ int rectX;
+ int rectY;
+ int rectWidth;
+ int rectHeight;
+};
+
#define CANNED_MESSAGE_MODULE_MESSAGE_MAX_COUNT 50
/**
* Sum of CannedMessageModuleConfig part sizes.
@@ -51,6 +63,8 @@ class CannedMessageModule : public SinglePortModule, public Observablerx_rssi != 0) {
+ this->lastRxRssi = p->rx_rssi;
+ }
+
+ if (p->rx_snr > 0) {
+ this->lastRxSnr = p->rx_snr;
+ }
+
switch (p->decoded.portnum) {
case meshtastic_PortNum_TEXT_MESSAGE_APP:
case meshtastic_PortNum_ROUTING_APP:
@@ -76,6 +98,18 @@ class CannedMessageModule : public SinglePortModule, public ObservableshouldDraw(); }
virtual Observable *getUIFrameObservable() override { return this; }
@@ -107,12 +141,85 @@ class CannedMessageModule : public SinglePortModule, public Observable
+
+#include "modules/Telemetry/Sensor/DFRobotLarkSensor.h"
+#include "modules/Telemetry/UnitConversions.h"
+
+#include
+
+DropzoneModule *dropzoneModule;
+
+int32_t DropzoneModule::runOnce()
+{
+ // Send on a 5 second delay from receiving the matching request
+ if (startSendConditions != 0 && (startSendConditions + 5000U) < millis()) {
+ service.sendToMesh(sendConditions(), RX_SRC_LOCAL);
+ startSendConditions = 0;
+ }
+ // Run every second to check if we need to send conditions
+ return 1000;
+}
+
+ProcessMessage DropzoneModule::handleReceived(const meshtastic_MeshPacket &mp)
+{
+ auto &p = mp.decoded;
+ char matchCompare[54];
+ auto incomingMessage = reinterpret_cast(p.payload.bytes);
+ sprintf(matchCompare, "%s conditions", owner.short_name);
+ if (strncasecmp(incomingMessage, matchCompare, strlen(matchCompare)) == 0) {
+ LOG_DEBUG("Received dropzone conditions request\n");
+ startSendConditions = millis();
+ }
+
+ sprintf(matchCompare, "%s conditions", owner.long_name);
+ if (strncasecmp(incomingMessage, matchCompare, strlen(matchCompare)) == 0) {
+ LOG_DEBUG("Received dropzone conditions request\n");
+ startSendConditions = millis();
+ }
+ return ProcessMessage::CONTINUE;
+}
+
+meshtastic_MeshPacket *DropzoneModule::sendConditions()
+{
+ char replyStr[200];
+ /*
+ CLOSED @ {HH:MM:SS}z
+ Wind 2 kts @ 125°
+ 29.25 inHg 72°C
+ */
+ uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true);
+ int hour = 0, min = 0, sec = 0;
+ if (rtc_sec > 0) {
+ long hms = rtc_sec % SEC_PER_DAY;
+ hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
+
+ hour = hms / SEC_PER_HOUR;
+ min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
+ sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN;
+ }
+
+ // Check if the dropzone is open or closed by reading the analog pin
+ // If pin is connected to GND (below 100 should be lower than floating voltage),
+ // the dropzone is open
+ auto dropzoneStatus = analogRead(A1) < 100 ? "OPEN" : "CLOSED";
+ auto reply = allocDataPacket();
+
+ auto node = nodeDB->getMeshNode(nodeDB->getNodeNum());
+ if (sensor.hasSensor()) {
+ meshtastic_Telemetry telemetry = meshtastic_Telemetry_init_zero;
+ sensor.getMetrics(&telemetry);
+ auto windSpeed = UnitConversions::MetersPerSecondToKnots(telemetry.variant.environment_metrics.wind_speed);
+ auto windDirection = telemetry.variant.environment_metrics.wind_direction;
+ auto temp = telemetry.variant.environment_metrics.temperature;
+ auto baro = UnitConversions::HectoPascalToInchesOfMercury(telemetry.variant.environment_metrics.barometric_pressure);
+ sprintf(replyStr, "%s @ %02d:%02d:%02dz\nWind %.2f kts @ %d°\nBaro %.2f inHg %.2f°C", dropzoneStatus, hour, min, sec,
+ windSpeed, windDirection, baro, temp);
+ } else {
+ LOG_ERROR("No sensor found\n");
+ sprintf(replyStr, "%s @ %02d:%02d:%02d\nNo sensor found", dropzoneStatus, hour, min, sec);
+ }
+ LOG_DEBUG("Conditions reply: %s\n", replyStr);
+ reply->decoded.payload.size = strlen(replyStr); // You must specify how many bytes are in the reply
+ memcpy(reply->decoded.payload.bytes, replyStr, reply->decoded.payload.size);
+
+ return reply;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/DropzoneModule.h b/src/modules/DropzoneModule.h
new file mode 100644
index 000000000..28f54ee0f
--- /dev/null
+++ b/src/modules/DropzoneModule.h
@@ -0,0 +1,37 @@
+#pragma once
+#if !MESHTASTIC_EXCLUDE_DROPZONE
+#include "SinglePortModule.h"
+#include "modules/Telemetry/Sensor/DFRobotLarkSensor.h"
+
+/**
+ * An example module that replies to a message with the current conditions
+ * and status at the dropzone when it receives a text message mentioning it's name followed by "conditions"
+ */
+class DropzoneModule : public SinglePortModule, private concurrency::OSThread
+{
+ DFRobotLarkSensor sensor;
+
+ public:
+ /** Constructor
+ * name is for debugging output
+ */
+ DropzoneModule() : SinglePortModule("dropzone", meshtastic_PortNum_TEXT_MESSAGE_APP), concurrency::OSThread("DropzoneModule")
+ {
+ // Set up the analog pin for reading the dropzone status
+ pinMode(PIN_A1, INPUT);
+ }
+
+ virtual int32_t runOnce() override;
+
+ protected:
+ /** Called to handle a particular incoming message
+ */
+ virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
+
+ private:
+ meshtastic_MeshPacket *sendConditions();
+ uint32_t startSendConditions = 0;
+};
+
+extern DropzoneModule *dropzoneModule;
+#endif
\ No newline at end of file
diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index b898e72ee..c02559240 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -26,7 +26,18 @@
#ifdef HAS_NCP5623
#include
+#endif
+#ifdef HAS_NEOPIXEL
+#include
+#endif
+
+#ifdef UNPHONE
+#include "unPhone.h"
+extern unPhone unphone;
+#endif
+
+#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE)
uint8_t red = 0;
uint8_t green = 0;
uint8_t blue = 0;
@@ -108,27 +119,44 @@ int32_t ExternalNotificationModule::runOnce()
millis()) {
getExternal(2) ? setExternalOff(2) : setExternalOn(2);
}
+#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE)
+ red = (colorState & 4) ? brightnessValues[brightnessIndex] : 0; // Red enabled on colorState = 4,5,6,7
+ green = (colorState & 2) ? brightnessValues[brightnessIndex] : 0; // Green enabled on colorState = 2,3,6,7
+ blue = (colorState & 1) ? (brightnessValues[brightnessIndex] * 1.5) : 0; // Blue enabled on colorState = 1,3,5,7
#ifdef HAS_NCP5623
if (rgb_found.type == ScanI2C::NCP5623) {
- red = (colorState & 4) ? brightnessValues[brightnessIndex] : 0; // Red enabled on colorState = 4,5,6,7
- green = (colorState & 2) ? brightnessValues[brightnessIndex] : 0; // Green enabled on colorState = 2,3,6,7
- blue = (colorState & 1) ? (brightnessValues[brightnessIndex] * 1.5) : 0; // Blue enabled on colorState = 1,3,5,7
rgb.setColor(red, green, blue);
-
- if (ascending) { // fade in
- brightnessIndex++;
- if (brightnessIndex == (sizeof(brightnessValues) - 1)) {
- ascending = false;
- }
- } else {
- brightnessIndex--; // fade out
+ }
+#endif
+#ifdef RGBLED_CA
+ analogWrite(RGBLED_RED, 255 - red); // CA type needs reverse logic
+ analogWrite(RGBLED_GREEN, 255 - green);
+ analogWrite(RGBLED_BLUE, 255 - blue);
+#elif defined(RGBLED_RED)
+ analogWrite(RGBLED_RED, red);
+ analogWrite(RGBLED_GREEN, green);
+ analogWrite(RGBLED_BLUE, blue);
+#endif
+#ifdef HAS_NEOPIXEL
+ pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT);
+ pixels.show();
+#endif
+#ifdef UNPHONE
+ unphone.rgb(red, green, blue);
+#endif
+ if (ascending) { // fade in
+ brightnessIndex++;
+ if (brightnessIndex == (sizeof(brightnessValues) - 1)) {
+ ascending = false;
}
- if (brightnessIndex == 0) {
- ascending = true;
- colorState++; // next color
- if (colorState > 7) {
- colorState = 1;
- }
+ } else {
+ brightnessIndex--; // fade out
+ }
+ if (brightnessIndex == 0) {
+ ascending = true;
+ colorState++; // next color
+ if (colorState > 7) {
+ colorState = 1;
}
}
#endif
@@ -179,6 +207,9 @@ void ExternalNotificationModule::setExternalOn(uint8_t index)
switch (index) {
case 1:
+#ifdef UNPHONE
+ unphone.vibe(true); // the unPhone's vibration motor is on a i2c GPIO expander
+#endif
if (moduleConfig.external_notification.output_vibra)
digitalWrite(moduleConfig.external_notification.output_vibra, true);
break;
@@ -197,6 +228,22 @@ void ExternalNotificationModule::setExternalOn(uint8_t index)
rgb.setColor(red, green, blue);
}
#endif
+#ifdef RGBLED_CA
+ analogWrite(RGBLED_RED, 255 - red); // CA type needs reverse logic
+ analogWrite(RGBLED_GREEN, 255 - green);
+ analogWrite(RGBLED_BLUE, 255 - blue);
+#elif defined(RGBLED_RED)
+ analogWrite(RGBLED_RED, red);
+ analogWrite(RGBLED_GREEN, green);
+ analogWrite(RGBLED_BLUE, blue);
+#endif
+#ifdef HAS_NEOPIXEL
+ pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT);
+ pixels.show();
+#endif
+#ifdef UNPHONE
+ unphone.rgb(red, green, blue);
+#endif
#ifdef T_WATCH_S3
drv.go();
#endif
@@ -209,6 +256,9 @@ void ExternalNotificationModule::setExternalOff(uint8_t index)
switch (index) {
case 1:
+#ifdef UNPHONE
+ unphone.vibe(false); // the unPhone's vibration motor is on a i2c GPIO expander
+#endif
if (moduleConfig.external_notification.output_vibra)
digitalWrite(moduleConfig.external_notification.output_vibra, false);
break;
@@ -222,14 +272,33 @@ void ExternalNotificationModule::setExternalOff(uint8_t index)
break;
}
+#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE)
+ red = 0;
+ green = 0;
+ blue = 0;
#ifdef HAS_NCP5623
if (rgb_found.type == ScanI2C::NCP5623) {
- red = 0;
- green = 0;
- blue = 0;
rgb.setColor(red, green, blue);
}
#endif
+#ifdef RGBLED_CA
+ analogWrite(RGBLED_RED, 255 - red); // CA type needs reverse logic
+ analogWrite(RGBLED_GREEN, 255 - green);
+ analogWrite(RGBLED_BLUE, 255 - blue);
+#elif defined(RGBLED_RED)
+ analogWrite(RGBLED_RED, red);
+ analogWrite(RGBLED_GREEN, green);
+ analogWrite(RGBLED_BLUE, blue);
+#endif
+#ifdef HAS_NEOPIXEL
+ pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT);
+ pixels.show();
+#endif
+#ifdef UNPHONE
+ unphone.rgb(red, green, blue);
+#endif
+#endif
+
#ifdef T_WATCH_S3
drv.stop();
#endif
@@ -328,6 +397,21 @@ ExternalNotificationModule::ExternalNotificationModule()
rgb.begin();
rgb.setCurrent(10);
}
+#endif
+#ifdef RGBLED_RED
+ pinMode(RGBLED_RED, OUTPUT); // set up the RGB led pins
+ pinMode(RGBLED_GREEN, OUTPUT);
+ pinMode(RGBLED_BLUE, OUTPUT);
+#endif
+#ifdef RGBLED_CA
+ analogWrite(RGBLED_RED, 255); // with a common anode type, logic is reversed
+ analogWrite(RGBLED_GREEN, 255); // so we want to initialise with lights off
+ analogWrite(RGBLED_BLUE, 255);
+#endif
+#ifdef HAS_NEOPIXEL
+ pixels.begin(); // Initialise the pixel(s)
+ pixels.clear(); // Set all pixel colors to 'off'
+ pixels.setBrightness(moduleConfig.ambient_lighting.current);
#endif
} else {
LOG_INFO("External Notification Module Disabled\n");
@@ -509,4 +593,4 @@ void ExternalNotificationModule::handleSetRingtone(const char *from_msg)
if (changed) {
nodeDB->saveProto(rtttlConfigFile, meshtastic_RTTTLConfig_size, &meshtastic_RTTTLConfig_msg, &rtttlConfig);
}
-}
\ No newline at end of file
+}
diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
index c4d3ce73a..d40c5b141 100644
--- a/src/modules/Modules.cpp
+++ b/src/modules/Modules.cpp
@@ -43,10 +43,11 @@
#include "modules/Telemetry/DeviceTelemetry.h"
#endif
#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+#include "main.h"
#include "modules/Telemetry/AirQualityTelemetry.h"
#include "modules/Telemetry/EnvironmentTelemetry.h"
#endif
-#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY
+#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY
#include "modules/Telemetry/PowerTelemetry.h"
#endif
#ifdef ARCH_ESP32
@@ -71,6 +72,11 @@
#include "modules/SerialModule.h"
#endif
#endif
+
+#if !MESHTASTIC_EXCLUDE_DROPZONE
+#include "modules/DropzoneModule.h"
+#endif
+
/**
* Create module instances here. If you are adding a new module, you must 'new' it here (or somewhere else)
*/
@@ -101,6 +107,10 @@ void setupModules()
#if !MESHTASTIC_EXCLUDE_ATAK
atakPluginModule = new AtakPluginModule();
#endif
+
+#if !MESHTASTIC_EXCLUDE_DROPZONE
+ dropzoneModule = new DropzoneModule();
+#endif
// 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.
@@ -140,7 +150,7 @@ void setupModules()
#if HAS_SCREEN && !MESHTASTIC_EXCLUDE_CANNEDMESSAGES
cannedMessageModule = new CannedMessageModule();
#endif
-#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
+#if HAS_TELEMETRY
new DeviceTelemetryModule();
#endif
#if HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
@@ -149,7 +159,7 @@ void setupModules()
new AirQualityTelemetryModule();
}
#endif
-#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY
+#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_POWER_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
new PowerTelemetryModule();
#endif
#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \
diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp
index 8c8135deb..3925bea9a 100644
--- a/src/modules/NeighborInfoModule.cpp
+++ b/src/modules/NeighborInfoModule.cpp
@@ -116,9 +116,8 @@ Will be used for broadcast.
*/
int32_t NeighborInfoModule::runOnce()
{
- bool requestReplies = false;
if (airTime->isTxAllowedChannelUtil(true) && airTime->isTxAllowedAirUtil()) {
- sendNeighborInfo(NODENUM_BROADCAST, requestReplies);
+ sendNeighborInfo(NODENUM_BROADCAST, false);
}
return Default::getConfiguredOrDefaultMs(moduleConfig.neighbor_info.update_interval, default_broadcast_interval_secs);
}
diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp
index f77026708..78af7099a 100644
--- a/src/modules/NodeInfoModule.cpp
+++ b/src/modules/NodeInfoModule.cpp
@@ -58,10 +58,15 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha
meshtastic_MeshPacket *NodeInfoModule::allocReply()
{
+ if (!airTime->isTxAllowedChannelUtil(false)) {
+ ignoreRequest = true; // Mark it as ignored for MeshModule
+ LOG_DEBUG("Skip sending NodeInfo due to > 40 percent channel util.\n");
+ return NULL;
+ }
uint32_t now = millis();
// If we sent our NodeInfo less than 5 min. ago, don't send it again as it may be still underway.
if (lastSentToMesh && (now - lastSentToMesh) < (5 * 60 * 1000)) {
- LOG_DEBUG("Sending NodeInfo will be ignored since we just sent it.\n");
+ LOG_DEBUG("Skip sending NodeInfo since we just sent it less than 5 minutes ago.\n");
ignoreRequest = true; // Mark it as ignored for MeshModule
return NULL;
} else {
diff --git a/src/modules/PositionModule.cpp b/src/modules/PositionModule.cpp
index 658b8b5a7..49f2b808b 100644
--- a/src/modules/PositionModule.cpp
+++ b/src/modules/PositionModule.cpp
@@ -55,6 +55,15 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
isLocal = true;
if (config.position.fixed_position) {
LOG_DEBUG("Ignore incoming position update from myself except for time, because position.fixed_position is true\n");
+
+#ifdef T_WATCH_S3
+ // Since we return early if position.fixed_position is true, set the T-Watch's RTC to the time received from the
+ // client device here
+ if (p.time && channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) {
+ trySetRtc(p, isLocal, true);
+ }
+#endif
+
nodeDB->setLocalPosition(p, true);
return false;
} else {
@@ -71,8 +80,17 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
p.time);
if (p.time && channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) {
+ bool force = false;
+
+#ifdef T_WATCH_S3
+ // The T-Watch appears to "pause" its RTC when shut down, such that the time it reads upon powering on is the same as when
+ // it was shut down. So we need to force the update here, since otherwise RTC::perhapsSetRTC will ignore it because it
+ // will always be an equivalent or lesser RTCQuality (RTCQualityNTP or RTCQualityNet).
+ force = true;
+#endif
+
// Set from phone RTC Quality to RTCQualityNTP since it should be approximately so
- trySetRtc(p, isLocal);
+ trySetRtc(p, isLocal, force);
}
nodeDB->updatePosition(getFrom(&mp), p);
@@ -87,14 +105,31 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
return false; // Let others look at this message also if they want
}
-void PositionModule::trySetRtc(meshtastic_Position p, bool isLocal)
+void PositionModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_Position *p)
+{
+ // Phone position packets need to be truncated to the channel precision
+ if (nodeDB->getNodeNum() == getFrom(&mp) && (precision < 32 && precision > 0)) {
+ LOG_DEBUG("Truncating phone position to channel precision %i\n", precision);
+ p->latitude_i = p->latitude_i & (UINT32_MAX << (32 - precision));
+ p->longitude_i = p->longitude_i & (UINT32_MAX << (32 - precision));
+
+ // We want the imprecise position to be the middle of the possible location, not
+ p->latitude_i += (1 << (31 - precision));
+ p->longitude_i += (1 << (31 - precision));
+
+ mp.decoded.payload.size =
+ pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes), &meshtastic_Position_msg, p);
+ }
+}
+
+void PositionModule::trySetRtc(meshtastic_Position p, bool isLocal, bool forceUpdate)
{
struct timeval tv;
uint32_t secs = p.time;
tv.tv_sec = secs;
tv.tv_usec = 0;
- perhapsSetRTC(isLocal ? RTCQualityNTP : RTCQualityFromNet, &tv);
+ perhapsSetRTC(isLocal ? RTCQualityNTP : RTCQualityFromNet, &tv, forceUpdate);
}
meshtastic_MeshPacket *PositionModule::allocReply()
@@ -174,13 +209,13 @@ meshtastic_MeshPacket *PositionModule::allocReply()
p.ground_speed = localPosition.ground_speed;
// Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other
- // nodes shouldn't trust it anyways) Note: we allow a device with a local GPS to include the time, so that gpsless
- // devices can get time.
- if (getRTCQuality() < RTCQualityDevice) {
+ // nodes shouldn't trust it anyways) Note: we allow a device with a local GPS or NTP to include the time, so that devices
+ // without can get time.
+ if (getRTCQuality() < RTCQualityNTP) {
LOG_INFO("Stripping time %u from position send\n", p.time);
p.time = 0;
} else {
- p.time = getValidTime(RTCQualityDevice);
+ p.time = getValidTime(RTCQualityNTP);
LOG_INFO("Providing time to mesh %u\n", p.time);
}
@@ -326,7 +361,7 @@ int32_t PositionModule::runOnce()
// The minimum time (in seconds) that would pass before we are able to send a new position packet.
auto smartPosition = getDistanceTraveledSinceLastSend(node->position);
- uint32_t msSinceLastSend = now - lastGpsSend;
+ msSinceLastSend = now - lastGpsSend;
if (smartPosition.hasTraveledOverThreshold &&
Throttle::execute(
@@ -419,4 +454,4 @@ void PositionModule::handleNewPosition()
}
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/modules/PositionModule.h b/src/modules/PositionModule.h
index 89ff50c64..763b51e5c 100644
--- a/src/modules/PositionModule.h
+++ b/src/modules/PositionModule.h
@@ -42,6 +42,8 @@ class PositionModule : public ProtobufModule, private concu
*/
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Position *p) override;
+ virtual void alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_Position *p) override;
+
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
* so that subclasses can (optionally) send a response back to the original sender. */
virtual meshtastic_MeshPacket *allocReply() override;
@@ -52,7 +54,7 @@ class PositionModule : public ProtobufModule, private concu
private:
struct SmartPosition getDistanceTraveledSinceLastSend(meshtastic_PositionLite currentPosition);
meshtastic_MeshPacket *allocAtakPli();
- void trySetRtc(meshtastic_Position p, bool isLocal);
+ void trySetRtc(meshtastic_Position p, bool isLocal, bool forceUpdate = false);
uint32_t precision;
void sendLostAndFoundText();
diff --git a/src/modules/Telemetry/AirQualityTelemetry.cpp b/src/modules/Telemetry/AirQualityTelemetry.cpp
index a51a7cea9..4f5fbcd13 100644
--- a/src/modules/Telemetry/AirQualityTelemetry.cpp
+++ b/src/modules/Telemetry/AirQualityTelemetry.cpp
@@ -1,12 +1,15 @@
-#include "AirQualityTelemetry.h"
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "AirQualityTelemetry.h"
#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "RTC.h"
#include "Router.h"
-#include "configuration.h"
#include "main.h"
int32_t AirQualityTelemetryModule::runOnce()
@@ -89,7 +92,7 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
return false;
}
- meshtastic_Telemetry m;
+ meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
m.time = getTime();
m.which_variant = meshtastic_Telemetry_air_quality_metrics_tag;
m.variant.air_quality_metrics.pm10_standard = data.pm10_standard;
@@ -130,3 +133,5 @@ bool AirQualityTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
}
return true;
}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/AirQualityTelemetry.h b/src/modules/Telemetry/AirQualityTelemetry.h
index ab77d61e7..eb0355001 100644
--- a/src/modules/Telemetry/AirQualityTelemetry.h
+++ b/src/modules/Telemetry/AirQualityTelemetry.h
@@ -1,3 +1,7 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#pragma once
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "Adafruit_PM25AQI.h"
@@ -35,3 +39,5 @@ class AirQualityTelemetryModule : private concurrency::OSThread, public Protobuf
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
uint32_t lastSentToMesh = 0;
};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/DeviceTelemetry.cpp b/src/modules/Telemetry/DeviceTelemetry.cpp
index 3529267cb..b64e8d113 100644
--- a/src/modules/Telemetry/DeviceTelemetry.cpp
+++ b/src/modules/Telemetry/DeviceTelemetry.cpp
@@ -64,13 +64,17 @@ meshtastic_MeshPacket *DeviceTelemetryModule::allocReply()
meshtastic_Telemetry DeviceTelemetryModule::getDeviceTelemetry()
{
- meshtastic_Telemetry t;
+ meshtastic_Telemetry t = meshtastic_Telemetry_init_zero;
t.time = getTime();
t.which_variant = meshtastic_Telemetry_device_metrics_tag;
t.variant.device_metrics.air_util_tx = airTime->utilizationTXPercent();
+#if ARCH_PORTDUINO
+ t.variant.device_metrics.battery_level = MAGIC_USB_BATTERY_LEVEL;
+#else
t.variant.device_metrics.battery_level =
powerStatus->getIsCharging() ? MAGIC_USB_BATTERY_LEVEL : powerStatus->getBatteryChargePercent();
+#endif
t.variant.device_metrics.channel_utilization = airTime->channelUtilizationPercent();
t.variant.device_metrics.voltage = powerStatus->getBatteryVoltageMv() / 1000.0;
t.variant.device_metrics.uptime_seconds = getUptimeSeconds();
@@ -100,4 +104,4 @@ bool DeviceTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
service.sendToMesh(p, RX_SRC_LOCAL, true);
}
return true;
-}
\ No newline at end of file
+}
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.cpp b/src/modules/Telemetry/EnvironmentTelemetry.cpp
index 189ab7ed0..ff3202067 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.cpp
+++ b/src/modules/Telemetry/EnvironmentTelemetry.cpp
@@ -1,12 +1,15 @@
-#include "EnvironmentTelemetry.h"
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "Default.h"
+#include "EnvironmentTelemetry.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "RTC.h"
#include "Router.h"
-#include "configuration.h"
#include "main.h"
#include "power.h"
#include "sleep.h"
@@ -15,14 +18,23 @@
#include
// Sensors
+#include "Sensor/AHT10.h"
#include "Sensor/BME280Sensor.h"
#include "Sensor/BME680Sensor.h"
#include "Sensor/BMP085Sensor.h"
#include "Sensor/BMP280Sensor.h"
+#include "Sensor/DFRobotLarkSensor.h"
#include "Sensor/LPS22HBSensor.h"
#include "Sensor/MCP9808Sensor.h"
+#include "Sensor/MLX90632Sensor.h"
+#include "Sensor/NAU7802Sensor.h"
+#include "Sensor/OPT3001Sensor.h"
+#include "Sensor/RCWL9620Sensor.h"
#include "Sensor/SHT31Sensor.h"
+#include "Sensor/SHT4XSensor.h"
#include "Sensor/SHTC3Sensor.h"
+#include "Sensor/TSL2591Sensor.h"
+#include "Sensor/VEML7700Sensor.h"
BMP085Sensor bmp085Sensor;
BMP280Sensor bmp280Sensor;
@@ -32,6 +44,15 @@ MCP9808Sensor mcp9808Sensor;
SHTC3Sensor shtc3Sensor;
LPS22HBSensor lps22hbSensor;
SHT31Sensor sht31Sensor;
+VEML7700Sensor veml7700Sensor;
+TSL2591Sensor tsl2591Sensor;
+OPT3001Sensor opt3001Sensor;
+SHT4XSensor sht4xSensor;
+RCWL9620Sensor rcwl9620Sensor;
+AHT10Sensor aht10Sensor;
+MLX90632Sensor mlx90632Sensor;
+DFRobotLarkSensor dfRobotLarkSensor;
+NAU7802Sensor nau7802Sensor;
#define FAILED_STATE_SENSOR_READ_MULTIPLIER 10
#define DISPLAY_RECEIVEID_MEASUREMENTS_ON_SCREEN true
@@ -54,8 +75,8 @@ int32_t EnvironmentTelemetryModule::runOnce()
*/
// moduleConfig.telemetry.environment_measurement_enabled = 1;
- // moduleConfig.telemetry.environment_screen_enabled = 1;
- // moduleConfig.telemetry.environment_update_interval = 45;
+ // moduleConfig.telemetry.environment_screen_enabled = 1;
+ // moduleConfig.telemetry.environment_update_interval = 15;
if (!(moduleConfig.telemetry.environment_measurement_enabled || moduleConfig.telemetry.environment_screen_enabled)) {
// If this module is not enabled, and the user doesn't want the display screen don't waste any OSThread time on it
@@ -70,6 +91,8 @@ int32_t EnvironmentTelemetryModule::runOnce()
LOG_INFO("Environment Telemetry: Initializing\n");
// it's possible to have this module enabled, only for displaying values on the screen.
// therefore, we should only enable the sensor loop if measurement is also enabled
+ if (dfRobotLarkSensor.hasSensor())
+ result = dfRobotLarkSensor.runOnce();
if (bmp085Sensor.hasSensor())
result = bmp085Sensor.runOnce();
if (bmp280Sensor.hasSensor())
@@ -86,10 +109,26 @@ int32_t EnvironmentTelemetryModule::runOnce()
result = lps22hbSensor.runOnce();
if (sht31Sensor.hasSensor())
result = sht31Sensor.runOnce();
+ if (sht4xSensor.hasSensor())
+ result = sht4xSensor.runOnce();
if (ina219Sensor.hasSensor())
result = ina219Sensor.runOnce();
if (ina260Sensor.hasSensor())
result = ina260Sensor.runOnce();
+ if (veml7700Sensor.hasSensor())
+ result = veml7700Sensor.runOnce();
+ if (tsl2591Sensor.hasSensor())
+ result = tsl2591Sensor.runOnce();
+ if (opt3001Sensor.hasSensor())
+ result = opt3001Sensor.runOnce();
+ if (rcwl9620Sensor.hasSensor())
+ result = rcwl9620Sensor.runOnce();
+ if (aht10Sensor.hasSensor())
+ result = aht10Sensor.runOnce();
+ if (mlx90632Sensor.hasSensor())
+ result = mlx90632Sensor.runOnce();
+ if (nau7802Sensor.hasSensor())
+ result = nau7802Sensor.runOnce();
}
return result;
} else {
@@ -144,45 +183,62 @@ uint32_t GetTimeSinceMeshPacket(const meshtastic_MeshPacket *mp)
void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_LEFT);
- display->setFont(FONT_MEDIUM);
- display->drawString(x, y, "Environment");
+ display->setFont(FONT_SMALL);
+
if (lastMeasurementPacket == nullptr) {
- display->setFont(FONT_SMALL);
- display->drawString(x, y += fontHeight(FONT_MEDIUM), "No measurement");
+ // If there's no valid packet, display "Environment"
+ display->drawString(x, y, "Environment");
+ display->drawString(x, y += fontHeight(FONT_SMALL), "No measurement");
return;
}
+ // Decode the last measurement packet
meshtastic_Telemetry lastMeasurement;
-
uint32_t agoSecs = GetTimeSinceMeshPacket(lastMeasurementPacket);
const char *lastSender = getSenderShortName(*lastMeasurementPacket);
auto &p = lastMeasurementPacket->decoded;
if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) {
- display->setFont(FONT_SMALL);
- display->drawString(x, y += fontHeight(FONT_MEDIUM), "Measurement Error");
+ display->drawString(x, y, "Measurement Error");
LOG_ERROR("Unable to decode last packet");
return;
}
- display->setFont(FONT_SMALL);
+ // Display "Env. From: ..." on its own
+ display->drawString(x, y, "Env. From: " + String(lastSender) + "(" + String(agoSecs) + "s)");
+
String last_temp = String(lastMeasurement.variant.environment_metrics.temperature, 0) + "°C";
if (moduleConfig.telemetry.environment_display_fahrenheit) {
last_temp = String(CelsiusToFahrenheit(lastMeasurement.variant.environment_metrics.temperature), 0) + "°F";
}
- display->drawString(x, y += fontHeight(FONT_MEDIUM) - 2, "From: " + String(lastSender) + "(" + String(agoSecs) + "s)");
- display->drawString(x, y += fontHeight(FONT_SMALL) - 2,
+
+ // Continue with the remaining details
+ display->drawString(x, y += fontHeight(FONT_SMALL),
"Temp/Hum: " + last_temp + " / " +
String(lastMeasurement.variant.environment_metrics.relative_humidity, 0) + "%");
- if (lastMeasurement.variant.environment_metrics.barometric_pressure != 0)
+
+ if (lastMeasurement.variant.environment_metrics.barometric_pressure != 0) {
display->drawString(x, y += fontHeight(FONT_SMALL),
"Press: " + String(lastMeasurement.variant.environment_metrics.barometric_pressure, 0) + "hPA");
- if (lastMeasurement.variant.environment_metrics.voltage != 0)
+ }
+
+ if (lastMeasurement.variant.environment_metrics.voltage != 0) {
display->drawString(x, y += fontHeight(FONT_SMALL),
"Volt/Cur: " + String(lastMeasurement.variant.environment_metrics.voltage, 0) + "V / " +
String(lastMeasurement.variant.environment_metrics.current, 0) + "mA");
- if (lastMeasurement.variant.environment_metrics.iaq != 0)
+ }
+
+ if (lastMeasurement.variant.environment_metrics.iaq != 0) {
display->drawString(x, y += fontHeight(FONT_SMALL), "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq));
+ }
+
+ if (lastMeasurement.variant.environment_metrics.distance != 0)
+ display->drawString(x, y += fontHeight(FONT_SMALL),
+ "Water Level: " + String(lastMeasurement.variant.environment_metrics.distance, 0) + "mm");
+
+ if (lastMeasurement.variant.environment_metrics.weight != 0)
+ display->drawString(x, y += fontHeight(FONT_SMALL),
+ "Weight: " + String(lastMeasurement.variant.environment_metrics.weight, 0) + "kg");
}
bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
@@ -192,10 +248,17 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac
const char *sender = getSenderShortName(mp);
LOG_INFO("(Received from %s): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, "
- "temperature=%f, voltage=%f\n",
+ "temperature=%f\n",
sender, t->variant.environment_metrics.barometric_pressure, t->variant.environment_metrics.current,
t->variant.environment_metrics.gas_resistance, t->variant.environment_metrics.relative_humidity,
- t->variant.environment_metrics.temperature, t->variant.environment_metrics.voltage);
+ t->variant.environment_metrics.temperature);
+ LOG_INFO("(Received from %s): voltage=%f, IAQ=%d, distance=%f, lux=%f\n", sender, t->variant.environment_metrics.voltage,
+ t->variant.environment_metrics.iaq, t->variant.environment_metrics.distance, t->variant.environment_metrics.lux);
+
+ LOG_INFO("(Received from %s): wind speed=%fm/s, direction=%d degrees, weight=%fkg\n", sender,
+ t->variant.environment_metrics.wind_speed, t->variant.environment_metrics.wind_direction,
+ t->variant.environment_metrics.weight);
+
#endif
// release previous packet before occupying a new spot
if (lastMeasurementPacket != nullptr)
@@ -209,45 +272,105 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac
bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
{
- meshtastic_Telemetry m;
- bool valid = false;
+ meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
+ bool valid = true;
+ bool hasSensor = false;
m.time = getTime();
m.which_variant = meshtastic_Telemetry_environment_metrics_tag;
- m.variant.environment_metrics.barometric_pressure = 0;
- m.variant.environment_metrics.current = 0;
- m.variant.environment_metrics.gas_resistance = 0;
- m.variant.environment_metrics.relative_humidity = 0;
- m.variant.environment_metrics.temperature = 0;
- m.variant.environment_metrics.voltage = 0;
+ if (dfRobotLarkSensor.hasSensor()) {
+ valid = valid && dfRobotLarkSensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (sht31Sensor.hasSensor()) {
+ valid = valid && sht31Sensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (lps22hbSensor.hasSensor()) {
+ valid = valid && lps22hbSensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (shtc3Sensor.hasSensor()) {
+ valid = valid && shtc3Sensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (bmp085Sensor.hasSensor()) {
+ valid = valid && bmp085Sensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (bmp280Sensor.hasSensor()) {
+ valid = valid && bmp280Sensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (bme280Sensor.hasSensor()) {
+ valid = valid && bme280Sensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (bme680Sensor.hasSensor()) {
+ valid = valid && bme680Sensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (mcp9808Sensor.hasSensor()) {
+ valid = valid && mcp9808Sensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (ina219Sensor.hasSensor()) {
+ valid = valid && ina219Sensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (ina260Sensor.hasSensor()) {
+ valid = valid && ina260Sensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (veml7700Sensor.hasSensor()) {
+ valid = valid && veml7700Sensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (tsl2591Sensor.hasSensor()) {
+ valid = valid && tsl2591Sensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (opt3001Sensor.hasSensor()) {
+ valid = valid && opt3001Sensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (mlx90632Sensor.hasSensor()) {
+ valid = valid && mlx90632Sensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (rcwl9620Sensor.hasSensor()) {
+ valid = valid && rcwl9620Sensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (nau7802Sensor.hasSensor()) {
+ valid = valid && nau7802Sensor.getMetrics(&m);
+ hasSensor = true;
+ }
+ if (aht10Sensor.hasSensor()) {
+ if (!bmp280Sensor.hasSensor()) {
+ valid = valid && aht10Sensor.getMetrics(&m);
+ hasSensor = true;
+ } else {
+ // prefer bmp280 temp if both sensors are present, fetch only humidity
+ meshtastic_Telemetry m_ahtx = meshtastic_Telemetry_init_zero;
+ LOG_INFO("AHTX0+BMP280 module detected: using temp from BMP280 and humy from AHTX0\n");
+ aht10Sensor.getMetrics(&m_ahtx);
+ m.variant.environment_metrics.relative_humidity = m_ahtx.variant.environment_metrics.relative_humidity;
+ }
+ }
- if (sht31Sensor.hasSensor())
- valid = sht31Sensor.getMetrics(&m);
- if (lps22hbSensor.hasSensor())
- valid = lps22hbSensor.getMetrics(&m);
- if (shtc3Sensor.hasSensor())
- valid = shtc3Sensor.getMetrics(&m);
- if (bmp085Sensor.hasSensor())
- valid = bmp085Sensor.getMetrics(&m);
- if (bmp280Sensor.hasSensor())
- valid = bmp280Sensor.getMetrics(&m);
- if (bme280Sensor.hasSensor())
- valid = bme280Sensor.getMetrics(&m);
- if (bme680Sensor.hasSensor())
- valid = bme680Sensor.getMetrics(&m);
- if (mcp9808Sensor.hasSensor())
- valid = mcp9808Sensor.getMetrics(&m);
- if (ina219Sensor.hasSensor())
- valid = ina219Sensor.getMetrics(&m);
- if (ina260Sensor.hasSensor())
- valid = ina260Sensor.getMetrics(&m);
+ valid = valid && hasSensor;
if (valid) {
- LOG_INFO("(Sending): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f, "
- "voltage=%f\n",
+ LOG_INFO("(Sending): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, temperature=%f\n",
m.variant.environment_metrics.barometric_pressure, m.variant.environment_metrics.current,
m.variant.environment_metrics.gas_resistance, m.variant.environment_metrics.relative_humidity,
- m.variant.environment_metrics.temperature, m.variant.environment_metrics.voltage);
+ m.variant.environment_metrics.temperature);
+ LOG_INFO("(Sending): voltage=%f, IAQ=%d, distance=%f, lux=%f\n", m.variant.environment_metrics.voltage,
+ m.variant.environment_metrics.iaq, m.variant.environment_metrics.distance, m.variant.environment_metrics.lux);
+
+ LOG_INFO("(Sending): wind speed=%fm/s, direction=%d degrees, weight=%fkg\n", m.variant.environment_metrics.wind_speed,
+ m.variant.environment_metrics.wind_direction, m.variant.environment_metrics.weight);
sensor_read_error_count = 0;
@@ -278,4 +401,104 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
}
}
return valid;
-}
\ No newline at end of file
+}
+
+AdminMessageHandleResult EnvironmentTelemetryModule::handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
+ meshtastic_AdminMessage *request,
+ meshtastic_AdminMessage *response)
+{
+ AdminMessageHandleResult result = AdminMessageHandleResult::NOT_HANDLED;
+ if (dfRobotLarkSensor.hasSensor()) {
+ result = dfRobotLarkSensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (sht31Sensor.hasSensor()) {
+ result = sht31Sensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (lps22hbSensor.hasSensor()) {
+ result = lps22hbSensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (shtc3Sensor.hasSensor()) {
+ result = shtc3Sensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (bmp085Sensor.hasSensor()) {
+ result = bmp085Sensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (bmp280Sensor.hasSensor()) {
+ result = bmp280Sensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (bme280Sensor.hasSensor()) {
+ result = bme280Sensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (bme680Sensor.hasSensor()) {
+ result = bme680Sensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (mcp9808Sensor.hasSensor()) {
+ result = mcp9808Sensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (ina219Sensor.hasSensor()) {
+ result = ina219Sensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (ina260Sensor.hasSensor()) {
+ result = ina260Sensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (veml7700Sensor.hasSensor()) {
+ result = veml7700Sensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (tsl2591Sensor.hasSensor()) {
+ result = tsl2591Sensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (opt3001Sensor.hasSensor()) {
+ result = opt3001Sensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (mlx90632Sensor.hasSensor()) {
+ result = mlx90632Sensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (rcwl9620Sensor.hasSensor()) {
+ result = rcwl9620Sensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (nau7802Sensor.hasSensor()) {
+ result = nau7802Sensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ if (aht10Sensor.hasSensor()) {
+ result = aht10Sensor.handleAdminMessage(mp, request, response);
+ if (result != AdminMessageHandleResult::NOT_HANDLED)
+ return result;
+ }
+ return result;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/EnvironmentTelemetry.h b/src/modules/Telemetry/EnvironmentTelemetry.h
index d6cd2137f..ca150347e 100644
--- a/src/modules/Telemetry/EnvironmentTelemetry.h
+++ b/src/modules/Telemetry/EnvironmentTelemetry.h
@@ -1,3 +1,7 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#pragma once
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "NodeDB.h"
@@ -33,6 +37,10 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu
*/
bool sendTelemetry(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
+ virtual AdminMessageHandleResult handleAdminMessageForModule(const meshtastic_MeshPacket &mp,
+ meshtastic_AdminMessage *request,
+ meshtastic_AdminMessage *response) override;
+
private:
float CelsiusToFahrenheit(float c);
bool firstTime = 1;
@@ -42,3 +50,5 @@ class EnvironmentTelemetryModule : private concurrency::OSThread, public Protobu
uint32_t lastSentToPhone = 0;
uint32_t sensor_read_error_count = 0;
};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp
index 713f6aacb..826de8a4a 100644
--- a/src/modules/Telemetry/PowerTelemetry.cpp
+++ b/src/modules/Telemetry/PowerTelemetry.cpp
@@ -1,12 +1,15 @@
-#include "PowerTelemetry.h"
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
+#include "PowerTelemetry.h"
#include "RTC.h"
#include "Router.h"
-#include "configuration.h"
#include "main.h"
#include "power.h"
#include "sleep.h"
@@ -162,7 +165,7 @@ bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &m
bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
{
- meshtastic_Telemetry m;
+ meshtastic_Telemetry m = meshtastic_Telemetry_init_zero;
bool valid = false;
m.time = getTime();
m.which_variant = meshtastic_Telemetry_power_metrics_tag;
@@ -217,4 +220,6 @@ bool PowerTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
}
}
return valid;
-}
\ No newline at end of file
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/PowerTelemetry.h b/src/modules/Telemetry/PowerTelemetry.h
index fc5b98875..3d6b686f2 100644
--- a/src/modules/Telemetry/PowerTelemetry.h
+++ b/src/modules/Telemetry/PowerTelemetry.h
@@ -1,4 +1,9 @@
#pragma once
+
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "NodeDB.h"
#include "ProtobufModule.h"
@@ -41,3 +46,5 @@ class PowerTelemetryModule : private concurrency::OSThread, public ProtobufModul
uint32_t lastSentToPhone = 0;
uint32_t sensor_read_error_count = 0;
};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/AHT10.cpp b/src/modules/Telemetry/Sensor/AHT10.cpp
new file mode 100644
index 000000000..a5212b39b
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/AHT10.cpp
@@ -0,0 +1,41 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "AHT10.h"
+#include "TelemetrySensor.h"
+
+#include
+#include
+
+AHT10Sensor::AHT10Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_AHT10, "AHT10") {}
+
+int32_t AHT10Sensor::runOnce()
+{
+ LOG_INFO("Init sensor: %s\n", sensorName);
+ if (!hasSensor()) {
+ return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ }
+ aht10 = Adafruit_AHTX0();
+ status = aht10.begin(nodeTelemetrySensorsMap[sensorType].second, 0, nodeTelemetrySensorsMap[sensorType].first);
+
+ return initI2CSensor();
+}
+
+void AHT10Sensor::setup() {}
+
+bool AHT10Sensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+ LOG_DEBUG("AHT10Sensor::getMetrics\n");
+
+ sensors_event_t humidity, temp;
+ aht10.getEvent(&humidity, &temp);
+
+ measurement->variant.environment_metrics.temperature = temp.temperature;
+ measurement->variant.environment_metrics.relative_humidity = humidity.relative_humidity;
+
+ return true;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/AHT10.h b/src/modules/Telemetry/Sensor/AHT10.h
new file mode 100644
index 000000000..d9a133402
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/AHT10.h
@@ -0,0 +1,23 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include
+
+class AHT10Sensor : public TelemetrySensor
+{
+ private:
+ Adafruit_AHTX0 aht10;
+
+ protected:
+ virtual void setup() override;
+
+ public:
+ AHT10Sensor();
+ virtual int32_t runOnce() override;
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/BME280Sensor.cpp b/src/modules/Telemetry/Sensor/BME280Sensor.cpp
index a30614123..aea6f2c3d 100644
--- a/src/modules/Telemetry/Sensor/BME280Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/BME280Sensor.cpp
@@ -1,7 +1,10 @@
-#include "BME280Sensor.h"
-#include "../mesh/generated/meshtastic/telemetry.pb.h"
-#include "TelemetrySensor.h"
#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "BME280Sensor.h"
+#include "TelemetrySensor.h"
#include
#include
@@ -35,4 +38,5 @@ bool BME280Sensor::getMetrics(meshtastic_Telemetry *measurement)
measurement->variant.environment_metrics.barometric_pressure = bme280.readPressure() / 100.0F;
return true;
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/BME280Sensor.h b/src/modules/Telemetry/Sensor/BME280Sensor.h
index 2034c0a82..eb78f79f7 100644
--- a/src/modules/Telemetry/Sensor/BME280Sensor.h
+++ b/src/modules/Telemetry/Sensor/BME280Sensor.h
@@ -1,3 +1,7 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include
@@ -14,4 +18,6 @@ class BME280Sensor : public TelemetrySensor
BME280Sensor();
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
-};
\ No newline at end of file
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp
index e1222bba4..411cbbf69 100644
--- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp
@@ -1,8 +1,11 @@
-#include "BME680Sensor.h"
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "BME680Sensor.h"
#include "FSCommon.h"
#include "TelemetrySensor.h"
-#include "configuration.h"
BME680Sensor::BME680Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_BME680, "BME680") {}
@@ -25,7 +28,7 @@ int32_t BME680Sensor::runOnce()
if (bme680.status == BSEC_OK) {
status = 1;
- if (!bme680.setConfig(bsec_config_iaq)) {
+ if (!bme680.setConfig(bsec_config)) {
checkStatus("setConfig");
status = 0;
}
@@ -54,7 +57,7 @@ bool BME680Sensor::getMetrics(meshtastic_Telemetry *measurement)
measurement->variant.environment_metrics.temperature = bme680.getData(BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE).signal;
measurement->variant.environment_metrics.relative_humidity =
bme680.getData(BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY).signal;
- measurement->variant.environment_metrics.barometric_pressure = bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal / 100.0F;
+ measurement->variant.environment_metrics.barometric_pressure = bme680.getData(BSEC_OUTPUT_RAW_PRESSURE).signal;
measurement->variant.environment_metrics.gas_resistance = bme680.getData(BSEC_OUTPUT_RAW_GAS).signal / 1000.0;
// Check if we need to save state to filesystem (every STATE_SAVE_PERIOD ms)
measurement->variant.environment_metrics.iaq = bme680.getData(BSEC_OUTPUT_IAQ).signal;
@@ -134,3 +137,5 @@ void BME680Sensor::checkStatus(String functionName)
else if (bme680.sensor.status > BME68X_OK)
LOG_WARN("%s BME68X code: %s\n", functionName.c_str(), String(bme680.sensor.status).c_str());
}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.h b/src/modules/Telemetry/Sensor/BME680Sensor.h
index 4b7f84cf0..a5d2b5a48 100644
--- a/src/modules/Telemetry/Sensor/BME680Sensor.h
+++ b/src/modules/Telemetry/Sensor/BME680Sensor.h
@@ -1,10 +1,16 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include
#define STATE_SAVE_PERIOD UINT32_C(360 * 60 * 1000) // That's 6 hours worth of millis()
-#include "bme680_iaq_33v_3s_4d/bsec_iaq.h"
+const uint8_t bsec_config[] = {
+#include "config/bme680/bme680_iaq_33v_3s_4d/bsec_iaq.txt"
+};
class BME680Sensor : public TelemetrySensor
{
@@ -35,4 +41,6 @@ class BME680Sensor : public TelemetrySensor
int32_t runTrigger();
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
-};
\ No newline at end of file
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp
index b0991749b..0c4d0b5ca 100644
--- a/src/modules/Telemetry/Sensor/BMP085Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/BMP085Sensor.cpp
@@ -1,7 +1,10 @@
-#include "BMP085Sensor.h"
-#include "../mesh/generated/meshtastic/telemetry.pb.h"
-#include "TelemetrySensor.h"
#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "BMP085Sensor.h"
+#include "TelemetrySensor.h"
#include
#include
@@ -29,3 +32,5 @@ bool BMP085Sensor::getMetrics(meshtastic_Telemetry *measurement)
return true;
}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/BMP085Sensor.h b/src/modules/Telemetry/Sensor/BMP085Sensor.h
index c4a9479b9..4ba8c5af1 100644
--- a/src/modules/Telemetry/Sensor/BMP085Sensor.h
+++ b/src/modules/Telemetry/Sensor/BMP085Sensor.h
@@ -1,3 +1,7 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include
@@ -14,4 +18,6 @@ class BMP085Sensor : public TelemetrySensor
BMP085Sensor();
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
-};
\ No newline at end of file
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/BMP280Sensor.cpp b/src/modules/Telemetry/Sensor/BMP280Sensor.cpp
index 408532388..8d0e4c180 100644
--- a/src/modules/Telemetry/Sensor/BMP280Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/BMP280Sensor.cpp
@@ -1,7 +1,10 @@
-#include "BMP280Sensor.h"
-#include "../mesh/generated/meshtastic/telemetry.pb.h"
-#include "TelemetrySensor.h"
#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "BMP280Sensor.h"
+#include "TelemetrySensor.h"
#include
#include
@@ -35,3 +38,5 @@ bool BMP280Sensor::getMetrics(meshtastic_Telemetry *measurement)
return true;
}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/BMP280Sensor.h b/src/modules/Telemetry/Sensor/BMP280Sensor.h
index 48581df8f..da85fdc1d 100644
--- a/src/modules/Telemetry/Sensor/BMP280Sensor.h
+++ b/src/modules/Telemetry/Sensor/BMP280Sensor.h
@@ -1,3 +1,7 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include
@@ -14,4 +18,6 @@ class BMP280Sensor : public TelemetrySensor
BMP280Sensor();
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
-};
\ No newline at end of file
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp
new file mode 100644
index 000000000..830552023
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.cpp
@@ -0,0 +1,53 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "DFRobotLarkSensor.h"
+#include "TelemetrySensor.h"
+#include "gps/GeoCoord.h"
+#include
+#include
+
+DFRobotLarkSensor::DFRobotLarkSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_DFROBOT_LARK, "DFROBOT_LARK") {}
+
+int32_t DFRobotLarkSensor::runOnce()
+{
+ LOG_INFO("Init sensor: %s\n", sensorName);
+ if (!hasSensor()) {
+ return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ }
+
+ lark = DFRobot_LarkWeatherStation_I2C(nodeTelemetrySensorsMap[sensorType].first, nodeTelemetrySensorsMap[sensorType].second);
+
+ if (lark.begin() == 0) // DFRobotLarkSensor init
+ {
+ LOG_DEBUG("DFRobotLarkSensor Init Succeed\n");
+ status = true;
+ } else {
+ LOG_ERROR("DFRobotLarkSensor Init Failed\n");
+ status = false;
+ }
+ return initI2CSensor();
+}
+
+void DFRobotLarkSensor::setup() {}
+
+bool DFRobotLarkSensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+ measurement->variant.environment_metrics.temperature = lark.getValue("Temp").toFloat();
+ measurement->variant.environment_metrics.relative_humidity = lark.getValue("Humi").toFloat();
+ measurement->variant.environment_metrics.wind_speed = lark.getValue("Speed").toFloat();
+ measurement->variant.environment_metrics.wind_direction = GeoCoord::bearingToDegrees(lark.getValue("Dir").c_str());
+ measurement->variant.environment_metrics.barometric_pressure = lark.getValue("Pressure").toFloat();
+
+ LOG_INFO("Temperature: %f\n", measurement->variant.environment_metrics.temperature);
+ LOG_INFO("Humidity: %f\n", measurement->variant.environment_metrics.relative_humidity);
+ LOG_INFO("Wind Speed: %f\n", measurement->variant.environment_metrics.wind_speed);
+ LOG_INFO("Wind Direction: %d\n", measurement->variant.environment_metrics.wind_direction);
+ LOG_INFO("Barometric Pressure: %f\n", measurement->variant.environment_metrics.barometric_pressure);
+
+ return true;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h
new file mode 100644
index 000000000..7a988e84a
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/DFRobotLarkSensor.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#ifndef _MT_DFROBOTLARKSENSOR_H
+#define _MT_DFROBOTLARKSENSOR_H
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include
+#include
+
+class DFRobotLarkSensor : public TelemetrySensor
+{
+ private:
+ DFRobot_LarkWeatherStation_I2C lark = DFRobot_LarkWeatherStation_I2C();
+
+ protected:
+ virtual void setup() override;
+
+ public:
+ DFRobotLarkSensor();
+ virtual int32_t runOnce() override;
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+};
+
+#endif
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/INA219Sensor.cpp b/src/modules/Telemetry/Sensor/INA219Sensor.cpp
index ecb564368..040e59575 100644
--- a/src/modules/Telemetry/Sensor/INA219Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/INA219Sensor.cpp
@@ -1,7 +1,10 @@
-#include "INA219Sensor.h"
-#include "../mesh/generated/meshtastic/telemetry.pb.h"
-#include "TelemetrySensor.h"
#include "configuration.h"
+
+#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "INA219Sensor.h"
+#include "TelemetrySensor.h"
#include
#ifndef INA219_MULTIPLIER
@@ -37,4 +40,6 @@ bool INA219Sensor::getMetrics(meshtastic_Telemetry *measurement)
uint16_t INA219Sensor::getBusVoltageMv()
{
return lround(ina219.getBusVoltage_V() * 1000);
-}
\ No newline at end of file
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/INA219Sensor.h b/src/modules/Telemetry/Sensor/INA219Sensor.h
index 76f4613db..9dded067b 100644
--- a/src/modules/Telemetry/Sensor/INA219Sensor.h
+++ b/src/modules/Telemetry/Sensor/INA219Sensor.h
@@ -1,3 +1,7 @@
+#include "configuration.h"
+
+#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include "VoltageSensor.h"
@@ -16,4 +20,6 @@ class INA219Sensor : public TelemetrySensor, VoltageSensor
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual uint16_t getBusVoltageMv() override;
-};
\ No newline at end of file
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/INA260Sensor.cpp b/src/modules/Telemetry/Sensor/INA260Sensor.cpp
index 89b7580d2..f156a9aba 100644
--- a/src/modules/Telemetry/Sensor/INA260Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/INA260Sensor.cpp
@@ -1,7 +1,10 @@
-#include "INA260Sensor.h"
-#include "../mesh/generated/meshtastic/telemetry.pb.h"
-#include "TelemetrySensor.h"
#include "configuration.h"
+
+#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "INA260Sensor.h"
+#include "TelemetrySensor.h"
#include
INA260Sensor::INA260Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_INA260, "INA260") {}
@@ -32,4 +35,6 @@ bool INA260Sensor::getMetrics(meshtastic_Telemetry *measurement)
uint16_t INA260Sensor::getBusVoltageMv()
{
return lround(ina260.readBusVoltage());
-}
\ No newline at end of file
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/INA260Sensor.h b/src/modules/Telemetry/Sensor/INA260Sensor.h
index 28e8944bf..f436b8f38 100644
--- a/src/modules/Telemetry/Sensor/INA260Sensor.h
+++ b/src/modules/Telemetry/Sensor/INA260Sensor.h
@@ -1,3 +1,7 @@
+#include "configuration.h"
+
+#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include "VoltageSensor.h"
@@ -16,4 +20,6 @@ class INA260Sensor : public TelemetrySensor, VoltageSensor
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual uint16_t getBusVoltageMv() override;
-};
\ No newline at end of file
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp
index 3269ba47a..ea2cb4ea8 100644
--- a/src/modules/Telemetry/Sensor/INA3221Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/INA3221Sensor.cpp
@@ -1,7 +1,10 @@
-#include "INA3221Sensor.h"
-#include "../mesh/generated/meshtastic/telemetry.pb.h"
-#include "TelemetrySensor.h"
#include "configuration.h"
+
+#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "INA3221Sensor.h"
+#include "TelemetrySensor.h"
#include
INA3221Sensor::INA3221Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_INA3221, "INA3221"){};
@@ -41,4 +44,6 @@ bool INA3221Sensor::getMetrics(meshtastic_Telemetry *measurement)
uint16_t INA3221Sensor::getBusVoltageMv()
{
return lround(ina3221.getVoltage(INA3221_CH1) * 1000);
-}
\ No newline at end of file
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/INA3221Sensor.h b/src/modules/Telemetry/Sensor/INA3221Sensor.h
index 4c82fc34d..3b8e382ee 100644
--- a/src/modules/Telemetry/Sensor/INA3221Sensor.h
+++ b/src/modules/Telemetry/Sensor/INA3221Sensor.h
@@ -1,3 +1,7 @@
+#include "configuration.h"
+
+#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include "VoltageSensor.h"
@@ -16,4 +20,6 @@ class INA3221Sensor : public TelemetrySensor, VoltageSensor
int32_t runOnce() override;
bool getMetrics(meshtastic_Telemetry *measurement) override;
virtual uint16_t getBusVoltageMv() override;
-};
\ No newline at end of file
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp b/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp
index 6e30113cd..c3c994cfa 100644
--- a/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp
+++ b/src/modules/Telemetry/Sensor/LPS22HBSensor.cpp
@@ -1,7 +1,10 @@
-#include "LPS22HBSensor.h"
-#include "../mesh/generated/meshtastic/telemetry.pb.h"
-#include "TelemetrySensor.h"
#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "LPS22HBSensor.h"
+#include "TelemetrySensor.h"
#include
#include
@@ -32,4 +35,6 @@ bool LPS22HBSensor::getMetrics(meshtastic_Telemetry *measurement)
measurement->variant.environment_metrics.barometric_pressure = pressure.pressure;
return true;
-}
\ No newline at end of file
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/LPS22HBSensor.h b/src/modules/Telemetry/Sensor/LPS22HBSensor.h
index 5b86539b1..955f2a1e5 100644
--- a/src/modules/Telemetry/Sensor/LPS22HBSensor.h
+++ b/src/modules/Telemetry/Sensor/LPS22HBSensor.h
@@ -1,3 +1,7 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include
@@ -15,4 +19,6 @@ class LPS22HBSensor : public TelemetrySensor
LPS22HBSensor();
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
-};
\ No newline at end of file
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp b/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp
index c1d9bfa71..b01a19291 100644
--- a/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/MCP9808Sensor.cpp
@@ -1,7 +1,10 @@
-#include "MCP9808Sensor.h"
-#include "../mesh/generated/meshtastic/telemetry.pb.h"
-#include "TelemetrySensor.h"
#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "MCP9808Sensor.h"
+#include "TelemetrySensor.h"
#include
MCP9808Sensor::MCP9808Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MCP9808, "MCP9808") {}
@@ -26,4 +29,6 @@ bool MCP9808Sensor::getMetrics(meshtastic_Telemetry *measurement)
LOG_DEBUG("MCP9808Sensor::getMetrics\n");
measurement->variant.environment_metrics.temperature = mcp9808.readTempC();
return true;
-}
\ No newline at end of file
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/MCP9808Sensor.h b/src/modules/Telemetry/Sensor/MCP9808Sensor.h
index c1029f8a7..05bdabf3f 100644
--- a/src/modules/Telemetry/Sensor/MCP9808Sensor.h
+++ b/src/modules/Telemetry/Sensor/MCP9808Sensor.h
@@ -1,3 +1,7 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include
@@ -14,4 +18,6 @@ class MCP9808Sensor : public TelemetrySensor
MCP9808Sensor();
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
-};
\ No newline at end of file
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp b/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp
new file mode 100644
index 000000000..4c459c365
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/MLX90632Sensor.cpp
@@ -0,0 +1,40 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "MLX90632Sensor.h"
+#include "TelemetrySensor.h"
+
+MLX90632Sensor::MLX90632Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_MLX90632, "MLX90632") {}
+
+int32_t MLX90632Sensor::runOnce()
+{
+ LOG_INFO("Init sensor: %s\n", sensorName);
+ if (!hasSensor()) {
+ return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ }
+
+ MLX90632::status returnError;
+ if (mlx.begin(nodeTelemetrySensorsMap[sensorType].first, *nodeTelemetrySensorsMap[sensorType].second, returnError) ==
+ true) // MLX90632 init
+ {
+ LOG_DEBUG("MLX90632 Init Succeed\n");
+ status = true;
+ } else {
+ LOG_ERROR("MLX90632 Init Failed\n");
+ status = false;
+ }
+ return initI2CSensor();
+}
+
+void MLX90632Sensor::setup() {}
+
+bool MLX90632Sensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+ measurement->variant.environment_metrics.temperature = mlx.getObjectTemp(); // Get the object temperature in Fahrenheit
+
+ return true;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/MLX90632Sensor.h b/src/modules/Telemetry/Sensor/MLX90632Sensor.h
new file mode 100644
index 000000000..7b36c44cd
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/MLX90632Sensor.h
@@ -0,0 +1,23 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include
+
+class MLX90632Sensor : public TelemetrySensor
+{
+ private:
+ MLX90632 mlx = MLX90632();
+
+ protected:
+ virtual void setup() override;
+
+ public:
+ MLX90632Sensor();
+ virtual int32_t runOnce() override;
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp
new file mode 100644
index 000000000..39ac4b08b
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/NAU7802Sensor.cpp
@@ -0,0 +1,143 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "FSCommon.h"
+#include "NAU7802Sensor.h"
+#include "TelemetrySensor.h"
+#include
+#include
+
+meshtastic_Nau7802Config nau7802config = meshtastic_Nau7802Config_init_zero;
+
+NAU7802Sensor::NAU7802Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_NAU7802, "NAU7802") {}
+
+int32_t NAU7802Sensor::runOnce()
+{
+ LOG_INFO("Init sensor: %s\n", sensorName);
+ if (!hasSensor()) {
+ return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ }
+ status = nau7802.begin(*nodeTelemetrySensorsMap[sensorType].second);
+ nau7802.setSampleRate(NAU7802_SPS_320);
+ if (!loadCalibrationData()) {
+ LOG_ERROR("Failed to load calibration data\n");
+ }
+ nau7802.calibrateAFE();
+ LOG_INFO("Offset: %d, Calibration factor: %.2f\n", nau7802.getZeroOffset(), nau7802.getCalibrationFactor());
+ return initI2CSensor();
+}
+
+void NAU7802Sensor::setup() {}
+
+bool NAU7802Sensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+ LOG_DEBUG("NAU7802Sensor::getMetrics\n");
+ nau7802.powerUp();
+ // Wait for the sensor to become ready for one second max
+ uint32_t start = millis();
+ while (!nau7802.available()) {
+ delay(100);
+ if (millis() - start > 1000) {
+ nau7802.powerDown();
+ return false;
+ }
+ }
+ // Check if we have correct calibration values after powerup
+ LOG_DEBUG("Offset: %d, Calibration factor: %.2f\n", nau7802.getZeroOffset(), nau7802.getCalibrationFactor());
+ measurement->variant.environment_metrics.weight = nau7802.getWeight() / 1000; // sample is in kg
+ nau7802.powerDown();
+ return true;
+}
+
+void NAU7802Sensor::calibrate(float weight)
+{
+ nau7802.calculateCalibrationFactor(weight * 1000, 64); // internal sample is in grams
+ if (!saveCalibrationData()) {
+ LOG_WARN("Failed to save calibration data\n");
+ }
+ LOG_INFO("Offset: %d, Calibration factor: %.2f\n", nau7802.getZeroOffset(), nau7802.getCalibrationFactor());
+}
+
+AdminMessageHandleResult NAU7802Sensor::handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request,
+ meshtastic_AdminMessage *response)
+{
+ AdminMessageHandleResult result;
+
+ switch (request->which_payload_variant) {
+ case meshtastic_AdminMessage_set_scale_tag:
+ if (request->set_scale == 0) {
+ this->tare();
+ LOG_DEBUG("Client requested to tare scale\n");
+ } else {
+ this->calibrate(request->set_scale);
+ LOG_DEBUG("Client requested to calibrate to %d kg\n", request->set_scale);
+ }
+ result = AdminMessageHandleResult::HANDLED;
+ break;
+
+ default:
+ result = AdminMessageHandleResult::NOT_HANDLED;
+ }
+
+ return result;
+}
+
+void NAU7802Sensor::tare()
+{
+ nau7802.calculateZeroOffset(64);
+ if (!saveCalibrationData()) {
+ LOG_WARN("Failed to save calibration data\n");
+ }
+ LOG_INFO("Offset: %d, Calibration factor: %.2f\n", nau7802.getZeroOffset(), nau7802.getCalibrationFactor());
+}
+
+bool NAU7802Sensor::saveCalibrationData()
+{
+ if (FSCom.exists(nau7802ConfigFileName) && !FSCom.remove(nau7802ConfigFileName)) {
+ LOG_WARN("Can't remove old state file\n");
+ }
+ auto file = FSCom.open(nau7802ConfigFileName, FILE_O_WRITE);
+ nau7802config.zeroOffset = nau7802.getZeroOffset();
+ nau7802config.calibrationFactor = nau7802.getCalibrationFactor();
+ bool okay = false;
+ if (file) {
+ LOG_INFO("%s state write to %s.\n", sensorName, nau7802ConfigFileName);
+ pb_ostream_t stream = {&writecb, &file, meshtastic_Nau7802Config_size};
+
+ if (!pb_encode(&stream, &meshtastic_Nau7802Config_msg, &nau7802config)) {
+ LOG_ERROR("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream));
+ } else {
+ okay = true;
+ }
+ file.flush();
+ file.close();
+ } else {
+ LOG_INFO("Can't write %s state (File: %s).\n", sensorName, nau7802ConfigFileName);
+ }
+ return okay;
+}
+
+bool NAU7802Sensor::loadCalibrationData()
+{
+ auto file = FSCom.open(nau7802ConfigFileName, FILE_O_READ);
+ bool okay = false;
+ if (file) {
+ LOG_INFO("%s state read from %s.\n", sensorName, nau7802ConfigFileName);
+ pb_istream_t stream = {&readcb, &file, meshtastic_Nau7802Config_size};
+ if (!pb_decode(&stream, &meshtastic_Nau7802Config_msg, &nau7802config)) {
+ LOG_ERROR("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
+ } else {
+ nau7802.setZeroOffset(nau7802config.zeroOffset);
+ nau7802.setCalibrationFactor(nau7802config.calibrationFactor);
+ okay = true;
+ }
+ file.close();
+ } else {
+ LOG_INFO("No %s state found (File: %s).\n", sensorName, nau7802ConfigFileName);
+ }
+ return okay;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/NAU7802Sensor.h b/src/modules/Telemetry/Sensor/NAU7802Sensor.h
new file mode 100644
index 000000000..c53a3b31a
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/NAU7802Sensor.h
@@ -0,0 +1,31 @@
+#include "MeshModule.h"
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include
+
+class NAU7802Sensor : public TelemetrySensor
+{
+ private:
+ NAU7802 nau7802;
+
+ protected:
+ virtual void setup() override;
+ const char *nau7802ConfigFileName = "/prefs/nau7802.dat";
+ bool saveCalibrationData();
+ bool loadCalibrationData();
+
+ public:
+ NAU7802Sensor();
+ virtual int32_t runOnce() override;
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+ void tare();
+ void calibrate(float weight);
+ AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request,
+ meshtastic_AdminMessage *response) override;
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp b/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp
new file mode 100644
index 000000000..d0e38bf88
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/OPT3001Sensor.cpp
@@ -0,0 +1,49 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "OPT3001Sensor.h"
+#include "TelemetrySensor.h"
+#include
+
+OPT3001Sensor::OPT3001Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_OPT3001, "OPT3001") {}
+
+int32_t OPT3001Sensor::runOnce()
+{
+ LOG_INFO("Init sensor: %s\n", sensorName);
+ if (!hasSensor()) {
+ return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ }
+ auto errorCode = opt3001.begin(nodeTelemetrySensorsMap[sensorType].first);
+ status = errorCode == NO_ERROR;
+
+ return initI2CSensor();
+}
+
+void OPT3001Sensor::setup()
+{
+ OPT3001_Config newConfig;
+
+ newConfig.RangeNumber = B1100;
+ newConfig.ConvertionTime = B0;
+ newConfig.Latch = B1;
+ newConfig.ModeOfConversionOperation = B11;
+
+ OPT3001_ErrorCode errorConfig = opt3001.writeConfig(newConfig);
+ if (errorConfig != NO_ERROR) {
+ LOG_ERROR("OPT3001 configuration error #%d", errorConfig);
+ }
+}
+
+bool OPT3001Sensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+ OPT3001 result = opt3001.readResult();
+
+ measurement->variant.environment_metrics.lux = result.lux;
+ LOG_INFO("Lux: %f\n", measurement->variant.environment_metrics.lux);
+
+ return true;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/OPT3001Sensor.h b/src/modules/Telemetry/Sensor/OPT3001Sensor.h
new file mode 100644
index 000000000..2ac149319
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/OPT3001Sensor.h
@@ -0,0 +1,24 @@
+#pragma once
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include
+
+class OPT3001Sensor : public TelemetrySensor
+{
+ private:
+ ClosedCube_OPT3001 opt3001;
+
+ protected:
+ virtual void setup() override;
+
+ public:
+ OPT3001Sensor();
+ virtual int32_t runOnce() override;
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp
new file mode 100644
index 000000000..49a509d38
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.cpp
@@ -0,0 +1,65 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "RCWL9620Sensor.h"
+#include "TelemetrySensor.h"
+
+RCWL9620Sensor::RCWL9620Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_RCWL9620, "RCWL9620") {}
+
+int32_t RCWL9620Sensor::runOnce()
+{
+ LOG_INFO("Init sensor: %s\n", sensorName);
+ if (!hasSensor()) {
+ return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ }
+ status = 1;
+ begin(nodeTelemetrySensorsMap[sensorType].second, nodeTelemetrySensorsMap[sensorType].first);
+ return initI2CSensor();
+}
+
+void RCWL9620Sensor::setup() {}
+
+bool RCWL9620Sensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+ LOG_DEBUG("RCWL9620Sensor::getMetrics\n");
+ measurement->variant.environment_metrics.distance = getDistance();
+ return true;
+}
+
+void RCWL9620Sensor::begin(TwoWire *wire, uint8_t addr, uint8_t sda, uint8_t scl, uint32_t speed)
+{
+ _wire = wire;
+ _addr = addr;
+ _sda = sda;
+ _scl = scl;
+ _speed = speed;
+ _wire->begin();
+}
+
+float RCWL9620Sensor::getDistance()
+{
+ uint32_t data;
+ _wire->beginTransmission(_addr); // Transfer data to addr.
+ _wire->write(0x01);
+ _wire->endTransmission(); // Stop data transmission with the Ultrasonic
+ // Unit.
+
+ _wire->requestFrom(_addr,
+ (uint8_t)3); // Request 3 bytes from Ultrasonic Unit.
+
+ data = _wire->read();
+ data <<= 8;
+ data |= _wire->read();
+ data <<= 8;
+ data |= _wire->read();
+ float Distance = float(data) / 1000;
+ if (Distance > 4500.00) {
+ return 4500.00;
+ } else {
+ return Distance;
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/RCWL9620Sensor.h b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h
new file mode 100644
index 000000000..7f9486d25
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/RCWL9620Sensor.h
@@ -0,0 +1,29 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include
+
+class RCWL9620Sensor : public TelemetrySensor
+{
+ private:
+ uint8_t _addr = 0x57;
+ TwoWire *_wire = &Wire;
+ uint8_t _scl = -1;
+ uint8_t _sda = -1;
+ uint32_t _speed = 200000UL;
+
+ protected:
+ virtual void setup() override;
+ void begin(TwoWire *wire = &Wire, uint8_t addr = 0x57, uint8_t sda = -1, uint8_t scl = -1, uint32_t speed = 200000UL);
+ float getDistance();
+
+ public:
+ RCWL9620Sensor();
+ virtual int32_t runOnce() override;
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp
index 35978d970..aa2b5dcfc 100644
--- a/src/modules/Telemetry/Sensor/SHT31Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/SHT31Sensor.cpp
@@ -1,7 +1,10 @@
-#include "SHT31Sensor.h"
-#include "../mesh/generated/meshtastic/telemetry.pb.h"
-#include "TelemetrySensor.h"
#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "SHT31Sensor.h"
+#include "TelemetrySensor.h"
#include
SHT31Sensor::SHT31Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHT31, "SHT31") {}
@@ -29,3 +32,5 @@ bool SHT31Sensor::getMetrics(meshtastic_Telemetry *measurement)
return true;
}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/SHT31Sensor.h b/src/modules/Telemetry/Sensor/SHT31Sensor.h
index c6f8f1596..560b22436 100644
--- a/src/modules/Telemetry/Sensor/SHT31Sensor.h
+++ b/src/modules/Telemetry/Sensor/SHT31Sensor.h
@@ -1,3 +1,7 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include
@@ -15,3 +19,5 @@ class SHT31Sensor : public TelemetrySensor
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.cpp b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp
new file mode 100644
index 000000000..7f37327c6
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/SHT4XSensor.cpp
@@ -0,0 +1,49 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "SHT4XSensor.h"
+#include "TelemetrySensor.h"
+#include
+
+SHT4XSensor::SHT4XSensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHT4X, "SHT4X") {}
+
+int32_t SHT4XSensor::runOnce()
+{
+ LOG_INFO("Init sensor: %s\n", sensorName);
+ if (!hasSensor()) {
+ return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ }
+
+ uint32_t serialNumber = 0;
+
+ sht4x.begin(nodeTelemetrySensorsMap[sensorType].second);
+
+ serialNumber = sht4x.readSerial();
+ if (serialNumber != 0) {
+ LOG_DEBUG("serialNumber : %x\n", serialNumber);
+ status = 1;
+ } else {
+ LOG_DEBUG("Error trying to execute readSerial(): ");
+ status = 0;
+ }
+
+ return initI2CSensor();
+}
+
+void SHT4XSensor::setup()
+{
+ // Set up oversampling and filter initialization
+}
+
+bool SHT4XSensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+ sensors_event_t humidity, temp;
+ sht4x.getEvent(&humidity, &temp);
+ measurement->variant.environment_metrics.temperature = temp.temperature;
+ measurement->variant.environment_metrics.relative_humidity = humidity.relative_humidity;
+ return true;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/SHT4XSensor.h b/src/modules/Telemetry/Sensor/SHT4XSensor.h
new file mode 100644
index 000000000..62a5cefeb
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/SHT4XSensor.h
@@ -0,0 +1,23 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include
+
+class SHT4XSensor : public TelemetrySensor
+{
+ private:
+ Adafruit_SHT4x sht4x = Adafruit_SHT4x();
+
+ protected:
+ virtual void setup() override;
+
+ public:
+ SHT4XSensor();
+ virtual int32_t runOnce() override;
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp b/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp
index b0b5d37dc..37685fed7 100644
--- a/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp
+++ b/src/modules/Telemetry/Sensor/SHTC3Sensor.cpp
@@ -1,7 +1,10 @@
-#include "SHTC3Sensor.h"
-#include "../mesh/generated/meshtastic/telemetry.pb.h"
-#include "TelemetrySensor.h"
#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "SHTC3Sensor.h"
+#include "TelemetrySensor.h"
#include
SHTC3Sensor::SHTC3Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_SHTC3, "SHTC3") {}
@@ -30,4 +33,6 @@ bool SHTC3Sensor::getMetrics(meshtastic_Telemetry *measurement)
measurement->variant.environment_metrics.relative_humidity = humidity.relative_humidity;
return true;
-}
\ No newline at end of file
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/SHTC3Sensor.h b/src/modules/Telemetry/Sensor/SHTC3Sensor.h
index e5db417f5..7a760292f 100644
--- a/src/modules/Telemetry/Sensor/SHTC3Sensor.h
+++ b/src/modules/Telemetry/Sensor/SHTC3Sensor.h
@@ -1,3 +1,7 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "TelemetrySensor.h"
#include
@@ -14,4 +18,6 @@ class SHTC3Sensor : public TelemetrySensor
SHTC3Sensor();
virtual int32_t runOnce() override;
virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
-};
\ No newline at end of file
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp b/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp
new file mode 100644
index 000000000..d20e48dce
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/TSL2591Sensor.cpp
@@ -0,0 +1,43 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TSL2591Sensor.h"
+#include "TelemetrySensor.h"
+#include
+#include
+
+TSL2591Sensor::TSL2591Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_TSL25911FN, "TSL2591") {}
+
+int32_t TSL2591Sensor::runOnce()
+{
+ LOG_INFO("Init sensor: %s\n", sensorName);
+ if (!hasSensor()) {
+ return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ }
+ status = tsl.begin(nodeTelemetrySensorsMap[sensorType].second);
+
+ return initI2CSensor();
+}
+
+void TSL2591Sensor::setup()
+{
+ tsl.setGain(TSL2591_GAIN_MED); // 25x gain
+ tsl.setTiming(TSL2591_INTEGRATIONTIME_300MS);
+}
+
+bool TSL2591Sensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+ uint32_t lum = tsl.getFullLuminosity();
+ uint16_t ir, full;
+ ir = lum >> 16;
+ full = lum & 0xFFFF;
+
+ measurement->variant.environment_metrics.lux = tsl.calculateLux(full, ir);
+ LOG_INFO("Lux: %f\n", measurement->variant.environment_metrics.lux);
+
+ return true;
+}
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/TSL2591Sensor.h b/src/modules/Telemetry/Sensor/TSL2591Sensor.h
new file mode 100644
index 000000000..27bebdfe5
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/TSL2591Sensor.h
@@ -0,0 +1,22 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include
+
+class TSL2591Sensor : public TelemetrySensor
+{
+ private:
+ Adafruit_TSL2591 tsl;
+
+ protected:
+ virtual void setup() override;
+
+ public:
+ TSL2591Sensor();
+ virtual int32_t runOnce() override;
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+};
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.cpp b/src/modules/Telemetry/Sensor/TelemetrySensor.cpp
index cd8fe2566..d6e7d1fac 100644
--- a/src/modules/Telemetry/Sensor/TelemetrySensor.cpp
+++ b/src/modules/Telemetry/Sensor/TelemetrySensor.cpp
@@ -1,4 +1,10 @@
-#include "TelemetrySensor.h"
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#include "../mesh/generated/meshtastic/telemetry.pb.h"
#include "NodeDB.h"
+#include "TelemetrySensor.h"
#include "main.h"
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/TelemetrySensor.h b/src/modules/Telemetry/Sensor/TelemetrySensor.h
index 7282e6dfa..da376ad31 100644
--- a/src/modules/Telemetry/Sensor/TelemetrySensor.h
+++ b/src/modules/Telemetry/Sensor/TelemetrySensor.h
@@ -1,5 +1,10 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#pragma once
#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "MeshModule.h"
#include "NodeDB.h"
#include
@@ -38,6 +43,12 @@ class TelemetrySensor
virtual void setup();
public:
+ virtual AdminMessageHandleResult handleAdminMessage(const meshtastic_MeshPacket &mp, meshtastic_AdminMessage *request,
+ meshtastic_AdminMessage *response)
+ {
+ return AdminMessageHandleResult::NOT_HANDLED;
+ }
+
bool hasSensor() { return nodeTelemetrySensorsMap[sensorType].first > 0; }
virtual int32_t runOnce() = 0;
@@ -45,4 +56,6 @@ class TelemetrySensor
virtual bool isRunning() { return status > 0; }
virtual bool getMetrics(meshtastic_Telemetry *measurement) = 0;
-};
\ No newline at end of file
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp
new file mode 100644
index 000000000..cbeaf4c2e
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.cpp
@@ -0,0 +1,65 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include "VEML7700Sensor.h"
+
+#include
+#include
+
+VEML7700Sensor::VEML7700Sensor() : TelemetrySensor(meshtastic_TelemetrySensorType_VEML7700, "VEML7700") {}
+
+int32_t VEML7700Sensor::runOnce()
+{
+ LOG_INFO("Init sensor: %s\n", sensorName);
+ if (!hasSensor()) {
+ return DEFAULT_SENSOR_MINIMUM_WAIT_TIME_BETWEEN_READS;
+ }
+ status = veml7700.begin(nodeTelemetrySensorsMap[sensorType].second);
+
+ veml7700.setLowThreshold(10000);
+ veml7700.setHighThreshold(20000);
+ veml7700.interruptEnable(true);
+
+ return initI2CSensor();
+}
+
+void VEML7700Sensor::setup() {}
+
+/*!
+ * @brief Copmute lux from ALS reading.
+ * @param rawALS raw ALS register value
+ * @param corrected if true, apply non-linear correction
+ * @return lux value
+ */
+float VEML7700Sensor::computeLux(uint16_t rawALS, bool corrected)
+{
+ float lux = getResolution() * rawALS;
+ if (corrected)
+ lux = (((6.0135e-13 * lux - 9.3924e-9) * lux + 8.1488e-5) * lux + 1.0023) * lux;
+ return lux;
+}
+
+/*!
+ * @brief Determines resolution for current gain and integration time
+ * settings.
+ */
+float VEML7700Sensor::getResolution(void)
+{
+ return MAX_RES * (IT_MAX / veml7700.getIntegrationTimeValue()) * (GAIN_MAX / veml7700.getGainValue());
+}
+
+bool VEML7700Sensor::getMetrics(meshtastic_Telemetry *measurement)
+{
+ int16_t white;
+ measurement->variant.environment_metrics.lux = veml7700.readLux(VEML_LUX_AUTO);
+ white = veml7700.readWhite(true);
+ measurement->variant.environment_metrics.white_lux = computeLux(white, white > 100);
+ LOG_INFO("white lux %f, als lux %f\n", measurement->variant.environment_metrics.white_lux,
+ measurement->variant.environment_metrics.lux);
+
+ return true;
+}
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/VEML7700Sensor.h b/src/modules/Telemetry/Sensor/VEML7700Sensor.h
new file mode 100644
index 000000000..97e57334c
--- /dev/null
+++ b/src/modules/Telemetry/Sensor/VEML7700Sensor.h
@@ -0,0 +1,27 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
+#include "../mesh/generated/meshtastic/telemetry.pb.h"
+#include "TelemetrySensor.h"
+#include
+
+class VEML7700Sensor : public TelemetrySensor
+{
+ private:
+ const float MAX_RES = 0.0036;
+ const float GAIN_MAX = 2;
+ const float IT_MAX = 800;
+ Adafruit_VEML7700 veml7700;
+ float computeLux(uint16_t rawALS, bool corrected);
+ float getResolution(void);
+
+ protected:
+ virtual void setup() override;
+
+ public:
+ VEML7700Sensor();
+ virtual int32_t runOnce() override;
+ virtual bool getMetrics(meshtastic_Telemetry *measurement) override;
+};
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/VoltageSensor.h b/src/modules/Telemetry/Sensor/VoltageSensor.h
index f2f28fb06..767ffd246 100644
--- a/src/modules/Telemetry/Sensor/VoltageSensor.h
+++ b/src/modules/Telemetry/Sensor/VoltageSensor.h
@@ -1,7 +1,13 @@
+#include "configuration.h"
+
+#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+
#pragma once
class VoltageSensor
{
public:
virtual uint16_t getBusVoltageMv() = 0;
-};
\ No newline at end of file
+};
+
+#endif
\ No newline at end of file
diff --git a/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.c b/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.c
deleted file mode 100644
index 1f27e6c69..000000000
--- a/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.c
+++ /dev/null
@@ -1,82 +0,0 @@
-#include "bsec_iaq.h"
-
-const uint8_t bsec_config_iaq[1974] = {
- 0, 0, 4, 2, 189, 1, 0, 0, 0, 0, 0, 0, 158, 7, 0, 0, 176, 0, 1, 0, 0, 192, 168, 71, 64,
- 49, 119, 76, 0, 0, 97, 69, 0, 0, 97, 69, 137, 65, 0, 191, 205, 204, 204, 190, 0, 0, 64, 191, 225, 122,
- 148, 190, 10, 0, 3, 0, 0, 0, 96, 64, 23, 183, 209, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 205, 204, 204, 189, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 128, 63,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 128, 63, 0, 0, 0, 0, 0, 0, 128, 63, 82, 73, 157, 188, 95, 41, 203, 61, 118, 224,
- 108, 63, 155, 230, 125, 63, 191, 14, 124, 63, 0, 0, 160, 65, 0, 0, 32, 66, 0, 0, 160, 65, 0, 0, 32,
- 66, 0, 0, 32, 66, 0, 0, 160, 65, 0, 0, 32, 66, 0, 0, 160, 65, 8, 0, 2, 0, 236, 81, 133, 66,
- 16, 0, 3, 0, 10, 215, 163, 60, 10, 215, 35, 59, 10, 215, 35, 59, 13, 0, 5, 0, 0, 0, 0, 0, 100,
- 35, 41, 29, 86, 88, 0, 9, 0, 229, 208, 34, 62, 0, 0, 0, 0, 0, 0, 0, 0, 218, 27, 156, 62, 225,
- 11, 67, 64, 0, 0, 160, 64, 0, 0, 0, 0, 0, 0, 0, 0, 94, 75, 72, 189, 93, 254, 159, 64, 66, 62,
- 160, 191, 0, 0, 0, 0, 0, 0, 0, 0, 33, 31, 180, 190, 138, 176, 97, 64, 65, 241, 99, 190, 0, 0, 0,
- 0, 0, 0, 0, 0, 167, 121, 71, 61, 165, 189, 41, 192, 184, 30, 189, 64, 12, 0, 10, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 13, 5, 11, 0, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
- 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128,
- 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63,
- 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0,
- 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
- 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128,
- 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63,
- 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0,
- 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
- 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128,
- 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63,
- 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0,
- 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
- 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128,
- 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63,
- 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0,
- 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
- 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128,
- 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63,
- 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0,
- 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
- 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128,
- 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63,
- 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0,
- 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
- 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128,
- 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63,
- 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0,
- 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
- 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128,
- 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63,
- 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0,
- 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
- 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128,
- 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63,
- 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0,
- 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
- 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128,
- 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63,
- 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0,
- 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0,
- 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128,
- 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63,
- 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 10, 10, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 0, 0, 128, 63,
- 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0,
- 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 0, 88, 1, 254,
- 0, 2, 1, 5, 48, 117, 100, 0, 44, 1, 112, 23, 151, 7, 132, 3, 197, 0, 92, 4, 144, 1, 64, 1, 64,
- 1, 144, 1, 48, 117, 48, 117, 48, 117, 48, 117, 100, 0, 100, 0, 100, 0, 48, 117, 48, 117, 48, 117, 100, 0,
- 100, 0, 48, 117, 48, 117, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 100, 0, 100, 0, 100, 0, 100, 0, 48,
- 117, 48, 117, 48, 117, 100, 0, 100, 0, 100, 0, 48, 117, 48, 117, 100, 0, 100, 0, 255, 255, 255, 255, 255, 255,
- 255, 255, 255, 255, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44,
- 1, 44, 1, 44, 1, 44, 1, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 112, 23, 112, 23, 112, 23, 112, 23,
- 8, 7, 8, 7, 8, 7, 8, 7, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 255, 255, 255, 255, 255,
- 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 112, 23, 112, 23, 112, 23, 112, 23, 255, 255, 255, 255,
- 220, 5, 220, 5, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
- 255, 220, 5, 220, 5, 220, 5, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
- 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
- 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 44, 1, 0, 5, 10, 5,
- 0, 2, 0, 10, 0, 30, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 64, 1, 100, 0, 100, 0,
- 100, 0, 200, 0, 200, 0, 200, 0, 64, 1, 64, 1, 64, 1, 10, 0, 0, 0, 0, 0, 21, 122, 0, 0};
diff --git a/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.h b/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.h
deleted file mode 100644
index cdd209ae5..000000000
--- a/src/modules/Telemetry/Sensor/bme680_iaq_33v_3s_4d/bsec_iaq.h
+++ /dev/null
@@ -1,3 +0,0 @@
-#include
-
-extern const uint8_t bsec_config_iaq[1974];
diff --git a/src/modules/Telemetry/UnitConversions.cpp b/src/modules/Telemetry/UnitConversions.cpp
new file mode 100644
index 000000000..9f40de40f
--- /dev/null
+++ b/src/modules/Telemetry/UnitConversions.cpp
@@ -0,0 +1,21 @@
+#include "UnitConversions.h"
+
+float UnitConversions::CelsiusToFahrenheit(float celcius)
+{
+ return (celcius * 9) / 5 + 32;
+}
+
+float UnitConversions::MetersPerSecondToKnots(float metersPerSecond)
+{
+ return metersPerSecond * 1.94384;
+}
+
+float UnitConversions::MetersPerSecondToMilesPerHour(float metersPerSecond)
+{
+ return metersPerSecond * 2.23694;
+}
+
+float UnitConversions::HectoPascalToInchesOfMercury(float hectoPascal)
+{
+ return hectoPascal * 0.029529983071445;
+}
diff --git a/src/modules/Telemetry/UnitConversions.h b/src/modules/Telemetry/UnitConversions.h
new file mode 100644
index 000000000..60f9b664a
--- /dev/null
+++ b/src/modules/Telemetry/UnitConversions.h
@@ -0,0 +1,10 @@
+#pragma once
+
+class UnitConversions
+{
+ public:
+ static float CelsiusToFahrenheit(float celcius);
+ static float MetersPerSecondToKnots(float metersPerSecond);
+ static float MetersPerSecondToMilesPerHour(float metersPerSecond);
+ static float HectoPascalToInchesOfMercury(float hectoPascal);
+};
diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp
index aa0b6a1eb..f390aafcd 100644
--- a/src/modules/TraceRouteModule.cpp
+++ b/src/modules/TraceRouteModule.cpp
@@ -16,17 +16,37 @@ bool TraceRouteModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, m
void TraceRouteModule::alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r)
{
auto &incoming = p.decoded;
- // Only append an ID for the request (one way) and if we are not the destination (the reply will have our NodeNum already)
- if (!incoming.request_id && p.to != nodeDB->getNodeNum()) {
- appendMyID(r);
- printRoute(r, p.from, NODENUM_BROADCAST);
+ // Only append IDs for the request (one way)
+ if (!incoming.request_id) {
+ // Insert unknown hops if necessary
+ insertUnknownHops(p, r);
+ // Don't add ourselves if we are the destination (the reply will have our NodeNum already)
+ if (p.to != nodeDB->getNodeNum()) {
+ appendMyID(r);
+ printRoute(r, 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), &meshtastic_RouteDiscovery_msg, r);
}
}
+void TraceRouteModule::insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r)
+{
+ // Only insert unknown hops if hop_start is valid
+ if (p.hop_start != 0 && p.hop_limit <= p.hop_start) {
+ uint8_t hopsTaken = p.hop_start - p.hop_limit;
+ int8_t diff = hopsTaken - r->route_count;
+ for (uint8_t i = 0; i < diff; i++) {
+ if (r->route_count < sizeof(r->route) / sizeof(r->route[0])) {
+ r->route[r->route_count] = NODENUM_BROADCAST; // This will represent an unknown hop
+ r->route_count += 1;
+ }
+ }
+ }
+}
+
void TraceRouteModule::appendMyID(meshtastic_RouteDiscovery *updated)
{
// Length of route array can normally not be exceeded due to the max. hop_limit of 7
diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h
index 15e01debd..18a5ac0cb 100644
--- a/src/modules/TraceRouteModule.h
+++ b/src/modules/TraceRouteModule.h
@@ -19,6 +19,9 @@ class TraceRouteModule : public ProtobufModule
void alterReceivedProtobuf(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r) override;
private:
+ // Call to add unknown hops (e.g. when a node couldn't decrypt it) to the route based on hopStart and current hopLimit
+ void insertUnknownHops(meshtastic_MeshPacket &p, meshtastic_RouteDiscovery *r);
+
// Call to add your ID to the route array of a RouteDiscovery message
void appendMyID(meshtastic_RouteDiscovery *r);
diff --git a/src/modules/esp32/PaxcounterModule.cpp b/src/modules/esp32/PaxcounterModule.cpp
index b9fdfcb63..e6712871d 100644
--- a/src/modules/esp32/PaxcounterModule.cpp
+++ b/src/modules/esp32/PaxcounterModule.cpp
@@ -93,8 +93,8 @@ int32_t PaxcounterModule::runOnce()
configuration.wificounter = 1;
configuration.wifi_channel_map = WIFI_CHANNEL_ALL;
configuration.wifi_channel_switch_interval = 50;
- configuration.wifi_rssi_threshold = -80;
- configuration.ble_rssi_threshold = -80;
+ configuration.wifi_rssi_threshold = Default::getConfiguredOrDefault(moduleConfig.paxcounter.wifi_threshold, -80);
+ configuration.ble_rssi_threshold = Default::getConfiguredOrDefault(moduleConfig.paxcounter.ble_threshold, -80);
libpax_update_config(&configuration);
// internal processing initialization
diff --git a/src/mqtt/JSONValue.cpp b/src/mqtt/JSONValue.cpp
index a229666a9..51e0c1a3b 100644
--- a/src/mqtt/JSONValue.cpp
+++ b/src/mqtt/JSONValue.cpp
@@ -368,9 +368,9 @@ JSONValue::JSONValue(int m_integer_value)
*
* @access public
*
- * @param uint m_integer_value The number to use as the value
+ * @param unsigned int m_integer_value The number to use as the value
*/
-JSONValue::JSONValue(uint m_integer_value)
+JSONValue::JSONValue(unsigned int m_integer_value)
{
type = JSONType_Number;
number_value = (double)m_integer_value;
diff --git a/src/mqtt/JSONValue.h b/src/mqtt/JSONValue.h
index 3a50a831a..0380d324b 100644
--- a/src/mqtt/JSONValue.h
+++ b/src/mqtt/JSONValue.h
@@ -45,7 +45,7 @@ class JSONValue
JSONValue(bool m_bool_value);
JSONValue(double m_number_value);
JSONValue(int m_integer_value);
- JSONValue(uint m_integer_value);
+ JSONValue(unsigned int m_integer_value);
JSONValue(const JSONArray &m_array_value);
JSONValue(const JSONObject &m_object_value);
JSONValue(const JSONValue &m_source);
diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp
index da1c204b8..9f9ac5c24 100644
--- a/src/mqtt/MQTT.cpp
+++ b/src/mqtt/MQTT.cpp
@@ -54,9 +54,9 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length)
JSONObject json;
json = json_value->AsObject();
- // parse the channel name from the topic string by looking for "json/"
- const char *jsonSlash = "json/";
- char *ptr = strstr(topic, jsonSlash) + sizeof(jsonSlash) + 1; // set pointer to after "json/"
+ // parse the channel name from the topic string
+ // the topic has been checked above for having jsonTopic prefix, so just move past it
+ char *ptr = topic + jsonTopic.length();
ptr = strtok(ptr, "/") ? strtok(ptr, "/") : ptr; // if another "/" was added, parse string up to that character
meshtastic_Channel sendChannel = channels.getByName(ptr);
// We allow downlink JSON packets only on a channel named "mqtt"
@@ -76,6 +76,8 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length)
p->channel = json["channel"]->AsNumber();
if (json.find("to") != json.end() && json["to"]->IsNumber())
p->to = json["to"]->AsNumber();
+ if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber())
+ p->hop_limit = json["hopLimit"]->AsNumber();
if (jsonPayloadStr.length() <= sizeof(p->decoded.payload.bytes)) {
memcpy(p->decoded.payload.bytes, jsonPayloadStr.c_str(), jsonPayloadStr.length());
p->decoded.payload.size = jsonPayloadStr.length();
@@ -105,6 +107,8 @@ void MQTT::onReceive(char *topic, byte *payload, size_t length)
p->channel = json["channel"]->AsNumber();
if (json.find("to") != json.end() && json["to"]->IsNumber())
p->to = json["to"]->AsNumber();
+ if (json.find("hopLimit") != json.end() && json["hopLimit"]->IsNumber())
+ p->hop_limit = json["hopLimit"]->AsNumber();
p->decoded.payload.size =
pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes),
&meshtastic_Position_msg, &pos); // make the Data protobuf from position
@@ -392,6 +396,7 @@ bool MQTT::wantsLink() const
int32_t MQTT::runOnce()
{
+#ifdef HAS_NETWORKING
if (!moduleConfig.mqtt.enabled || !(moduleConfig.mqtt.map_reporting_enabled || channels.anyMqttEnabled()))
return disable();
@@ -404,7 +409,7 @@ int32_t MQTT::runOnce()
publishQueuedMessages();
return 200;
}
-#ifdef HAS_NETWORKING
+
else if (!pubSub.loop()) {
if (!wantConnection)
return 5000; // If we don't want connection now, check again in 5 secs
@@ -548,7 +553,10 @@ void MQTT::perhapsReportToMap()
} else {
if (map_position_precision == 0 || (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)) {
last_report_to_map = millis();
- LOG_WARN("MQTT Map reporting is enabled, but precision is 0 or no position available.\n");
+ if (map_position_precision == 0)
+ LOG_WARN("MQTT Map reporting is enabled, but precision is 0\n");
+ if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0)
+ LOG_WARN("MQTT Map reporting is enabled, but no position available.\n");
return;
}
@@ -652,11 +660,11 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Telemetry_msg, &scratch)) {
decoded = &scratch;
if (decoded->which_variant == meshtastic_Telemetry_device_metrics_tag) {
- msgPayload["battery_level"] = new JSONValue((uint)decoded->variant.device_metrics.battery_level);
+ msgPayload["battery_level"] = new JSONValue((unsigned int)decoded->variant.device_metrics.battery_level);
msgPayload["voltage"] = new JSONValue(decoded->variant.device_metrics.voltage);
msgPayload["channel_utilization"] = new JSONValue(decoded->variant.device_metrics.channel_utilization);
msgPayload["air_util_tx"] = new JSONValue(decoded->variant.device_metrics.air_util_tx);
- msgPayload["uptime_seconds"] = new JSONValue((uint)decoded->variant.device_metrics.uptime_seconds);
+ msgPayload["uptime_seconds"] = new JSONValue((unsigned int)decoded->variant.device_metrics.uptime_seconds);
} else if (decoded->which_variant == meshtastic_Telemetry_environment_metrics_tag) {
msgPayload["temperature"] = new JSONValue(decoded->variant.environment_metrics.temperature);
msgPayload["relative_humidity"] = new JSONValue(decoded->variant.environment_metrics.relative_humidity);
@@ -664,6 +672,11 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
msgPayload["gas_resistance"] = new JSONValue(decoded->variant.environment_metrics.gas_resistance);
msgPayload["voltage"] = new JSONValue(decoded->variant.environment_metrics.voltage);
msgPayload["current"] = new JSONValue(decoded->variant.environment_metrics.current);
+ msgPayload["lux"] = new JSONValue(decoded->variant.environment_metrics.lux);
+ msgPayload["white_lux"] = new JSONValue(decoded->variant.environment_metrics.white_lux);
+ msgPayload["iaq"] = new JSONValue((uint)decoded->variant.environment_metrics.iaq);
+ msgPayload["wind_speed"] = new JSONValue((uint)decoded->variant.environment_metrics.wind_speed);
+ msgPayload["wind_direction"] = new JSONValue((uint)decoded->variant.environment_metrics.wind_direction);
} else if (decoded->which_variant == meshtastic_Telemetry_power_metrics_tag) {
msgPayload["voltage_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_voltage);
msgPayload["current_ch1"] = new JSONValue(decoded->variant.power_metrics.ch1_current);
@@ -703,10 +716,10 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Position_msg, &scratch)) {
decoded = &scratch;
if ((int)decoded->time) {
- msgPayload["time"] = new JSONValue((uint)decoded->time);
+ msgPayload["time"] = new JSONValue((unsigned int)decoded->time);
}
if ((int)decoded->timestamp) {
- msgPayload["timestamp"] = new JSONValue((uint)decoded->timestamp);
+ msgPayload["timestamp"] = new JSONValue((unsigned int)decoded->timestamp);
}
msgPayload["latitude_i"] = new JSONValue((int)decoded->latitude_i);
msgPayload["longitude_i"] = new JSONValue((int)decoded->longitude_i);
@@ -714,13 +727,13 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
msgPayload["altitude"] = new JSONValue((int)decoded->altitude);
}
if ((int)decoded->ground_speed) {
- msgPayload["ground_speed"] = new JSONValue((uint)decoded->ground_speed);
+ msgPayload["ground_speed"] = new JSONValue((unsigned int)decoded->ground_speed);
}
if (int(decoded->ground_track)) {
- msgPayload["ground_track"] = new JSONValue((uint)decoded->ground_track);
+ msgPayload["ground_track"] = new JSONValue((unsigned int)decoded->ground_track);
}
if (int(decoded->sats_in_view)) {
- msgPayload["sats_in_view"] = new JSONValue((uint)decoded->sats_in_view);
+ msgPayload["sats_in_view"] = new JSONValue((unsigned int)decoded->sats_in_view);
}
if ((int)decoded->PDOP) {
msgPayload["PDOP"] = new JSONValue((int)decoded->PDOP);
@@ -747,11 +760,11 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
memset(&scratch, 0, sizeof(scratch));
if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Waypoint_msg, &scratch)) {
decoded = &scratch;
- msgPayload["id"] = new JSONValue((uint)decoded->id);
+ msgPayload["id"] = new JSONValue((unsigned int)decoded->id);
msgPayload["name"] = new JSONValue(decoded->name);
msgPayload["description"] = new JSONValue(decoded->description);
- msgPayload["expire"] = new JSONValue((uint)decoded->expire);
- msgPayload["locked_to"] = new JSONValue((uint)decoded->locked_to);
+ msgPayload["expire"] = new JSONValue((unsigned int)decoded->expire);
+ msgPayload["locked_to"] = new JSONValue((unsigned int)decoded->locked_to);
msgPayload["latitude_i"] = new JSONValue((int)decoded->latitude_i);
msgPayload["longitude_i"] = new JSONValue((int)decoded->longitude_i);
jsonObj["payload"] = new JSONValue(msgPayload);
@@ -768,14 +781,14 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_NeighborInfo_msg,
&scratch)) {
decoded = &scratch;
- msgPayload["node_id"] = new JSONValue((uint)decoded->node_id);
- msgPayload["node_broadcast_interval_secs"] = new JSONValue((uint)decoded->node_broadcast_interval_secs);
- msgPayload["last_sent_by_id"] = new JSONValue((uint)decoded->last_sent_by_id);
+ msgPayload["node_id"] = new JSONValue((unsigned int)decoded->node_id);
+ msgPayload["node_broadcast_interval_secs"] = new JSONValue((unsigned int)decoded->node_broadcast_interval_secs);
+ msgPayload["last_sent_by_id"] = new JSONValue((unsigned int)decoded->last_sent_by_id);
msgPayload["neighbors_count"] = new JSONValue(decoded->neighbors_count);
JSONArray neighbors;
for (uint8_t i = 0; i < decoded->neighbors_count; i++) {
JSONObject neighborObj;
- neighborObj["node_id"] = new JSONValue((uint)decoded->neighbors[i].node_id);
+ neighborObj["node_id"] = new JSONValue((unsigned int)decoded->neighbors[i].node_id);
neighborObj["snr"] = new JSONValue((int)decoded->neighbors[i].snr);
neighbors.push_back(new JSONValue(neighborObj));
}
@@ -836,9 +849,9 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
memset(&scratch, 0, sizeof(scratch));
if (pb_decode_from_bytes(mp->decoded.payload.bytes, mp->decoded.payload.size, &meshtastic_Paxcount_msg, &scratch)) {
decoded = &scratch;
- msgPayload["wifi_count"] = new JSONValue((uint)decoded->wifi);
- msgPayload["ble_count"] = new JSONValue((uint)decoded->ble);
- msgPayload["uptime"] = new JSONValue((uint)decoded->uptime);
+ msgPayload["wifi_count"] = new JSONValue((unsigned int)decoded->wifi);
+ msgPayload["ble_count"] = new JSONValue((unsigned int)decoded->ble);
+ msgPayload["uptime"] = new JSONValue((unsigned int)decoded->uptime);
jsonObj["payload"] = new JSONValue(msgPayload);
} else {
LOG_ERROR("Error decoding protobuf for Paxcount message!\n");
@@ -855,12 +868,12 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
decoded = &scratch;
if (decoded->type == meshtastic_HardwareMessage_Type_GPIOS_CHANGED) {
msgType = "gpios_changed";
- msgPayload["gpio_value"] = new JSONValue((uint)decoded->gpio_value);
+ msgPayload["gpio_value"] = new JSONValue((unsigned int)decoded->gpio_value);
jsonObj["payload"] = new JSONValue(msgPayload);
} else if (decoded->type == meshtastic_HardwareMessage_Type_READ_GPIOS_REPLY) {
msgType = "gpios_read_reply";
- msgPayload["gpio_value"] = new JSONValue((uint)decoded->gpio_value);
- msgPayload["gpio_mask"] = new JSONValue((uint)decoded->gpio_mask);
+ msgPayload["gpio_value"] = new JSONValue((unsigned int)decoded->gpio_value);
+ msgPayload["gpio_mask"] = new JSONValue((unsigned int)decoded->gpio_mask);
jsonObj["payload"] = new JSONValue(msgPayload);
}
} else {
@@ -876,19 +889,21 @@ std::string MQTT::meshPacketToJson(meshtastic_MeshPacket *mp)
LOG_WARN("Couldn't convert encrypted payload of MeshPacket to JSON\n");
}
- jsonObj["id"] = new JSONValue((uint)mp->id);
- jsonObj["timestamp"] = new JSONValue((uint)mp->rx_time);
- jsonObj["to"] = new JSONValue((uint)mp->to);
- jsonObj["from"] = new JSONValue((uint)mp->from);
- jsonObj["channel"] = new JSONValue((uint)mp->channel);
+ jsonObj["id"] = new JSONValue((unsigned int)mp->id);
+ jsonObj["timestamp"] = new JSONValue((unsigned int)mp->rx_time);
+ jsonObj["to"] = new JSONValue((unsigned int)mp->to);
+ jsonObj["from"] = new JSONValue((unsigned int)mp->from);
+ jsonObj["channel"] = new JSONValue((unsigned int)mp->channel);
jsonObj["type"] = new JSONValue(msgType.c_str());
jsonObj["sender"] = new JSONValue(owner.id);
if (mp->rx_rssi != 0)
jsonObj["rssi"] = new JSONValue((int)mp->rx_rssi);
if (mp->rx_snr != 0)
jsonObj["snr"] = new JSONValue((float)mp->rx_snr);
- if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start)
- jsonObj["hops_away"] = new JSONValue((uint)(mp->hop_start - mp->hop_limit));
+ if (mp->hop_start != 0 && mp->hop_limit <= mp->hop_start) {
+ jsonObj["hops_away"] = new JSONValue((unsigned int)(mp->hop_start - mp->hop_limit));
+ jsonObj["hop_start"] = new JSONValue((unsigned int)(mp->hop_start));
+ }
// serialize and write it to the stream
JSONValue *value = new JSONValue(jsonObj);
@@ -904,6 +919,7 @@ bool MQTT::isValidJsonEnvelope(JSONObject &json)
{
// if "sender" is provided, avoid processing packets we uplinked
return (json.find("sender") != json.end() ? (json["sender"]->AsString().compare(owner.id) != 0) : true) &&
+ (json.find("hopLimit") != json.end() ? json["hopLimit"]->IsNumber() : true) && // hop limit should be a number
(json.find("from") != json.end()) && json["from"]->IsNumber() &&
(json["from"]->AsNumber() == nodeDB->getNodeNum()) && // only accept message if the "from" is us
(json.find("type") != json.end()) && json["type"]->IsString() && // should specify a type
diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp
index 8f7e00461..68aa9b465 100644
--- a/src/nimble/NimbleBluetooth.cpp
+++ b/src/nimble/NimbleBluetooth.cpp
@@ -157,7 +157,9 @@ void NimbleBluetooth::setup()
NimBLEDevice::setPower(ESP_PWR_LVL_P9);
if (config.bluetooth.mode != meshtastic_Config_BluetoothConfig_PairingMode_NO_PIN) {
- NimBLEDevice::setSecurityAuth(true, true, true);
+ NimBLEDevice::setSecurityAuth(BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM | BLE_SM_PAIR_AUTHREQ_SC);
+ NimBLEDevice::setSecurityInitKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID);
+ NimBLEDevice::setSecurityRespKey(BLE_SM_PAIR_KEY_DIST_ENC | BLE_SM_PAIR_KEY_DIST_ID);
NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY);
}
bleServer = NimBLEDevice::createServer();
diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h
index 27088f86f..c979d016c 100644
--- a/src/platform/esp32/architecture.h
+++ b/src/platform/esp32/architecture.h
@@ -143,6 +143,12 @@
#define HW_VENDOR meshtastic_HardwareModel_STATION_G2
#elif defined(UNPHONE)
#define HW_VENDOR meshtastic_HardwareModel_UNPHONE
+#elif defined(WIPHONE)
+#define HW_VENDOR meshtastic_HardwareModel_WIPHONE
+#elif defined(RADIOMASTER_900_BANDIT_NANO)
+#define HW_VENDOR meshtastic_HardwareModel_RADIOMASTER_900_BANDIT_NANO
+#elif defined(HELTEC_CAPSULE_SENSOR_V3)
+#define HW_VENDOR meshtastic_HardwareModel_HELTEC_CAPSULE_SENSOR_V3
#endif
// -----------------------------------------------------------------------------
diff --git a/src/platform/esp32/iram-quirk.c b/src/platform/esp32/iram-quirk.c
new file mode 100644
index 000000000..813842138
--- /dev/null
+++ b/src/platform/esp32/iram-quirk.c
@@ -0,0 +1,23 @@
+// Free up some precious space in the iram0_0_seg memory segment
+
+#include
+
+#include
+#include
+#include
+
+#define IRAM_SECTION section(".iram1.stub")
+
+IRAM_ATTR esp_err_t stub_probe(esp_flash_t *chip, uint32_t flash_id)
+{
+ return ESP_ERR_NOT_FOUND;
+}
+
+const spi_flash_chip_t stub_flash_chip __attribute__((IRAM_SECTION)) = {
+ .name = "stub",
+ .probe = stub_probe,
+};
+
+extern const spi_flash_chip_t __wrap_esp_flash_chip_gd __attribute__((IRAM_SECTION, alias("stub_flash_chip")));
+extern const spi_flash_chip_t __wrap_esp_flash_chip_issi __attribute__((IRAM_SECTION, alias("stub_flash_chip")));
+extern const spi_flash_chip_t __wrap_esp_flash_chip_winbond __attribute__((IRAM_SECTION, alias("stub_flash_chip")));
diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp
index 2894a49fc..1dd7a389a 100644
--- a/src/platform/esp32/main-esp32.cpp
+++ b/src/platform/esp32/main-esp32.cpp
@@ -24,17 +24,23 @@
#if !defined(CONFIG_IDF_TARGET_ESP32S2) && !MESHTASTIC_EXCLUDE_BLUETOOTH
void setBluetoothEnable(bool enable)
{
- if (!isWifiAvailable() && config.bluetooth.enabled == true) {
- if (!nimbleBluetooth) {
- nimbleBluetooth = new NimbleBluetooth();
+#ifndef MESHTASTIC_EXCLUDE_WIFI
+ if (!isWifiAvailable() && config.bluetooth.enabled == true)
+#endif
+#ifdef MESHTASTIC_EXCLUDE_WIFI
+ if (config.bluetooth.enabled == true)
+#endif
+ {
+ if (!nimbleBluetooth) {
+ nimbleBluetooth = new NimbleBluetooth();
+ }
+ if (enable && !nimbleBluetooth->isActive()) {
+ nimbleBluetooth->setup();
+ }
+ // For ESP32, no way to recover from bluetooth shutdown without reboot
+ // BLE advertising automatically stops when MCU enters light-sleep(?)
+ // For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse
}
- if (enable && !nimbleBluetooth->isActive()) {
- nimbleBluetooth->setup();
- }
- // For ESP32, no way to recover from bluetooth shutdown without reboot
- // BLE advertising automatically stops when MCU enters light-sleep(?)
- // For deep-sleep, shutdown hardware with nimbleBluetooth->deinit(). Requires reboot to reverse
- }
}
#else
void setBluetoothEnable(bool enable) {}
@@ -214,11 +220,16 @@ void cpuDeepSleep(uint32_t msecToWake)
#endif
// Not needed because both of the current boards have external pullups
- // FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of
- // just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN);
+ // FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead
+ // of just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN);
#if SOC_PM_SUPPORT_EXT_WAKEUP
+#ifdef CONFIG_IDF_TARGET_ESP32
+ // ESP_EXT1_WAKEUP_ALL_LOW has been deprecated since esp-idf v5.4 for any other target.
esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW);
+#else
+ esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ANY_LOW);
+#endif
#endif
#endif
diff --git a/src/platform/nrf52/NRF52Bluetooth.cpp b/src/platform/nrf52/NRF52Bluetooth.cpp
index 759cbb404..4c25f38ea 100644
--- a/src/platform/nrf52/NRF52Bluetooth.cpp
+++ b/src/platform/nrf52/NRF52Bluetooth.cpp
@@ -215,6 +215,18 @@ void NRF52Bluetooth::shutdown()
Bluefruit.Advertising.stop();
}
+void NRF52Bluetooth::startDisabled()
+{
+ // Setup Bluetooth
+ nrf52Bluetooth->setup();
+
+ // Shutdown bluetooth for minimum power draw
+ Bluefruit.Advertising.stop();
+ Bluefruit.setTxPower(-40); // Minimum power
+
+ LOG_INFO("Disabling NRF52 Bluetooth. (Workaround: tx power min, advertising stopped)\n");
+}
+
bool NRF52Bluetooth::isConnected()
{
return Bluefruit.connected(connectionHandle);
@@ -287,7 +299,7 @@ void NRF52Bluetooth::setup()
LOG_INFO("Advertising\n");
}
-void NRF52Bluetooth::resumeAdverising()
+void NRF52Bluetooth::resumeAdvertising()
{
Bluefruit.Advertising.restartOnDisconnect(true);
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
diff --git a/src/platform/nrf52/NRF52Bluetooth.h b/src/platform/nrf52/NRF52Bluetooth.h
index fd27bbf04..450af47f9 100644
--- a/src/platform/nrf52/NRF52Bluetooth.h
+++ b/src/platform/nrf52/NRF52Bluetooth.h
@@ -8,7 +8,8 @@ class NRF52Bluetooth : BluetoothApi
public:
void setup();
void shutdown();
- void resumeAdverising();
+ void startDisabled();
+ void resumeAdvertising();
void clearBonds();
bool isConnected();
int getRssi();
diff --git a/src/platform/nrf52/architecture.h b/src/platform/nrf52/architecture.h
index 35cd4fd84..b66552a28 100644
--- a/src/platform/nrf52/architecture.h
+++ b/src/platform/nrf52/architecture.h
@@ -42,6 +42,8 @@
#define HW_VENDOR meshtastic_HardwareModel_NRF52840DK
#elif defined(ARDUINO_NRF52840_PPR)
#define HW_VENDOR meshtastic_HardwareModel_PPR
+#elif defined(RAK2560)
+#define HW_VENDOR meshtastic_HardwareModel_RAK2560
#elif defined(RAK4630)
#define HW_VENDOR meshtastic_HardwareModel_RAK4631
#elif defined(TTGO_T_ECHO)
@@ -52,6 +54,12 @@
#define HW_VENDOR meshtastic_HardwareModel_CANARYONE
#elif defined(NORDIC_PCA10059)
#define HW_VENDOR meshtastic_HardwareModel_NRF52840_PCA10059
+#elif defined(TWC_MESH_V4)
+#define HW_VENDOR meshtastic_HardwareModel_TWC_MESH_V4
+#elif defined(NRF52_PROMICRO_DIY)
+#define HW_VENDOR meshtastic_HardwareModel_NRF52_PROMICRO_DIY
+#elif defined(WIO_WM1110)
+#define HW_VENDOR meshtastic_HardwareModel_WIO_WM1110
#elif defined(PRIVATE_HW) || defined(FEATHER_DIY)
#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW
#else
@@ -110,4 +118,4 @@
#if !defined(PIN_SERIAL_RX) && !defined(NRF52840_XXAA)
// No serial ports on this board - ONLY use segger in memory console
#define USE_SEGGER
-#endif
+#endif
\ No newline at end of file
diff --git a/src/platform/nrf52/main-nrf52.cpp b/src/platform/nrf52/main-nrf52.cpp
index ecffb745d..1f2c6867d 100644
--- a/src/platform/nrf52/main-nrf52.cpp
+++ b/src/platform/nrf52/main-nrf52.cpp
@@ -68,28 +68,47 @@ static const bool useSoftDevice = true; // Set to false for easier debugging
#if !MESHTASTIC_EXCLUDE_BLUETOOTH
void setBluetoothEnable(bool enable)
{
- if (enable && config.bluetooth.enabled) {
- if (!useSoftDevice) {
+ // For debugging use: don't use bluetooth
+ if (!useSoftDevice) {
+ if (enable)
LOG_INFO("DISABLING NRF52 BLUETOOTH WHILE DEBUGGING\n");
- } else {
- if (!nrf52Bluetooth) {
- LOG_DEBUG("Initializing NRF52 Bluetooth\n");
- nrf52Bluetooth = new NRF52Bluetooth();
- nrf52Bluetooth->setup();
-
- // We delay brownout init until after BLE because BLE starts soft device
- initBrownout();
- } else {
- nrf52Bluetooth->resumeAdverising();
- }
- }
- } else {
- if (nrf52Bluetooth) {
- nrf52Bluetooth->shutdown();
- }
+ return;
}
+
+ // If user disabled bluetooth: init then disable advertising & reduce power
+ // Workaround. Avoid issue where device hangs several days after boot..
+ // Allegedly, no significant increase in power consumption
+ if (!config.bluetooth.enabled) {
+ static bool initialized = false;
+ if (!initialized) {
+ nrf52Bluetooth = new NRF52Bluetooth();
+ nrf52Bluetooth->startDisabled();
+ initBrownout();
+ initialized = true;
+ }
+ return;
+ }
+
+ if (enable) {
+ // If not yet set-up
+ if (!nrf52Bluetooth) {
+ LOG_DEBUG("Initializing NRF52 Bluetooth\n");
+ nrf52Bluetooth = new NRF52Bluetooth();
+ nrf52Bluetooth->setup();
+
+ // We delay brownout init until after BLE because BLE starts soft device
+ initBrownout();
+ }
+ // Already setup, apparently
+ else
+ nrf52Bluetooth->resumeAdvertising();
+ }
+ // Disable (if previously set-up)
+ else if (nrf52Bluetooth)
+ nrf52Bluetooth->shutdown();
}
#else
+#warning NRF52 "Bluetooth disable" workaround does not apply to builds with MESHTASTIC_EXCLUDE_BLUETOOTH
void setBluetoothEnable(bool enable) {}
#endif
/**
diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp
index a04c9c12c..89ac806dd 100644
--- a/src/platform/portduino/PortduinoGlue.cpp
+++ b/src/platform/portduino/PortduinoGlue.cpp
@@ -15,8 +15,6 @@
#include