diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index e45fd5d93..d83d052b0 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -13,16 +13,13 @@
},
"customizations": {
"vscode": {
- "extensions": [
- "ms-vscode.cpptools",
- "platformio.platformio-ide",
- ]
+ "extensions": ["ms-vscode.cpptools", "platformio.platformio-ide"]
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
- "forwardPorts": [ 4403 ],
+ "forwardPorts": [4403],
// Run commands to prepare the container for use
- "postCreateCommand": ".devcontainer/setup.sh",
+ "postCreateCommand": ".devcontainer/setup.sh"
}
diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh
index 866a4a417..0b2665f84 100755
--- a/.devcontainer/setup.sh
+++ b/.devcontainer/setup.sh
@@ -1,3 +1,3 @@
#!/usr/bin/env sh
-git submodule update --init
\ No newline at end of file
+git submodule update --init
diff --git a/.github/ISSUE_TEMPLATE/Bug Report.yml b/.github/ISSUE_TEMPLATE/Bug Report.yml
index b5ca0db40..f2d2f6507 100644
--- a/.github/ISSUE_TEMPLATE/Bug Report.yml
+++ b/.github/ISSUE_TEMPLATE/Bug Report.yml
@@ -49,10 +49,24 @@ body:
- Heltec V3
- Heltec Wireless Paper
- Heltec Wireless Tracker
+ - Heltec Mesh Node T114
+ - Heltec Vision Master E213
+ - Heltec Vision Master E290
+ - Heltec Vision Master T190
+ - Nano G1
+ - Nano G1 Explorer
+ - Nano G2 Ultra
- Raspberry Pi Pico (W)
- Relay v1
- Relay v2
- Seeed Wio Tracker 1110
+ - Seeed Card Tracker T1000-E
+ - Station G1
+ - Station G2
+ - unPhone
+ - CanaryOne
+ - Chatter
+ - Linux Native
- DIY
- Other
validations:
diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml
index b027a36cc..b50ccac26 100644
--- a/.github/ISSUE_TEMPLATE/feature.yml
+++ b/.github/ISSUE_TEMPLATE/feature.yml
@@ -18,6 +18,7 @@ body:
- ESP32
- RP2040
- Linux Native
+ - Cross-Platform
- other
validations:
required: true
diff --git a/.github/actions/build-variant/action.yml b/.github/actions/build-variant/action.yml
new file mode 100644
index 000000000..80d2a56bb
--- /dev/null
+++ b/.github/actions/build-variant/action.yml
@@ -0,0 +1,94 @@
+name: Setup Build Variant Composite Action
+description: Variant build actions for Meshtastic Platform IO steps
+
+inputs:
+ board:
+ description: The board to build for
+ required: true
+ github_token:
+ description: GitHub token
+ required: true
+ build-script-path:
+ description: Path to the build script
+ required: true
+ remove-debug-flags:
+ description: A space separated list of files to remove debug flags from
+ required: false
+ default: ""
+ ota-firmware-source:
+ description: The OTA firmware file to pull
+ required: false
+ default: ""
+ ota-firmware-target:
+ description: The target path to store the OTA firmware file
+ required: false
+ default: ""
+ artifact-paths:
+ description: A newline separated list of paths to store as artifacts
+ required: false
+ default: ""
+ include-web-ui:
+ description: Include the web UI in the build
+ required: false
+ default: "false"
+ arch:
+ description: Processor arch name
+ required: true
+ default: "esp32"
+
+runs:
+ using: composite
+ steps:
+ - name: Build base
+ id: base
+ uses: ./.github/actions/setup-base
+
+ - name: Pull web ui
+ if: inputs.include-web-ui == 'true'
+ uses: dsaltares/fetch-gh-release-asset@master
+ with:
+ repo: meshtastic/web
+ file: build.tar
+ target: build.tar
+ token: ${{ inputs.github_token }}
+
+ - name: Unpack web ui
+ if: inputs.include-web-ui == 'true'
+ shell: bash
+ run: |
+ tar -xf build.tar -C data/static
+ rm build.tar
+
+ - name: Remove debug flags for release
+ shell: bash
+ if: inputs.remove-debug-flags != ''
+ run: |
+ for INI_FILE in ${{ inputs.remove-debug-flags }}; do
+ sed -i '/DDEBUG_HEAP/d' ${INI_FILE}
+ done
+
+ - name: Build ${{ inputs.board }}
+ shell: bash
+ run: ${{ inputs.build-script-path }} ${{ inputs.board }}
+
+ - name: Pull OTA Firmware
+ if: inputs.ota-firmware-source != '' && inputs.ota-firmware-target != ''
+ uses: dsaltares/fetch-gh-release-asset@master
+ with:
+ repo: meshtastic/firmware-ota
+ file: ${{ inputs.ota-firmware-source }}
+ target: ${{ inputs.ota-firmware-target }}
+ token: ${{ inputs.github_token }}
+
+ - name: Get release version string
+ shell: bash
+ run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+ id: version
+
+ - name: Store binaries as an artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
+ overwrite: true
+ path: |
+ ${{ inputs.artifact-paths }}
diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml
index 929c1df38..c0f6c4e66 100644
--- a/.github/actions/setup-base/action.yml
+++ b/.github/actions/setup-base/action.yml
@@ -11,6 +11,11 @@ runs:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
+ - name: Uncomment build epoch
+ shell: bash
+ run: |
+ sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
+
- name: Install dependencies
shell: bash
run: |
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..616c16ce2
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,26 @@
+version: 2
+updates:
+ - package-ecosystem: docker
+ directory: devcontainer
+ schedule:
+ interval: daily
+ time: "05:00" # trunk-ignore(yamllint/quoted-strings): required by dependabot syntax check
+ timezone: US/Pacific
+ - package-ecosystem: docker
+ directory: /
+ schedule:
+ interval: daily
+ time: "05:00" # trunk-ignore(yamllint/quoted-strings): required by dependabot syntax check
+ timezone: US/Pacific
+ - package-ecosystem: gitsubmodule
+ directory: /
+ schedule:
+ interval: daily
+ time: "05:00" # trunk-ignore(yamllint/quoted-strings): required by dependabot syntax check
+ timezone: US/Pacific
+ - package-ecosystem: github-actions
+ directory: /.github/workflows
+ schedule:
+ interval: daily
+ time: "05:00" # trunk-ignore(yamllint/quoted-strings): required by dependabot syntax check
+ timezone: US/Pacific
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 512dea311..6ccb4a105 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,9 +1,9 @@
+### ❌ (Please delete all these tips and replace them with your text) ❌
+
## Thank you for sending in a pull request, here's some tips to get started!
-(Please delete all these tips and replace with your text)
-
- Before starting on some new big chunk of code, it it is optional but highly recommended to open an issue first
- to say "hey, I think this idea X should be implemented and I'm starting work on it. My general plan is Y, any feedback
+ to say "Hey, I think this idea X should be implemented and I'm starting work on it. My general plan is Y, any feedback
is appreciated." This will allow other devs to potentially save you time by not accidentially duplicating work etc...
- Please do not check in files that don't have real changes
- Please do not reformat lines that you didn't have to change the code on
@@ -12,3 +12,4 @@
- If your PR fixes a bug, mention "fixes #bugnum" somewhere in your pull request description.
- If your other co-developers have comments on your PR please tweak as needed.
- Please also enable "Allow edits by maintainers".
+- If your PR gets accepted you can request a "Contributor" role in the Meshtastic Discord
diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml
index 4cbb4c7a4..7d069e3db 100644
--- a/.github/workflows/build_esp32.yml
+++ b/.github/workflows/build_esp32.yml
@@ -12,52 +12,23 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - name: Build base
- id: base
- uses: ./.github/actions/setup-base
-
- - name: Pull web ui
- uses: dsaltares/fetch-gh-release-asset@master
- with:
- repo: meshtastic/web
- file: build.tar
- target: build.tar
- token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Unpack web ui
- run: |
- tar -xf build.tar -C data/static
- rm build.tar
-
- - name: Remove debug flags for release
- if: ${{ github.event_name == 'workflow_dispatch' }}
- run: |
- sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
- sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
- sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
- sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini
- name: Build ESP32
- run: bin/build-esp32.sh ${{ inputs.board }}
-
- - name: Pull OTA Firmware
- uses: dsaltares/fetch-gh-release-asset@master
+ id: build
+ uses: ./.github/actions/build-variant
with:
- repo: meshtastic/firmware-ota
- file: firmware.bin
- target: release/bleota.bin
- token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Get release version string
- shell: bash
- run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
- id: version
-
- - name: Store binaries as an artifact
- uses: actions/upload-artifact@v4
- with:
- name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
- overwrite: true
- path: |
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ board: ${{ inputs.board }}
+ remove-debug-flags: >-
+ ./arch/esp32/esp32.ini
+ ./arch/esp32/esp32s2.ini
+ ./arch/esp32/esp32s3.ini
+ ./arch/esp32/esp32c3.ini
+ build-script-path: bin/build-esp32.sh
+ ota-firmware-source: firmware.bin
+ ota-firmware-target: release/bleota.bin
+ artifact-paths: |
release/*.bin
release/*.elf
+ include-web-ui: true
+ arch: esp32
diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml
index 07727d711..5234dbe81 100644
--- a/.github/workflows/build_esp32_c3.yml
+++ b/.github/workflows/build_esp32_c3.yml
@@ -14,50 +14,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - name: Build base
- id: base
- uses: ./.github/actions/setup-base
- - name: Pull web ui
- uses: dsaltares/fetch-gh-release-asset@master
+ - name: Build ESP32-C3
+ id: build
+ uses: ./.github/actions/build-variant
with:
- repo: meshtastic/web
- file: build.tar
- target: build.tar
- token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Unpack web ui
- run: |
- tar -xf build.tar -C data/static
- rm build.tar
- - name: Remove debug flags for release
- if: ${{ github.event_name == 'workflow_dispatch' }}
- run: |
- sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
- sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
- sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
- sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini
- - name: Build ESP32
- run: bin/build-esp32.sh ${{ inputs.board }}
-
- - name: Pull OTA Firmware
- uses: dsaltares/fetch-gh-release-asset@master
- with:
- repo: meshtastic/firmware-ota
- file: firmware-c3.bin
- target: release/bleota-c3.bin
- token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Get release version string
- shell: bash
- run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
- id: version
-
- - name: Store binaries as an artifact
- uses: actions/upload-artifact@v4
- with:
- name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
- overwrite: true
- path: |
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ board: ${{ inputs.board }}
+ remove-debug-flags: >-
+ ./arch/esp32/esp32.ini
+ ./arch/esp32/esp32s2.ini
+ ./arch/esp32/esp32s3.ini
+ ./arch/esp32/esp32c3.ini
+ build-script-path: bin/build-esp32.sh
+ ota-firmware-source: firmware-c3.bin
+ ota-firmware-target: release/bleota-c3.bin
+ artifact-paths: |
release/*.bin
release/*.elf
+ arch: esp32c3
diff --git a/.github/workflows/build_esp32_c6.yml b/.github/workflows/build_esp32_c6.yml
new file mode 100644
index 000000000..66f2764a6
--- /dev/null
+++ b/.github/workflows/build_esp32_c6.yml
@@ -0,0 +1,36 @@
+name: Build ESP32-C6
+
+on:
+ workflow_call:
+ inputs:
+ board:
+ required: true
+ type: string
+
+permissions: read-all
+
+jobs:
+ build-esp32-c6:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Build ESP32-C6
+ id: build
+ uses: ./.github/actions/build-variant
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ board: ${{ inputs.board }}
+ remove-debug-flags: >-
+ ./arch/esp32/esp32.ini
+ ./arch/esp32/esp32s2.ini
+ ./arch/esp32/esp32s3.ini
+ ./arch/esp32/esp32c3.ini
+ ./arch/esp32/esp32c6.ini
+ build-script-path: bin/build-esp32.sh
+ ota-firmware-source: firmware-c3.bin
+ ota-firmware-target: release/bleota-c3.bin
+ artifact-paths: |
+ release/*.bin
+ release/*.elf
+ arch: esp32c6
diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml
index 10773833e..554b37cef 100644
--- a/.github/workflows/build_esp32_s3.yml
+++ b/.github/workflows/build_esp32_s3.yml
@@ -12,50 +12,23 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - name: Build base
- id: base
- uses: ./.github/actions/setup-base
- - name: Pull web ui
- uses: dsaltares/fetch-gh-release-asset@master
+ - name: Build ESP32-S3
+ id: build
+ uses: ./.github/actions/build-variant
with:
- repo: meshtastic/web
- file: build.tar
- target: build.tar
- token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Unpack web ui
- run: |
- tar -xf build.tar -C data/static
- rm build.tar
- - name: Remove debug flags for release
- if: ${{ github.event_name == 'workflow_dispatch' }}
- run: |
- sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
- sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
- sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
- sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini
- - name: Build ESP32
- run: bin/build-esp32.sh ${{ inputs.board }}
-
- - name: Pull OTA Firmware
- uses: dsaltares/fetch-gh-release-asset@master
- with:
- repo: meshtastic/firmware-ota
- file: firmware-s3.bin
- target: release/bleota-s3.bin
- token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Get release version string
- shell: bash
- run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
- id: version
-
- - name: Store binaries as an artifact
- uses: actions/upload-artifact@v4
- with:
- name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
- overwrite: true
- path: |
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ board: ${{ inputs.board }}
+ remove-debug-flags: >-
+ ./arch/esp32/esp32.ini
+ ./arch/esp32/esp32s2.ini
+ ./arch/esp32/esp32s3.ini
+ ./arch/esp32/esp32c3.ini
+ build-script-path: bin/build-esp32.sh
+ ota-firmware-source: firmware-s3.bin
+ ota-firmware-target: release/bleota-s3.bin
+ artifact-paths: |
release/*.bin
release/*.elf
+ include-web-ui: true
+ arch: esp32s3
diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml
index 51bef0c13..1fb44a717 100644
--- a/.github/workflows/build_native.yml
+++ b/.github/workflows/build_native.yml
@@ -67,7 +67,7 @@ jobs:
- 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
+ uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
@@ -77,7 +77,7 @@ jobs:
- 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
+ uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
diff --git a/.github/workflows/build_nrf52.yml b/.github/workflows/build_nrf52.yml
index ac509a096..ce26838f2 100644
--- a/.github/workflows/build_nrf52.yml
+++ b/.github/workflows/build_nrf52.yml
@@ -12,24 +12,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - name: Build base
- id: base
- uses: ./.github/actions/setup-base
- name: Build NRF52
- run: bin/build-nrf52.sh ${{ inputs.board }}
-
- - name: Get release version string
- run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
- id: version
-
- - name: Store binaries as an artifact
- uses: actions/upload-artifact@v4
+ id: build
+ uses: ./.github/actions/build-variant
with:
- name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
- overwrite: true
- path: |
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ board: ${{ inputs.board }}
+ build-script-path: bin/build-nrf52.sh
+ artifact-paths: |
release/*.hex
release/*.uf2
release/*.elf
release/*.zip
+ arch: nrf52840
diff --git a/.github/workflows/build_rpi2040.yml b/.github/workflows/build_rpi2040.yml
index 6e258fe2a..492a1f010 100644
--- a/.github/workflows/build_rpi2040.yml
+++ b/.github/workflows/build_rpi2040.yml
@@ -12,22 +12,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - name: Build base
- id: base
- uses: ./.github/actions/setup-base
- name: Build Raspberry Pi 2040
- run: ./bin/build-rpi2040.sh ${{ inputs.board }}
-
- - name: Get release version string
- run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
- id: version
-
- - name: Store binaries as an artifact
- uses: actions/upload-artifact@v4
+ id: build
+ uses: ./.github/actions/build-variant
with:
- name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
- overwrite: true
- path: |
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ board: ${{ inputs.board }}
+ build-script-path: bin/build-rpi2040.sh
+ artifact-paths: |
release/*.uf2
release/*.elf
+ arch: rp2040
diff --git a/.github/workflows/build_stm32.yml b/.github/workflows/build_stm32.yml
index d13c52c8a..b463bab71 100644
--- a/.github/workflows/build_stm32.yml
+++ b/.github/workflows/build_stm32.yml
@@ -12,22 +12,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - name: Build base
- id: base
- uses: ./.github/actions/setup-base
- - name: Build STM32
- run: bin/build-stm32.sh ${{ inputs.board }}
-
- - name: Get release version string
- run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
- id: version
-
- - name: Store binaries as an artifact
- uses: actions/upload-artifact@v4
+ - name: Build STM32WL
+ id: build
+ uses: ./.github/actions/build-variant
with:
- name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
- overwrite: true
- path: |
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ board: ${{ inputs.board }}
+ build-script-path: bin/build-stm32.sh
+ artifact-paths: |
release/*.hex
release/*.bin
+ release/*.elf
+ arch: stm32
diff --git a/.github/workflows/generate-userprefs.yml b/.github/workflows/generate-userprefs.yml
new file mode 100644
index 000000000..10dd1ff7d
--- /dev/null
+++ b/.github/workflows/generate-userprefs.yml
@@ -0,0 +1,35 @@
+name: Generate UsersPrefs JSON manifest
+
+on:
+ push:
+ paths:
+ - userPrefs.h
+ branches:
+ - master
+
+jobs:
+ generate-userprefs:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Install Clang
+ run: sudo apt-get install -y clang
+
+ - name: Install trunk
+ run: curl https://get.trunk.io -fsSL | bash
+
+ - name: Generate userPrefs.jsom
+ run: python3 ./bin/build-userprefs-json.py
+
+ - name: Trunk format json
+ run: trunk format userPrefs.json
+
+ - name: Commit userPrefs.json
+ run: |
+ git config --global user.email "actions@github.com"
+ git config --global user.name "GitHub Actions"
+ git add userPrefs.json
+ git commit -m "Update userPrefs.json"
+ git push
diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index 36125f72f..0853df19f 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -1,7 +1,7 @@
name: CI
-#concurrency:
-# group: ${{ github.ref }}
-# cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
+concurrency:
+ group: ci-${{ github.head_ref || github.run_id }}
+ cancel-in-progress: true
on:
# # Triggers the workflow on push but only for the master branch
push:
@@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- arch: [esp32, esp32s3, esp32c3, nrf52840, rp2040, stm32, check]
+ arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32, check]
runs-on: ubuntu-latest
steps:
- id: checkout
@@ -32,13 +32,18 @@ jobs:
name: Checkout base
- id: jsonStep
run: |
- TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
- echo "$TARGETS"
+ if [[ "${{ github.head_ref }}" == "" ]]; then
+ TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
+ else
+ TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} quick)
+ fi
+ echo "Name: ${{ github.ref_name }} Base: ${{ github.base_ref }} Head: ${{ github.head_ref }} Ref: ${{ github.ref }} Targets: $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 }}
+ esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
stm32: ${{ steps.jsonStep.outputs.stm32 }}
@@ -51,6 +56,7 @@ jobs:
matrix: ${{ fromJson(needs.setup.outputs.check) }}
runs-on: ubuntu-latest
+ if: ${{ github.event_name != 'workflow_dispatch' }}
steps:
- uses: actions/checkout@v4
- name: Build base
@@ -86,6 +92,15 @@ jobs:
with:
board: ${{ matrix.board }}
+ build-esp32-c6:
+ needs: setup
+ strategy:
+ fail-fast: false
+ matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
+ uses: ./.github/workflows/build_esp32_c6.yml
+ with:
+ board: ${{ matrix.board }}
+
build-nrf52:
needs: setup
strategy:
@@ -124,6 +139,7 @@ jobs:
after-checks:
runs-on: ubuntu-latest
+ if: ${{ github.event_name != 'workflow_dispatch' }}
needs: [check]
steps:
- name: Checkout code
@@ -136,18 +152,20 @@ jobs:
permissions:
contents: write
pull-requests: write
+ strategy:
+ fail-fast: false
+ matrix:
+ arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32]
runs-on: ubuntu-latest
needs:
[
build-esp32,
build-esp32-s3,
build-esp32-c3,
+ build-esp32-c6,
build-nrf52,
build-rpi2040,
build-stm32,
- package-raspbian,
- package-raspbian-armv7l,
- package-native,
]
steps:
- name: Checkout code
@@ -159,6 +177,7 @@ jobs:
- uses: actions/download-artifact@v4
with:
path: ./
+ pattern: firmware-${{matrix.arch}}-*
merge-multiple: true
- name: Display structure of downloaded files
@@ -169,12 +188,12 @@ jobs:
id: version
- name: Move files up
- run: mv -b -t ./ ./release/meshtasticd_linux_* ./bin/config-dist.yaml ./bin/device-*.sh ./bin/device-*.bat
+ run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
uses: actions/upload-artifact@v4
with:
- name: firmware-${{ steps.version.outputs.version }}
+ name: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}
overwrite: true
path: |
./firmware-*.bin
@@ -183,16 +202,14 @@ jobs:
./firmware-*-ota.zip
./device-*.sh
./device-*.bat
- ./meshtasticd_linux_*
- ./config-dist.yaml
./littlefs-*.bin
./bleota*bin
./Meshtastic_nRF52_factory_erase*.uf2
- retention-days: 90
+ retention-days: 30
- uses: actions/download-artifact@v4
with:
- name: firmware-${{ steps.version.outputs.version }}
+ name: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}
merge-multiple: true
path: ./output
@@ -206,32 +223,34 @@ 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-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip ./output
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v4
with:
- name: debug-elfs-${{ steps.version.outputs.version }}.zip
+ name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip
overwrite: true
path: ./*.elf
retention-days: 30
- - name: Create request artifacts
- continue-on-error: true # FIXME: Why are we getting 502, but things still work?
- if: ${{ github.event_name == 'pull_request_target' || github.event_name == 'pull_request' }}
- uses: gavv/pull-request-artifacts@v2.1.0
+ - uses: scruplelesswizard/comment-artifact@main
+ if: ${{ github.event_name == 'pull_request' }}
with:
- commit: ${{ (github.event.pull_request_target || github.event.pull_request).head.sha }}
- repo-token: ${{ secrets.GITHUB_TOKEN }}
- artifacts-token: ${{ secrets.ARTIFACTS_TOKEN }}
- artifacts-repo: meshtastic/artifacts
- artifacts-branch: device
- artifacts: ./firmware-${{ steps.version.outputs.version }}.zip
+ name: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}
+ description: "Download firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip. This artifact will be available for 90 days from creation"
+ github-token: ${{ secrets.GITHUB_TOKEN }}
release-artifacts:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' }}
- needs: [gather-artifacts, after-checks]
+ outputs:
+ upload_url: ${{ steps.create_release.outputs.upload_url }}
+ needs: [
+ gather-artifacts,
+ package-raspbian,
+ package-raspbian-armv7l,
+ package-native,
+ ]
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -245,42 +264,6 @@ jobs:
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- - uses: actions/download-artifact@v4
- with:
- name: firmware-${{ steps.version.outputs.version }}
- merge-multiple: true
- path: ./output
-
- - uses: actions/download-artifact@v4
- with:
- pattern: meshtasticd_${{ steps.version.outputs.version }}_*.deb
- merge-multiple: true
- path: ./output
-
- - name: Display structure of downloaded files
- run: ls -R
-
- - name: Device scripts permissions
- run: |
- chmod +x ./output/device-install.sh
- chmod +x ./output/device-update.sh
-
- - name: Zip firmware
- run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output -x *.deb
-
- - uses: actions/download-artifact@v4
- with:
- name: debug-elfs-${{ steps.version.outputs.version }}.zip
- merge-multiple: true
- path: ./elfs
-
- - name: Zip Elfs
- run: zip -j -9 -r ./debug-elfs-${{ steps.version.outputs.version }}.zip ./elfs
-
- # For diagnostics
- - name: Show artifacts
- run: ls -lR
-
- name: Create release
uses: actions/create-release@v1
id: create_release
@@ -294,25 +277,16 @@ jobs:
env:
GITHUB_TOKEN: ${{ github.token }}
- - name: Add bins to release
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ github.token }}
+ - name: Download deb files
+ uses: actions/download-artifact@v4
with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./firmware-${{ steps.version.outputs.version }}.zip
- asset_name: firmware-${{ steps.version.outputs.version }}.zip
- asset_content_type: application/zip
+ pattern: meshtasticd_${{ steps.version.outputs.version }}_*.deb
+ merge-multiple: true
+ path: ./output
- - name: Add debug elfs to release
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ github.token }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./debug-elfs-${{ steps.version.outputs.version }}.zip
- asset_name: debug-elfs-${{ steps.version.outputs.version }}.zip
- asset_content_type: application/zip
+ # For diagnostics
+ - name: Display structure of downloaded files
+ run: ls -lR
- name: Add raspbian aarch64 .deb
uses: actions/upload-release-asset@v1
@@ -349,7 +323,79 @@ jobs:
bin/bump_version.py
- name: Create version.properties pull request
- uses: peter-evans/create-pull-request@v6
+ uses: peter-evans/create-pull-request@v7
with:
+ title: Bump version.properties
add-paths: |
version.properties
+
+ release-firmware:
+ strategy:
+ fail-fast: false
+ matrix:
+ arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32]
+ runs-on: ubuntu-latest
+ if: ${{ github.event_name == 'workflow_dispatch' }}
+ needs: [release-artifacts]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: 3.x
+
+ - name: Get release version string
+ run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+ id: version
+
+ - uses: actions/download-artifact@v4
+ with:
+ pattern: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}
+ merge-multiple: true
+ path: ./output
+
+ - name: Display structure of downloaded files
+ run: ls -lR
+
+ - name: Device scripts permissions
+ run: |
+ chmod +x ./output/device-install.sh
+ chmod +x ./output/device-update.sh
+
+ - name: Zip firmware
+ run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip ./output
+
+ - uses: actions/download-artifact@v4
+ with:
+ name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip
+ merge-multiple: true
+ path: ./elfs
+
+ - name: Zip firmware
+ run: zip -j -9 -r ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip ./elfs
+
+ # For diagnostics
+ - name: Display structure of downloaded files
+ run: ls -lR
+
+ - name: Add bins to release
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ with:
+ upload_url: ${{needs.release-artifacts.outputs.upload_url}}
+ asset_path: ./firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip
+ asset_name: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip
+ asset_content_type: application/zip
+
+ - name: Add debug elfs to release
+ uses: actions/upload-release-asset@v1
+ env:
+ GITHUB_TOKEN: ${{ github.token }}
+ with:
+ upload_url: ${{needs.release-artifacts.outputs.upload_url}}
+ asset_path: ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip
+ asset_name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip
+ asset_content_type: application/zip
diff --git a/.github/workflows/package_amd64.yml b/.github/workflows/package_amd64.yml
index ae7bf3242..a5442246a 100644
--- a/.github/workflows/package_amd64.yml
+++ b/.github/workflows/package_amd64.yml
@@ -13,7 +13,7 @@ jobs:
uses: ./.github/workflows/build_native.yml
package-native:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
needs: build-native
steps:
- name: Checkout code
@@ -50,11 +50,14 @@ jobs:
mkdir -p .debpkg/usr/share/doc/meshtasticd/web
mkdir -p .debpkg/usr/sbin
mkdir -p .debpkg/etc/meshtasticd
+ mkdir -p .debpkg/etc/meshtasticd/config.d
+ mkdir -p .debpkg/etc/meshtasticd/available.d
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
+ cp bin/config.d/* .debpkg/etc/meshtasticd/available.d/
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
diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml
index 5471332c5..89efba1de 100644
--- a/.github/workflows/package_raspbian.yml
+++ b/.github/workflows/package_raspbian.yml
@@ -13,7 +13,7 @@ jobs:
uses: ./.github/workflows/build_raspbian.yml
package-raspbian:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
needs: build-raspbian
steps:
- name: Checkout code
@@ -50,11 +50,14 @@ jobs:
mkdir -p .debpkg/usr/share/doc/meshtasticd/web
mkdir -p .debpkg/usr/sbin
mkdir -p .debpkg/etc/meshtasticd
+ mkdir -p .debpkg/etc/meshtasticd/config.d
+ mkdir -p .debpkg/etc/meshtasticd/available.d
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_aarch64 .debpkg/usr/sbin/meshtasticd
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
+ cp bin/config.d/* .debpkg/etc/meshtasticd/available.d/
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
diff --git a/.github/workflows/package_raspbian_armv7l.yml b/.github/workflows/package_raspbian_armv7l.yml
index 5b9c9aa71..5cbc27097 100644
--- a/.github/workflows/package_raspbian_armv7l.yml
+++ b/.github/workflows/package_raspbian_armv7l.yml
@@ -13,7 +13,7 @@ jobs:
uses: ./.github/workflows/build_raspbian_armv7l.yml
package-raspbian_armv7l:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
needs: build-raspbian_armv7l
steps:
- name: Checkout code
@@ -50,11 +50,14 @@ jobs:
mkdir -p .debpkg/usr/share/doc/meshtasticd/web
mkdir -p .debpkg/usr/sbin
mkdir -p .debpkg/etc/meshtasticd
+ mkdir -p .debpkg/etc/meshtasticd/config.d
+ mkdir -p .debpkg/etc/meshtasticd/available.d
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
+ cp bin/config.d/* .debpkg/etc/meshtasticd/available.d/
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
diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml
index 2a0361f5e..54bbbe6d2 100644
--- a/.github/workflows/sec_sast_semgrep_cron.yml
+++ b/.github/workflows/sec_sast_semgrep_cron.yml
@@ -12,7 +12,7 @@ jobs:
semgrep-full:
runs-on: ubuntu-latest
container:
- image: returntocorp/semgrep
+ image: semgrep/semgrep
steps:
# step 1
diff --git a/.github/workflows/sec_sast_semgrep_pull.yml b/.github/workflows/sec_sast_semgrep_pull.yml
index b6c288494..9013f1c74 100644
--- a/.github/workflows/sec_sast_semgrep_pull.yml
+++ b/.github/workflows/sec_sast_semgrep_pull.yml
@@ -4,9 +4,9 @@ on: pull_request
jobs:
semgrep-diff:
- runs-on: ubuntu-latest
+ runs-on: ubuntu-22.04
container:
- image: returntocorp/semgrep
+ image: semgrep/semgrep
steps:
# step 1
diff --git a/.github/workflows/stale_bot.yml b/.github/workflows/stale_bot.yml
new file mode 100644
index 000000000..0fd2cd5c3
--- /dev/null
+++ b/.github/workflows/stale_bot.yml
@@ -0,0 +1,21 @@
+name: process stale Issues and PR's
+on:
+ schedule:
+ - cron: 0 6 * * *
+ workflow_dispatch: {}
+
+permissions:
+ issues: write
+ pull-requests: write
+
+jobs:
+ stale_issues:
+ name: Close Stale Issues
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Stale PR+Issues
+ uses: actions/stale@v9.0.0
+ with:
+ exempt-issue-labels: pinned,3.0
+ exempt-pr-labels: pinned,3.0
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index f58b38ac9..241598fd0 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -57,35 +57,50 @@ jobs:
reporter: java-junit
hardware-tests:
- runs-on: ubuntu-latest
+ runs-on: test-runner
steps:
- name: Checkout code
uses: actions/checkout@v4
+ # - uses: actions/setup-python@v5
+ # with:
+ # python-version: '3.10'
+
+ # pipx install "setuptools<72"
- name: Upgrade python tools
shell: bash
run: |
- python -m pip install --upgrade pip
- pip install -U --no-build-isolation --no-cache-dir "setuptools<72"
- pip install -U platformio adafruit-nrfutil --no-build-isolation
- pip install -U poetry --no-build-isolation
- pip install -U meshtastic --pre --no-build-isolation
+ pipx install adafruit-nrfutil
+ pipx install poetry
+ pipx install meshtastic --pip-args=--pre
+
+ - name: Install PlatformIO from script
+ shell: bash
+ run: |
+ curl -fsSL -o get-platformio.py https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py
+ python3 get-platformio.py
- name: Upgrade platformio
shell: bash
run: |
+ export PATH=$PATH:$HOME/.local/bin
pio upgrade
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: 18
+
- name: Setup pnpm
- uses: pnpm/action-setup@v2
+ uses: pnpm/action-setup@v4
with:
version: latest
- - name: Install Dependencies
- run: pnpm install
-
- - name: Setup devices
- run: pnpm run setup
-
- - name: Execute end to end tests on connected hardware
- run: pnpm run test
+ - name: Install dependencies, setup devices and run
+ shell: bash
+ run: |
+ git submodule update --init --recursive
+ cd meshtestic/
+ pnpm install
+ pnpm run setup
+ pnpm run test
diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml
index 4402a280e..f1c92b860 100644
--- a/.github/workflows/update_protobufs.yml
+++ b/.github/workflows/update_protobufs.yml
@@ -1,4 +1,4 @@
-name: "Update protobufs and regenerate classes"
+name: Update protobufs and regenerate classes
on: workflow_dispatch
jobs:
@@ -17,17 +17,18 @@ jobs:
- name: Download nanopb
run: |
- wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8-linux-x86.tar.gz
- tar xvzf nanopb-0.4.8-linux-x86.tar.gz
- mv nanopb-0.4.8-linux-x86 nanopb-0.4.8
+ wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.9-linux-x86.tar.gz
+ tar xvzf nanopb-0.4.9-linux-x86.tar.gz
+ mv nanopb-0.4.9-linux-x86 nanopb-0.4.9
- name: Re-generate protocol buffers
run: |
./bin/regen-protos.sh
- name: Create pull request
- uses: peter-evans/create-pull-request@v6
+ uses: peter-evans/create-pull-request@v7
with:
+ title: Update protobufs and classes
add-paths: |
protobufs
src/mesh
diff --git a/.gitignore b/.gitignore
index 0f2202f8d..28f9a24cc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,4 @@
.pio
-main/configuration.h
-main/credentials.h
# ignore vscode IDE settings files
.vscode/*
@@ -32,4 +30,4 @@ release/
.vscode/extensions.json
/compile_commands.json
src/mesh/raspihttp/certificate.pem
-src/mesh/raspihttp/private_key.pem
\ No newline at end of file
+src/mesh/raspihttp/private_key.pem
diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index b20a1ec95..2fa237d31 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -1,34 +1,34 @@
version: 0.1
cli:
- version: 1.22.3
+ version: 1.22.7
plugins:
sources:
- id: trunk
- ref: v1.6.2
+ ref: v1.6.4
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- - trufflehog@3.81.9
+ - trufflehog@3.82.13
- yamllint@1.35.1
- - bandit@1.7.9
- - checkov@3.2.238
- - terrascan@1.19.1
- - trivy@0.54.1
+ - bandit@1.7.10
+ - checkov@3.2.269
+ - terrascan@1.19.9
+ - trivy@0.56.2
#- trufflehog@3.63.2-rc0
- taplo@0.9.3
- - ruff@0.6.2
+ - ruff@0.7.1
- isort@5.13.2
- - markdownlint@0.41.0
+ - markdownlint@0.42.0
- oxipng@9.1.2
- svgo@3.3.2
- - actionlint@1.7.1
+ - actionlint@1.7.3
- flake8@7.1.1
- hadolint@2.12.0
- shfmt@3.6.0
- shellcheck@0.10.0
- - black@24.8.0
+ - black@24.10.0
- git-diff-check
- - gitleaks@8.18.4
+ - gitleaks@8.21.1
- clang-format@16.0.3
- prettier@3.3.3
ignore:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 000000000..f579a7fe0
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,47 @@
+# Contributing to Meshtastic Firmware
+
+We're excited that you're interested in contributing to the Meshtastic firmware! This document provides a high-level overview of how you can get involved.
+
+## Important First Steps
+
+Before you begin, please:
+
+1. **Read our documentation**: Our [official documentation](https://meshtastic.org/docs/) is a crucial resource. It contains essential information about the project.
+
+2. **Check out the firmware build guide**: For specific instructions on setting up your development environment and building the firmware, refer to our [Firmware Build Guide](https://meshtastic.org/docs/development/firmware/build/).
+
+3. Read our [Code of Conduct](https://meshtastic.org/docs/legal/conduct/)
+
+4. Join our [Discord community](https://discord.com/invite/ktMAKGBnBs) to connect with developers and other contributors to get help.
+
+## Getting Help and Discussing Ideas
+
+We encourage open communication and discussion before diving into code changes:
+
+1. **Use GitHub Discussions**: For new ideas, questions, or to discuss potential changes, start a conversation in our [GitHub Discussions](https://github.com/meshtastic/firmware/discussions) first. This helps us collaborate and avoid duplicate work.
+
+2. **Join our Discord**: For real-time chat and quick questions, join our [Discord server](https://discord.com/invite/ktMAKGBnBs). It's a great place to get help and connect with other developers and the community.
+
+3. **Reporting Issues**: If you've identified a bug, please use our bug report template when creating a new issue in the [issue tracker](https://github.com/meshtastic/firmware/issues). Ensure you've searched existing issues to avoid duplicates.
+
+## Making Contributions
+
+> [!IMPORTANT]
+> Before making any contributions, you must sign our Contributor License Agreement (CLA). You can do this by visiting https://cla-assistant.io/meshtastic/firmware. Be sure to use the GitHub account you will use to submit your contributions when signing.
+
+1. Fork the repository
+2. Create a new branch for your feature or bug fix
+3. Make your changes
+4. Test your changes thoroughly
+5. Create a pull request with a clear description, using the provided template, of your changes. Be sure to enable "Allow edits from maintainers".
+
+## Coding Standards
+
+To ensure consistent code formatting across the project:
+
+1. Install the [Trunk](https://marketplace.visualstudio.com/items?itemName=Trunk.io) extension for Visual Studio Code.
+2. Before submitting your changes, run `trunk fmt` to automatically format your code according to our standards.
+
+Adhering to these formatting guidelines helps maintain code consistency and makes the review process smoother.
+
+Thank you for contributing to Meshtastic!
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..fb4d9005a
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,12 @@
+# Security Policy
+
+## Supported Versions
+
+| Firmware Version | Supported |
+| ---------------- | ------------------ |
+| 2.5.x | :white_check_mark: |
+| <= 2.4.x | :x: |
+
+## Reporting a Vulnerability
+
+We support the private reporting of potential security vulnerabilities. Please go to the Security tab to file a report with a description of the potential vulnerability and reproduction scripts (preferred) or steps, and our developers will review.
diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini
index 0dd6cbc1d..382975e9f 100644
--- a/arch/esp32/esp32.ini
+++ b/arch/esp32/esp32.ini
@@ -2,10 +2,10 @@
[esp32_base]
extends = arduino_base
custom_esp32_kind = esp32
-platform = platformio/espressif32@6.7.0
+platform = platformio/espressif32@6.9.0
build_src_filter =
- ${arduino_base.build_src_filter} - - - - -
+ ${arduino_base.build_src_filter} - - - - -
upload_speed = 921600
debug_init_break = tbreak setup
@@ -31,7 +31,7 @@ build_flags =
-DCONFIG_BT_NIMBLE_ENABLED
-DCONFIG_NIMBLE_CPP_LOG_LEVEL=2
-DCONFIG_BT_NIMBLE_MAX_CCCDS=20
- -DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=5120
+ -DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192
-DESP_OPENSSL_SUPPRESS_LEGACY_WARNING
-DSERIAL_BUFFER_SIZE=4096
-DLIBPAX_ARDUINO
@@ -46,7 +46,7 @@ lib_deps =
https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2
h2zero/NimBLE-Arduino@^1.4.2
https://github.com/dbSuS/libpax.git#7bcd3fcab75037505be9b122ab2b24cc5176b587
- https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6
+ lewisxhe/XPowersLib@^0.2.6
https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f
rweather/Crypto@^0.4.0
diff --git a/arch/esp32/esp32c6.ini b/arch/esp32/esp32c6.ini
new file mode 100644
index 000000000..53d7f92ec
--- /dev/null
+++ b/arch/esp32/esp32c6.ini
@@ -0,0 +1,40 @@
+[esp32c6_base]
+extends = esp32_base
+platform = https://github.com/Jason2866/platform-espressif32.git#22faa566df8c789000f8136cd8d0aca49617af55
+build_flags =
+ ${arduino_base.build_flags}
+ -Wall
+ -Wextra
+ -Isrc/platform/esp32
+ -std=c++11
+ -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING
+ -DSERIAL_BUFFER_SIZE=4096
+ -DLIBPAX_ARDUINO
+ -DLIBPAX_WIFI
+ -DLIBPAX_BLE
+ -DMESHTASTIC_EXCLUDE_WEBSERVER
+ ;-DDEBUG_HEAP
+ ; TEMP
+ -DHAS_BLUETOOTH=0
+ -DMESHTASTIC_EXCLUDE_PAXCOUNTER
+ -DMESHTASTIC_EXCLUDE_BLUETOOTH
+
+lib_deps =
+ ${arduino_base.lib_deps}
+ ${networking_base.lib_deps}
+ ${environmental_base.lib_deps}
+ lewisxhe/XPowersLib@^0.2.6
+ https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f
+ rweather/Crypto@^0.4.0
+
+build_src_filter =
+ ${esp32_base.build_src_filter} -
+
+monitor_speed = 460800
+monitor_filters = esp32_c3_exception_decoder
+
+lib_ignore =
+ NonBlockingRTTTL
+ NimBLE-Arduino
+ libpax
+
\ No newline at end of file
diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini
index 503da2aab..1af68198b 100644
--- a/arch/nrf52/nrf52.ini
+++ b/arch/nrf52/nrf52.ini
@@ -16,11 +16,12 @@ build_flags =
-DLFS_NO_ASSERT ; Disable LFS assertions , see https://github.com/meshtastic/firmware/pull/3818
build_src_filter =
- ${arduino_base.build_src_filter} - - - - - - - - - -
+ ${arduino_base.build_src_filter} - - - - - - - - - -
lib_deps=
${arduino_base.lib_deps}
rweather/Crypto@^0.4.0
lib_ignore =
- BluetoothOTA
\ No newline at end of file
+ BluetoothOTA
+ lvgl
diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini
index 482b1f9c5..7eb563d77 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#ad8112adf82ce1f5b917092cf32be07a077801a0
+platform = https://github.com/meshtastic/platform-native.git#6b3796d697481c8f6e3f4aa5c111bd9979f29e64
framework = arduino
build_src_filter =
@@ -9,7 +9,7 @@ build_src_filter =
-
-
-
- -
+ -
-
-
+
@@ -24,7 +24,7 @@ lib_deps =
${env.lib_deps}
${networking_base.lib_deps}
rweather/Crypto@^0.4.0
- https://github.com/lovyan03/LovyanGFX.git#5a39989aa2c9492572255b22f033843ec8900233
+ https://github.com/lovyan03/LovyanGFX.git#1401c28a47646fe00538d487adcb2eb3c72de805
build_flags =
${arduino_base.build_flags}
diff --git a/arch/rp2xx0/rp2040.ini b/arch/rp2xx0/rp2040.ini
new file mode 100644
index 000000000..c004a3bec
--- /dev/null
+++ b/arch/rp2xx0/rp2040.ini
@@ -0,0 +1,25 @@
+; Common settings for rp2040 Processor based targets
+[rp2040_base]
+platform = https://github.com/maxgerhardt/platform-raspberrypi.git#v1.2.0-gcc12
+extends = arduino_base
+platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#4.0.3
+
+board_build.core = earlephilhower
+board_build.filesystem_size = 0.5m
+build_flags =
+ ${arduino_base.build_flags} -Wno-unused-variable -Wcast-align
+ -Isrc/platform/rp2xx0
+ -Isrc/platform/rp2xx0/hardware_rosc/include
+ -Isrc/platform/rp2xx0/pico_sleep/include
+ -D__PLAT_RP2040__
+# -D _POSIX_THREADS
+build_src_filter =
+ ${arduino_base.build_src_filter} - - - - - - - - -
+
+lib_ignore =
+ BluetoothOTA
+
+lib_deps =
+ ${arduino_base.lib_deps}
+ ${environmental_base.lib_deps}
+ rweather/Crypto
\ No newline at end of file
diff --git a/arch/rp2040/rp2040.ini b/arch/rp2xx0/rp2350.ini
similarity index 78%
rename from arch/rp2040/rp2040.ini
rename to arch/rp2xx0/rp2350.ini
index dd3a4d7ff..7ef6332e3 100644
--- a/arch/rp2040/rp2040.ini
+++ b/arch/rp2xx0/rp2350.ini
@@ -1,14 +1,14 @@
; Common settings for rp2040 Processor based targets
-[rp2040_base]
-platform = https://github.com/maxgerhardt/platform-raspberrypi.git#60d6ae81fcc73c34b1493ca9e261695e471bc0c2
+[rp2350_base]
+platform = https://github.com/maxgerhardt/platform-raspberrypi.git#9e55f6db5c56b9867c69fe473f388beea4546672
extends = arduino_base
-platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#3.7.2
+platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#a6ab6e1f95bc1428d667d55ea7173c0744acc03c ; 4.0.2+
board_build.core = earlephilhower
board_build.filesystem_size = 0.5m
build_flags =
${arduino_base.build_flags} -Wno-unused-variable
- -Isrc/platform/rp2040
+ -Isrc/platform/rp2xx0
-D__PLAT_RP2040__
# -D _POSIX_THREADS
build_src_filter =
@@ -16,8 +16,9 @@ build_src_filter =
lib_ignore =
BluetoothOTA
+ lvgl
lib_deps =
${arduino_base.lib_deps}
${environmental_base.lib_deps}
- rweather/Crypto
\ No newline at end of file
+ rweather/Crypto
diff --git a/arch/stm32/stm32.ini b/arch/stm32/stm32.ini
index 050dbf7f0..715e8aa73 100644
--- a/arch/stm32/stm32.ini
+++ b/arch/stm32/stm32.ini
@@ -1,7 +1,7 @@
[stm32_base]
extends = arduino_base
platform = ststm32
-platform_packages = platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32.git#361a7fdb67e2a7104e99b4f42a802469eef8b129
+platform_packages = platformio/framework-arduinoststm32@https://github.com/stm32duino/Arduino_Core_STM32.git#ea74156acd823b6d14739f389e6cdc648f8ee36e
build_type = release
@@ -22,7 +22,7 @@ build_flags =
-fdata-sections
build_src_filter =
- ${arduino_base.build_src_filter} - - - - - - - - - - - - - -
+ ${arduino_base.build_src_filter} - - - - - - - - - - - - - -
board_upload.offset_address = 0x08000000
upload_protocol = stlink
@@ -33,5 +33,5 @@ lib_deps =
https://github.com/caveman99/Crypto.git#f61ae26a53f7a2d0ba5511625b8bf8eff3a35d5e
lib_ignore =
- https://github.com/mathertel/OneButton@~2.6.1
+ mathertel/OneButton@~2.6.1
Wire
\ No newline at end of file
diff --git a/bin/build-userprefs-json.py b/bin/build-userprefs-json.py
new file mode 100644
index 000000000..58f460bcf
--- /dev/null
+++ b/bin/build-userprefs-json.py
@@ -0,0 +1,48 @@
+import json
+import subprocess
+import re
+
+def get_macros_from_header(header_file):
+ # Run clang to preprocess the header file and capture the output
+ result = subprocess.run(['clang', '-E', '-dM', header_file], capture_output=True, text=True)
+ if result.returncode != 0:
+ raise RuntimeError(f"Clang preprocessing failed: {result.stderr}")
+
+ # Extract macros from the output
+ macros = {}
+ macro_pattern = re.compile(r'#define\s+(\w+)\s+(.*)')
+ for line in result.stdout.splitlines():
+ match = macro_pattern.match(line)
+ if match and 'USERPREFS_' in line and '_USERPREFS_' not in line:
+ macros[match.group(1)] = match.group(2)
+
+ return macros
+
+def write_macros_to_json(macros, output_file):
+ with open(output_file, 'w') as f:
+ json.dump(macros, f, indent=4)
+
+def main():
+ header_file = 'userPrefs.h'
+ output_file = 'userPrefs.json'
+ # Uncomment all macros in the header file
+ with open(header_file, 'r') as file:
+ lines = file.readlines()
+
+ uncommented_lines = []
+ for line in lines:
+ stripped_line = line.strip().replace('/*', '').replace('*/', '')
+ if stripped_line.startswith('//') and 'USERPREFS_' in stripped_line:
+ # Replace "//"
+ stripped_line = stripped_line.replace('//', '')
+ uncommented_lines.append(stripped_line + '\n')
+
+ with open(header_file, 'w') as file:
+ for line in uncommented_lines:
+ file.write(line)
+ macros = get_macros_from_header(header_file)
+ write_macros_to_json(macros, output_file)
+ print(f"Macros have been written to {output_file}")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml
index 5590ed3b0..bf1331a2b 100644
--- a/bin/config-dist.yaml
+++ b/bin/config-dist.yaml
@@ -1,14 +1,11 @@
+### Many device configs have been moved to /etc/meshtasticd/available.d
+### To activate, simply copy or link the appropriate file into /etc/meshtasticd/config.d
+
### Define your devices here using Broadcom pin numbering
### Uncomment the block that corresponds to your hardware
### Including the "Module:" line!
---
Lora:
-# Module: sx1262 # Waveshare SX126X XXXM
-# DIO2_AS_RF_SWITCH: true
-# CS: 21
-# IRQ: 16
-# Busy: 20
-# Reset: 18
# Module: sx1262 # Waveshare SX1302 LISTEN ONLY AT THIS TIME!
# CS: 7
@@ -19,6 +16,7 @@ Lora:
# CS: 0
# IRQ: 10
# Busy: 11
+# DIO2_AS_RF_SWITCH: true
# spidev: spidev0.1
# Module: RF95 # Adafruit RFM9x
@@ -113,6 +111,29 @@ Display:
# Height: 320
# Rotate: true
+### SHCHV 3.5 RPi TFT+Touchscreen
+# Panel: ILI9486
+# spidev: spidev0.0
+# BusFrequency: 30000000
+# DC: 24
+# Reset: 25
+# Width: 320
+# Height: 480
+# OffsetRotate: 2
+
+### TZT 2.0 Inch TFT Display ST7789V 240RGBx320
+# Panel: ST7789
+# spidev: spidev0.0
+# # CS: 8 # can be freely chosen
+# BusFrequency: 80000000
+# DC: 24 # can be freely chosen
+# Width: 320
+# Height: 240
+# Reset: 25 # can be freely chosen
+# Rotate: true
+# OffsetRotate: 1
+# Invert: true
+
### You can also specify the spi device for the display to use
# spidev: spidev0.0
@@ -154,3 +175,4 @@ Webserver:
General:
MaxNodes: 200
MaxMessageQueue: 100
+ ConfigDirectory: /etc/meshtasticd/config.d/
\ No newline at end of file
diff --git a/bin/config.d/display-waveshare-2.8.yaml b/bin/config.d/display-waveshare-2.8.yaml
new file mode 100644
index 000000000..2e28276d8
--- /dev/null
+++ b/bin/config.d/display-waveshare-2.8.yaml
@@ -0,0 +1,18 @@
+Display:
+
+### Waveshare 2.8inch RPi LCD
+ Panel: ST7789
+ CS: 8
+ DC: 22 # Data/Command pin
+ Backlight: 18
+ Width: 240
+ Height: 320
+ Reset: 27
+ Rotate: true
+ Invert: 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: XPT2046 # Waveshare 2.8inch
+ CS: 7
+ IRQ: 17
\ No newline at end of file
diff --git a/bin/config.d/lora-waveshare-sxxx.yaml b/bin/config.d/lora-waveshare-sxxx.yaml
new file mode 100644
index 000000000..a9ff13653
--- /dev/null
+++ b/bin/config.d/lora-waveshare-sxxx.yaml
@@ -0,0 +1,8 @@
+Lora:
+ Module: sx1262 # Waveshare SX126X XXXM
+ DIO2_AS_RF_SWITCH: true
+ CS: 21
+ IRQ: 16
+ Busy: 20
+ Reset: 18
+ SX126X_ANT_SW: 6
diff --git a/bin/generate_ci_matrix.py b/bin/generate_ci_matrix.py
index 46398dd59..4d8759ecc 100755
--- a/bin/generate_ci_matrix.py
+++ b/bin/generate_ci_matrix.py
@@ -6,6 +6,7 @@ import configparser
import json
import os
import sys
+import random
rootdir = "variants/"
@@ -39,5 +40,7 @@ for subdir, dirs, files in os.walk(rootdir):
"check" in options
):
outlist.append(section)
-
-print(json.dumps(outlist))
+if ("quick" in options) & (len(outlist) > 3):
+ print(json.dumps(random.sample(outlist, 3)))
+else:
+ print(json.dumps(outlist))
diff --git a/bin/meshtasticd.service b/bin/meshtasticd.service
index f15fdc871..1e8ee98b8 100644
--- a/bin/meshtasticd.service
+++ b/bin/meshtasticd.service
@@ -1,12 +1,16 @@
[Unit]
Description=Meshtastic Native Daemon
After=network-online.target
+StartLimitInterval=200
+StartLimitBurst=5
[Service]
User=root
Group=root
Type=simple
ExecStart=/usr/sbin/meshtasticd
+Restart=always
+RestartSec=3
[Install]
-WantedBy=multi-user.target
\ No newline at end of file
+WantedBy=multi-user.target
diff --git a/bin/native-install.sh b/bin/native-install.sh
index ba71c4f46..a8fcc29a6 100755
--- a/bin/native-install.sh
+++ b/bin/native-install.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
cp "release/meshtasticd_linux_$(uname -m)" /usr/sbin/meshtasticd
-mkdir /etc/meshtasticd
+mkdir -p /etc/meshtasticd
if [[ -f "/etc/meshtasticd/config.yaml" ]]; then
cp bin/config-dist.yaml /etc/meshtasticd/config-upgrade.yaml
else
diff --git a/bin/regen-protos.bat b/bin/regen-protos.bat
index f28ef0025..7fa8f333d 100644
--- a/bin/regen-protos.bat
+++ b/bin/regen-protos.bat
@@ -1 +1 @@
-cd protobufs && ..\nanopb-0.4.8\generator-bin\protoc.exe --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:..\src\mesh\generated" -I=..\protobufs\ ..\protobufs\meshtastic\*.proto
+cd protobufs && ..\nanopb-0.4.9\generator-bin\protoc.exe --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:..\src\mesh\generated" -I=..\protobufs\ ..\protobufs\meshtastic\*.proto
diff --git a/bin/regen-protos.sh b/bin/regen-protos.sh
index 2e60784e3..12546bfdc 100755
--- a/bin/regen-protos.sh
+++ b/bin/regen-protos.sh
@@ -2,10 +2,10 @@
set -e
-echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.8 to be located in the"
+echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.9 to be located in the"
echo "firmware root directory if the following step fails, you should download the correct"
-echo "prebuilt binaries for your computer into nanopb-0.4.8"
+echo "prebuilt binaries for your computer into nanopb-0.4.9"
# the nanopb tool seems to require that the .options file be in the current directory!
cd protobufs
-../nanopb-0.4.8/generator-bin/protoc --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:../src/mesh/generated/" -I=../protobufs meshtastic/*.proto
+../nanopb-0.4.9/generator-bin/protoc --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:../src/mesh/generated/" -I=../protobufs meshtastic/*.proto
diff --git a/boards/bpi_picow_esp32_s3.json b/boards/bpi_picow_esp32_s3.json
index 9a20dd57f..75983d845 100644
--- a/boards/bpi_picow_esp32_s3.json
+++ b/boards/bpi_picow_esp32_s3.json
@@ -28,6 +28,8 @@
"flash_size": "8MB",
"maximum_ram_size": 327680,
"maximum_size": 8388608,
+ "use_1200bps_touch": true,
+ "wait_for_upload_port": true,
"require_upload_port": true,
"speed": 921600
},
diff --git a/boards/heltec_mesh_node_t114.json b/boards/heltec_mesh_node_t114.json
index 5c97d8c75..2bd306eb9 100644
--- a/boards/heltec_mesh_node_t114.json
+++ b/boards/heltec_mesh_node_t114.json
@@ -5,7 +5,7 @@
},
"core": "nRF5",
"cpu": "cortex-m4",
- "extra_flags": "-DARDUINO_NRF52840_PCA10056 -DNRF52840_XXAA",
+ "extra_flags": "-DHELTEC_T114 -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x4405"],
diff --git a/boards/icarus.json b/boards/icarus.json
new file mode 100644
index 000000000..03da4682f
--- /dev/null
+++ b/boards/icarus.json
@@ -0,0 +1,41 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "esp32s3_out.ld",
+ "memory_type": "qio_opi"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-DBOARD_HAS_PSRAM",
+ "-DARDUINO_USB_CDC_ON_BOOT=1",
+ "-DARDUINO_USB_MODE=0",
+ "-DARDUINO_RUNNING_CORE=1",
+ "-DARDUINO_EVENT_RUNNING_CORE=0"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "80000000L",
+ "flash_mode": "qio",
+ "hwids": [["0x2886", "0x0059"]],
+ "mcu": "esp32s3",
+ "variant": "icarus"
+ },
+ "connectivity": ["wifi", "bluetooth", "lora"],
+ "debug": {
+ "default_tool": "esp-builtin",
+ "onboard_tools": ["esp-builtin"],
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": ["arduino", "espidf"],
+ "name": "icarus",
+ "upload": {
+ "flash_size": "8MB",
+ "maximum_ram_size": 8388608,
+ "maximum_size": 8388608,
+ "use_1200bps_touch": true,
+ "wait_for_upload_port": true,
+ "require_upload_port": true,
+ "speed": 921600
+ },
+ "url": "https://icarus.azlan.works",
+ "vendor": "Muhammad Shah"
+}
diff --git a/boards/lora-relay-v1.json b/boards/lora-relay-v1.json
deleted file mode 100644
index b390b8404..000000000
--- a/boards/lora-relay-v1.json
+++ /dev/null
@@ -1,47 +0,0 @@
-{
- "build": {
- "arduino": {
- "ldscript": "nrf52840_s140_v6.ld"
- },
- "core": "nRF5",
- "cpu": "cortex-m4",
- "extra_flags": "-DARDUINO_NRF52840_LORA_RELAY_V1 -DNRF52840_XXAA",
- "f_cpu": "64000000L",
- "hwids": [["0x239A", "0x4404"]],
- "usb_product": "LORA_RELAY",
- "mcu": "nrf52840",
- "variant": "lora_relay_v1",
- "variants_dir": "variants",
- "bsp": {
- "name": "adafruit"
- },
- "softdevice": {
- "sd_flags": "-DS140",
- "sd_name": "s140",
- "sd_version": "6.1.1",
- "sd_fwid": "0x00B6"
- },
- "bootloader": {
- "settings_addr": "0xFF000"
- }
- },
- "connectivity": ["bluetooth"],
- "debug": {
- "jlink_device": "nRF52840_xxAA",
- "onboard_tools": ["jlink"],
- "svd_path": "nrf52840.svd",
- "openocd_target": "nrf52840-mdk-rs"
- },
- "frameworks": ["arduino"],
- "name": "Meshtastic Lora Relay V1 (Adafruit BSP)",
- "upload": {
- "maximum_ram_size": 248832,
- "maximum_size": 815104,
- "require_upload_port": true,
- "speed": 115200,
- "protocol": "jlink",
- "protocols": ["jlink", "nrfjprog", "stlink"]
- },
- "url": "https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay",
- "vendor": "BigCorvus"
-}
diff --git a/boards/lora-relay-v2.json b/boards/lora-relay-v2.json
deleted file mode 100644
index 52b775e58..000000000
--- a/boards/lora-relay-v2.json
+++ /dev/null
@@ -1,47 +0,0 @@
-{
- "build": {
- "arduino": {
- "ldscript": "nrf52840_s140_v6.ld"
- },
- "core": "nRF5",
- "cpu": "cortex-m4",
- "extra_flags": "-DARDUINO_NRF52840_LORA_RELAY_V2 -DNRF52840_XXAA",
- "f_cpu": "64000000L",
- "hwids": [["0x239A", "0x4406"]],
- "usb_product": "LORA_RELAY",
- "mcu": "nrf52840",
- "variant": "lora_relay_v2",
- "variants_dir": "variants",
- "bsp": {
- "name": "adafruit"
- },
- "softdevice": {
- "sd_flags": "-DS140",
- "sd_name": "s140",
- "sd_version": "6.1.1",
- "sd_fwid": "0x00B6"
- },
- "bootloader": {
- "settings_addr": "0xFF000"
- }
- },
- "connectivity": ["bluetooth"],
- "debug": {
- "jlink_device": "nRF52840_xxAA",
- "onboard_tools": ["jlink"],
- "svd_path": "nrf52840.svd",
- "openocd_target": "nrf52840-mdk-rs"
- },
- "frameworks": ["arduino"],
- "name": "Meshtastic Lora Relay V1 (Adafruit BSP)",
- "upload": {
- "maximum_ram_size": 248832,
- "maximum_size": 815104,
- "require_upload_port": true,
- "speed": 115200,
- "protocol": "jlink",
- "protocols": ["jlink", "nrfjprog", "stlink"]
- },
- "url": "https://github.com/BigCorvus/SX1262-LoRa-BLE-Relay",
- "vendor": "BigCorvus"
-}
diff --git a/boards/lora_isp4520.json b/boards/lora_isp4520.json
deleted file mode 100644
index 8125aa666..000000000
--- a/boards/lora_isp4520.json
+++ /dev/null
@@ -1,40 +0,0 @@
-{
- "build": {
- "arduino": {
- "ldscript": "nrf52832_s132_v6.ld"
- },
- "core": "nRF5",
- "cpu": "cortex-m4",
- "extra_flags": "-DNRF52832_XXAA -DNRF52",
- "f_cpu": "64000000L",
- "mcu": "nrf52832",
- "variant": "lora_isp4520",
- "bsp": {
- "name": "adafruit"
- },
- "softdevice": {
- "sd_flags": "-DS132",
- "sd_name": "s132",
- "sd_version": "6.1.1",
- "sd_fwid": "0x00B7"
- }
- },
- "connectivity": ["bluetooth"],
- "debug": {
- "jlink_device": "nRF52832_xxAA",
- "svd_path": "nrf52.svd",
- "openocd_target": "nrf52840-mdk-rs"
- },
- "frameworks": ["arduino"],
- "name": "lora ISP4520",
- "upload": {
- "maximum_ram_size": 65536,
- "maximum_size": 524288,
- "require_upload_port": true,
- "speed": 115200,
- "protocol": "nrfutil",
- "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"]
- },
- "url": "",
- "vendor": "PsiSoft"
-}
diff --git a/boards/me25ls01-4y10td.json b/boards/me25ls01-4y10td.json
index 46c526a7c..9e1d63265 100644
--- a/boards/me25ls01-4y10td.json
+++ b/boards/me25ls01-4y10td.json
@@ -5,7 +5,7 @@
},
"core": "nRF5",
"cpu": "cortex-m4",
- "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA",
+ "extra_flags": "-DME25LS01_4Y10TD -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
diff --git a/boards/ms24sf1.json b/boards/ms24sf1.json
index 4e28507da..8356e3012 100644
--- a/boards/ms24sf1.json
+++ b/boards/ms24sf1.json
@@ -5,7 +5,7 @@
},
"core": "nRF5",
"cpu": "cortex-m4",
- "extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA",
+ "extra_flags": "-DMS24SF1 -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
diff --git a/boards/nordic_pca10059.json b/boards/nordic_pca10059.json
index b99e3c763..6540817a2 100644
--- a/boards/nordic_pca10059.json
+++ b/boards/nordic_pca10059.json
@@ -5,7 +5,7 @@
},
"core": "nRF5",
"cpu": "cortex-m4",
- "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
+ "extra_flags": "-DNORDIC_PCA10059 -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
diff --git a/boards/nrf52840_dk_modified.json b/boards/nrf52840_dk_modified.json
deleted file mode 100644
index 2932cb4b9..000000000
--- a/boards/nrf52840_dk_modified.json
+++ /dev/null
@@ -1,47 +0,0 @@
-{
- "build": {
- "arduino": {
- "ldscript": "nrf52840_s113_v7.ld"
- },
- "core": "nRF5",
- "cpu": "cortex-m4",
- "extra_flags": "-DARDUINO_NRF52840_PCA10056 -DNRF52840_XXAA",
- "f_cpu": "64000000L",
- "hwids": [["0x239A", "0x4404"]],
- "usb_product": "nrf52840dk",
- "mcu": "nrf52840",
- "variant": "pca10056-rc-clock",
- "variants_dir": "variants",
- "bsp": {
- "name": "adafruit"
- },
- "softdevice": {
- "sd_flags": "-DS140",
- "sd_name": "s140",
- "sd_version": "6.1.1",
- "sd_fwid": "0x00B6"
- },
- "bootloader": {
- "settings_addr": "0xFF000"
- }
- },
- "connectivity": ["bluetooth"],
- "debug": {
- "jlink_device": "nRF52840_xxAA",
- "onboard_tools": ["jlink"],
- "svd_path": "nrf52840.svd",
- "openocd_target": "nrf52840-mdk-rs"
- },
- "frameworks": ["arduino"],
- "name": "A modified NRF52840-DK devboard (Adafruit BSP)",
- "upload": {
- "maximum_ram_size": 248832,
- "maximum_size": 815104,
- "require_upload_port": true,
- "speed": 115200,
- "protocol": "jlink",
- "protocols": ["jlink", "nrfjprog", "stlink"]
- },
- "url": "https://meshtastic.org/",
- "vendor": "Nordic Semi"
-}
diff --git a/boards/ppr.json b/boards/ppr.json
deleted file mode 100644
index 15e3025c0..000000000
--- a/boards/ppr.json
+++ /dev/null
@@ -1,47 +0,0 @@
-{
- "build": {
- "arduino": {
- "ldscript": "nrf52840_s140_v6.ld"
- },
- "core": "nRF5",
- "cpu": "cortex-m4",
- "extra_flags": "-DARDUINO_NRF52840_PPR -DNRF52840_XXAA",
- "f_cpu": "64000000L",
- "hwids": [["0x239A", "0x4403"]],
- "usb_product": "PPR",
- "mcu": "nrf52840",
- "variant": "ppr",
- "variants_dir": "variants",
- "bsp": {
- "name": "adafruit"
- },
- "softdevice": {
- "sd_flags": "-DS140",
- "sd_name": "s140",
- "sd_version": "6.1.1",
- "sd_fwid": "0x00B6"
- },
- "bootloader": {
- "settings_addr": "0xFF000"
- }
- },
- "connectivity": ["bluetooth"],
- "debug": {
- "jlink_device": "nRF52840_xxAA",
- "onboard_tools": ["jlink"],
- "svd_path": "nrf52840.svd",
- "openocd_target": "nrf52840-mdk-rs"
- },
- "frameworks": ["arduino"],
- "name": "Meshtastic PPR (Adafruit BSP)",
- "upload": {
- "maximum_ram_size": 248832,
- "maximum_size": 815104,
- "require_upload_port": true,
- "speed": 115200,
- "protocol": "jlink",
- "protocols": ["jlink", "nrfjprog", "stlink"]
- },
- "url": "https://meshtastic.org/",
- "vendor": "Othernet"
-}
diff --git a/boards/ppr1.json b/boards/ppr1.json
deleted file mode 100644
index 35bf7d1e4..000000000
--- a/boards/ppr1.json
+++ /dev/null
@@ -1,47 +0,0 @@
-{
- "build": {
- "arduino": {
- "ldscript": "nrf52833_s113_v7.ld"
- },
- "core": "nRF5",
- "cpu": "cortex-m4",
- "extra_flags": "-DARDUINO_NRF52833_PPR -DNRF52833_XXAA",
- "f_cpu": "64000000L",
- "hwids": [["0x239A", "0x4406"]],
- "usb_product": "PPR",
- "mcu": "nrf52833",
- "variant": "ppr",
- "variants_dir": "variants",
- "bsp": {
- "name": "adafruit"
- },
- "softdevice": {
- "sd_flags": "-DS113",
- "sd_name": "s113",
- "sd_version": "7.2.0",
- "sd_fwid": "0x00b6"
- },
- "bootloader": {
- "settings_addr": "0xFF000"
- }
- },
- "connectivity": ["bluetooth"],
- "debug": {
- "jlink_device": "nRF52833_xxAA",
- "onboard_tools": ["jlink"],
- "svd_path": "nrf52833.svd",
- "openocd_target": "nrf52840-mdk-rs"
- },
- "frameworks": ["arduino"],
- "name": "Meshtastic PPR1 (Adafruit BSP)",
- "upload": {
- "maximum_ram_size": 248832,
- "maximum_size": 815104,
- "require_upload_port": true,
- "speed": 115200,
- "protocol": "jlink",
- "protocols": ["jlink", "nrfjprog", "stlink"]
- },
- "url": "https://meshtastic.org/",
- "vendor": "Othernet"
-}
diff --git a/boards/seeed-sensecap-indicator.json b/boards/seeed-sensecap-indicator.json
new file mode 100644
index 000000000..3fc57126f
--- /dev/null
+++ b/boards/seeed-sensecap-indicator.json
@@ -0,0 +1,42 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "esp32s3_out.ld",
+ "partitions": "default_8MB.csv",
+ "memory_type": "qio_opi"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-DBOARD_HAS_PSRAM",
+ "-DARDUINO_USB_CDC_ON_BOOT=0",
+ "-DARDUINO_USB_MODE=1",
+ "-DARDUINO_RUNNING_CORE=1",
+ "-DARDUINO_EVENT_RUNNING_CORE=1"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "80000000L",
+ "flash_mode": "qio",
+ "hwids": [["0x1A86", "0x7523"]],
+ "mcu": "esp32s3",
+ "variant": "esp32s3r8"
+ },
+ "connectivity": ["wifi", "bluetooth", "lora"],
+ "debug": {
+ "default_tool": "esp-builtin",
+ "onboard_tools": ["esp-builtin"],
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": ["arduino"],
+ "name": "Seeed Studio SenseCAP Indicator",
+ "upload": {
+ "flash_size": "8MB",
+ "maximum_ram_size": 327680,
+ "maximum_size": 8388608,
+ "require_upload_port": true,
+ "use_1200bps_touch": true,
+ "wait_for_upload_port": true,
+ "speed": 921600
+ },
+ "url": "https://www.seeedstudio.com/Indicator-for-Meshtastic.html",
+ "vendor": "Seeed Studio"
+}
diff --git a/boards/seeed-xiao-s3.json b/boards/seeed-xiao-s3.json
new file mode 100644
index 000000000..0b7b432a0
--- /dev/null
+++ b/boards/seeed-xiao-s3.json
@@ -0,0 +1,41 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "esp32s3_out.ld",
+ "memory_type": "qio_opi"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-DBOARD_HAS_PSRAM",
+ "-DARDUINO_USB_CDC_ON_BOOT=1",
+ "-DARDUINO_USB_MODE=0",
+ "-DARDUINO_RUNNING_CORE=1",
+ "-DARDUINO_EVENT_RUNNING_CORE=0"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "80000000L",
+ "flash_mode": "qio",
+ "hwids": [["0x2886", "0x0059"]],
+ "mcu": "esp32s3",
+ "variant": "seeed-xiao-s3"
+ },
+ "connectivity": ["wifi", "bluetooth", "lora"],
+ "debug": {
+ "default_tool": "esp-builtin",
+ "onboard_tools": ["esp-builtin"],
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": ["arduino", "espidf"],
+ "name": "seeed-xiao-s3",
+ "upload": {
+ "flash_size": "8MB",
+ "maximum_ram_size": 8388608,
+ "maximum_size": 8388608,
+ "use_1200bps_touch": true,
+ "wait_for_upload_port": true,
+ "require_upload_port": true,
+ "speed": 921600
+ },
+ "url": "https://www.seeedstudio.com/XIAO-ESP32S3-Sense-p-5639.html",
+ "vendor": "Seeed Studio"
+}
diff --git a/boards/unphone.json b/boards/unphone.json
new file mode 100644
index 000000000..bf711993c
--- /dev/null
+++ b/boards/unphone.json
@@ -0,0 +1,46 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "esp32s3_out.ld",
+ "memory_type": "qio_opi",
+ "partitions": "default_8MB.csv"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-DBOARD_HAS_PSRAM",
+ "-DUNPHONE_SPIN=9",
+ "-DARDUINO_USB_CDC_ON_BOOT=1",
+ "-DARDUINO_USB_MODE=0",
+ "-DARDUINO_RUNNING_CORE=1",
+ "-DARDUINO_EVENT_RUNNING_CORE=1"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "80000000L",
+ "flash_mode": "qio",
+ "hwids": [
+ ["0x16D0", "0x1178"],
+ ["0x303a", "0x1001"]
+ ],
+ "mcu": "esp32s3",
+ "variant": "unphone"
+ },
+ "connectivity": ["wifi", "bluetooth", "lora"],
+ "debug": {
+ "default_tool": "esp-builtin",
+ "onboard_tools": ["esp-builtin"],
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": ["arduino", "espidf"],
+ "name": "unPhone",
+ "upload": {
+ "flash_size": "8MB",
+ "maximum_ram_size": 327680,
+ "maximum_size": 8323072,
+ "use_1200bps_touch": true,
+ "wait_for_upload_port": true,
+ "require_upload_port": true,
+ "speed": 921600
+ },
+ "url": "https://unphone.net/",
+ "vendor": "University of Sheffield"
+}
diff --git a/images/compass.png b/images/compass.png
index 8639dde52..c4e5b589b 100644
Binary files a/images/compass.png and b/images/compass.png differ
diff --git a/images/face-24px.svg b/images/face-24px.svg
index 293468664..08e8f5ffc 100644
--- a/images/face-24px.svg
+++ b/images/face-24px.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/face.png b/images/face.png
index 036e29f73..eb6b91e13 100644
Binary files a/images/face.png and b/images/face.png differ
diff --git a/images/location_searching-24px.svg b/images/location_searching-24px.svg
index 7f72a31bc..abef0ef78 100644
--- a/images/location_searching-24px.svg
+++ b/images/location_searching-24px.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/pin.png b/images/pin.png
index 112a7ce8e..3c91a726c 100644
Binary files a/images/pin.png and b/images/pin.png differ
diff --git a/images/room-24px.svg b/images/room-24px.svg
index 79a4807e7..48e55ab80 100644
--- a/images/room-24px.svg
+++ b/images/room-24px.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/images/textsms-24px.svg b/images/textsms-24px.svg
index 4455f047e..84c4fdcc1 100644
--- a/images/textsms-24px.svg
+++ b/images/textsms-24px.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/meshtestic b/meshtestic
index 31ee3d90c..dcac7e567 160000
--- a/meshtestic
+++ b/meshtestic
@@ -1 +1 @@
-Subproject commit 31ee3d90c8bef61e835c3271be2c7cda8c4a5cc2
+Subproject commit dcac7e5673005f4d8a2b1f0f6e06877b689d7519
diff --git a/platformio.ini b/platformio.ini
index 87eb0afb7..2e3ee56f9 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -17,11 +17,9 @@ default_envs = tbeam
;default_envs = tlora-v2-1-1_6
;default_envs = tlora-v2-1-1_6-tcxo
;default_envs = tlora-t3s3-v1
-;default_envs = lora-relay-v1 # nrf board
;default_envs = t-echo
;default_envs = canaryone
-;default_envs = nrf52840dk-geeksville
-;default_envs = native # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
+;default_envs = native
;default_envs = nano-g1
;default_envs = pca10059_diy_eink
;default_envs = meshtastic-diy-v1
@@ -29,6 +27,7 @@ default_envs = tbeam
;default_envs = meshtastic-dr-dev
;default_envs = m5stack-coreink
;default_envs = rak4631
+;default_envs = rak4631_eth_gw
;default_envs = rak2560
;default_envs = rak10701
;default_envs = wio-e5
@@ -60,43 +59,44 @@ build_flags = -Wno-missing-field-initializers
-DUSE_THREAD_NAMES
-DTINYGPS_OPTION_NO_CUSTOM_FIELDS
-DPB_ENABLE_MALLOC=1
- -DRADIOLIB_EXCLUDE_CC1101
- -DRADIOLIB_EXCLUDE_NRF24
- -DRADIOLIB_EXCLUDE_RF69
- -DRADIOLIB_EXCLUDE_SX1231
- -DRADIOLIB_EXCLUDE_SX1233
- -DRADIOLIB_EXCLUDE_SI443X
- -DRADIOLIB_EXCLUDE_RFM2X
- -DRADIOLIB_EXCLUDE_AFSK
- -DRADIOLIB_EXCLUDE_BELL
- -DRADIOLIB_EXCLUDE_HELLSCHREIBER
- -DRADIOLIB_EXCLUDE_MORSE
- -DRADIOLIB_EXCLUDE_RTTY
- -DRADIOLIB_EXCLUDE_SSTV
- -DRADIOLIB_EXCLUDE_AX25
- -DRADIOLIB_EXCLUDE_DIRECT_RECEIVE
- -DRADIOLIB_EXCLUDE_BELL
- -DRADIOLIB_EXCLUDE_PAGER
- -DRADIOLIB_EXCLUDE_FSK4
- -DRADIOLIB_EXCLUDE_APRS
- -DRADIOLIB_EXCLUDE_LORAWAN
+ -DRADIOLIB_EXCLUDE_CC1101=1
+ -DRADIOLIB_EXCLUDE_NRF24=1
+ -DRADIOLIB_EXCLUDE_RF69=1
+ -DRADIOLIB_EXCLUDE_SX1231=1
+ -DRADIOLIB_EXCLUDE_SX1233=1
+ -DRADIOLIB_EXCLUDE_SI443X=1
+ -DRADIOLIB_EXCLUDE_RFM2X=1
+ -DRADIOLIB_EXCLUDE_AFSK=1
+ -DRADIOLIB_EXCLUDE_BELL=1
+ -DRADIOLIB_EXCLUDE_HELLSCHREIBER=1
+ -DRADIOLIB_EXCLUDE_MORSE=1
+ -DRADIOLIB_EXCLUDE_RTTY=1
+ -DRADIOLIB_EXCLUDE_SSTV=1
+ -DRADIOLIB_EXCLUDE_AX25=1
+ -DRADIOLIB_EXCLUDE_DIRECT_RECEIVE=1
+ -DRADIOLIB_EXCLUDE_BELL=1
+ -DRADIOLIB_EXCLUDE_PAGER=1
+ -DRADIOLIB_EXCLUDE_FSK4=1
+ -DRADIOLIB_EXCLUDE_APRS=1
+ -DRADIOLIB_EXCLUDE_LORAWAN=1
-DMESHTASTIC_EXCLUDE_DROPZONE=1
+ -DMESHTASTIC_EXCLUDE_REMOTEHARDWARE=1
+ #-DBUILD_EPOCH=$UNIX_TIME
;-D OLED_PL
monitor_speed = 115200
monitor_filters = direct
lib_deps =
-; jgromes/RadioLib@~6.6.0
- https://github.com/jgromes/RadioLib.git#3115fc2d6700a9aee05888791ac930a910f2628f
+ jgromes/RadioLib@~7.0.2
https://github.com/meshtastic/esp8266-oled-ssd1306.git#e16cee124fe26490cb14880c679321ad8ac89c95 ; ESP8266_SSD1306
- https://github.com/mathertel/OneButton@~2.6.1 ; OneButton library for non-blocking button debounce
+ mathertel/OneButton@~2.6.1 ; OneButton library for non-blocking button debounce
https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159
https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4
https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0
- nanopb/Nanopb@^0.4.8
+ nanopb/Nanopb@^0.4.9
erriez/ErriezCRC32@^1.0.1
-
+
; Used for the code analysis in PIO Home / Inspect
check_tool = cppcheck
check_skip_packages = yes
@@ -127,7 +127,7 @@ lib_deps =
; (not included in native / portduino)
[environmental_base]
lib_deps =
- adafruit/Adafruit BusIO@^1.15.0
+ adafruit/Adafruit BusIO@^1.16.1
adafruit/Adafruit Unified Sensor@^1.1.11
adafruit/Adafruit BMP280 Library@^2.6.8
adafruit/Adafruit BMP085 Library@^1.2.4
@@ -136,28 +136,31 @@ lib_deps =
adafruit/Adafruit MCP9808 Library@^2.0.0
adafruit/Adafruit INA260 Library@^1.5.0
adafruit/Adafruit INA219@^1.2.0
+ adafruit/Adafruit MAX1704X@^1.0.3
adafruit/Adafruit SHTC3 Library@^1.0.0
adafruit/Adafruit LPS2X@^2.0.4
adafruit/Adafruit SHT31 Library@^2.2.2
- adafruit/Adafruit PM25 AQI Sensor@^1.0.6
+ adafruit/Adafruit PM25 AQI Sensor@^1.1.1
adafruit/Adafruit MPU6050@^2.2.4
- adafruit/Adafruit LIS3DH@^1.2.4
+ adafruit/Adafruit LIS3DH@^1.3.0
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
+ sparkfun/SparkFun 9DoF IMU Breakout - ICM 20948 - Arduino Library@^1.2.13
ClosedCube OPT3001@^1.1.2
emotibit/EmotiBit MLX90632@^1.0.8
dfrobot/DFRobot_RTU@^1.0.3
-
+ sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@^1.1.2
+ adafruit/Adafruit MLX90614 Library@^2.1.5
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
+ https://github.com/KodinLanewave/INA3221@^1.0.1
+ lewisxhe/SensorLib@0.2.0
mprograms/QMC5883LCompass@^1.2.0
-
- https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee
\ No newline at end of file
+ https://github.com/meshtastic/DFRobot_LarkWeatherStation#4de3a9cadef0f6a5220a8a906cf9775b02b0040d
+ https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1
diff --git a/protobufs b/protobufs
index 5f7c91adb..807236815 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 5f7c91adb97187e0cb2140de7057344d93444bd1
+Subproject commit 807236815d61cc0ebd89472c2a2aa76758bc92ac
diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h
deleted file mode 100644
index c2910007e..000000000
--- a/src/AccelerometerThread.h
+++ /dev/null
@@ -1,297 +0,0 @@
-#pragma once
-#include "configuration.h"
-
-#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
-
-#include "PowerFSM.h"
-#include "concurrency/OSThread.h"
-#include "main.h"
-#include "power.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#ifdef RAK_4631
-#include "Fusion/Fusion.h"
-#include "graphics/Screen.h"
-#include "graphics/ScreenFonts.h"
-#include
-#endif
-
-#define ACCELEROMETER_CHECK_INTERVAL_MS 100
-#define ACCELEROMETER_CLICK_THRESHOLD 40
-
-static inline int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
-{
- Wire.beginTransmission(address);
- Wire.write(reg);
- Wire.endTransmission();
- Wire.requestFrom((uint8_t)address, (uint8_t)len);
- uint8_t i = 0;
- while (Wire.available()) {
- data[i++] = Wire.read();
- }
- return 0; // Pass
-}
-
-static inline int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
-{
- Wire.beginTransmission(address);
- Wire.write(reg);
- Wire.write(data, len);
- return (0 != Wire.endTransmission());
-}
-
-class AccelerometerThread : public concurrency::OSThread
-{
- public:
- explicit AccelerometerThread(ScanI2C::DeviceType type) : OSThread("AccelerometerThread")
- {
- if (accelerometer_found.port == ScanI2C::I2CPort::NO_I2C) {
- LOG_DEBUG("AccelerometerThread disabling due to no sensors found\n");
- 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();
- }
-
- 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() > 12 * 1000 && millis() < 30 * 1000) {
- if (!showingScreen) {
- showingScreen = true;
- screen->startAlert((FrameCallback)drawFrameCalibration);
- }
- 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;
- } else if (showingScreen && millis() >= 30 * 1000) {
- showingScreen = false;
- screen->endAlert();
- }
-
- 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)) {
- LOG_DEBUG("MPU6050 initializing\n");
- // setup motion detection
- mpu.setHighPassFilter(MPU6050_HIGHPASS_0_63_HZ);
- mpu.setMotionDetectionThreshold(1);
- mpu.setMotionDetectionDuration(20);
- mpu.setInterruptPinLatch(true); // Keep it latched. Will turn off when reinitialized.
- mpu.setInterruptPinPolarity(true);
- } else if (acceleremoter_type == ScanI2C::DeviceType::LIS3DH && lis.begin(accelerometer_found.address)) {
- LOG_DEBUG("LIS3DH initializing\n");
- lis.setRange(LIS3DH_RANGE_2_G);
- // Adjust threshold, higher numbers are less sensitive
- lis.setClick(config.device.double_tap_as_button_press ? 2 : 1, ACCELEROMETER_CLICK_THRESHOLD);
- } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 &&
- bmaSensor.begin(accelerometer_found.address, &readRegister, &writeRegister)) {
- LOG_DEBUG("BMA423 initializing\n");
- bmaSensor.configAccelerometer(bmaSensor.RANGE_2G, bmaSensor.ODR_100HZ, bmaSensor.BW_NORMAL_AVG4,
- bmaSensor.PERF_CONTINUOUS_MODE);
- bmaSensor.enableAccelerometer();
- bmaSensor.configInterrupt(BMA4_LEVEL_TRIGGER, BMA4_ACTIVE_HIGH, BMA4_PUSH_PULL, BMA4_OUTPUT_ENABLE,
- BMA4_INPUT_DISABLE);
-
-#ifdef BMA423_INT
- pinMode(BMA4XX_INT, INPUT);
- attachInterrupt(
- BMA4XX_INT,
- [] {
- // Set interrupt to set irq value to true
- BMA_IRQ = true;
- },
- RISING); // Select the interrupt mode according to the actual circuit
-#endif
-
-#ifdef T_WATCH_S3
- // Need to raise the wrist function, need to set the correct axis
- bmaSensor.setReampAxes(bmaSensor.REMAP_TOP_LAYER_RIGHT_CORNER);
-#else
- bmaSensor.setReampAxes(bmaSensor.REMAP_BOTTOM_LAYER_BOTTOM_LEFT_CORNER);
-#endif
- // bmaSensor.enableFeature(bmaSensor.FEATURE_STEP_CNTR, true);
- bmaSensor.enableFeature(bmaSensor.FEATURE_TILT, true);
- bmaSensor.enableFeature(bmaSensor.FEATURE_WAKEUP, true);
- // bmaSensor.resetPedometer();
-
- // Turn on feature interrupt
- bmaSensor.enablePedometerIRQ();
- 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
- lsm.setAccelRange(LSM6DS_ACCEL_RANGE_2_G);
-#ifndef LSM6DS3_WAKE_THRESH
-#define LSM6DS3_WAKE_THRESH 20
-#endif
- lsm.enableWakeup(config.display.wake_on_tap_or_motion, 1, LSM6DS3_WAKE_THRESH);
- // Duration is number of occurances needed to trigger, higher threshold is less sensitive
- }
- }
- void wakeScreen()
- {
- if (powerFSM.getState() == &stateDARK) {
- LOG_INFO("Tap or motion detected. Turning on screen\n");
- powerFSM.trigger(EVENT_INPUT);
- }
- }
-
- void buttonPress()
- {
- LOG_DEBUG("Double-tap detected. Firing button press\n");
- powerFSM.trigger(EVENT_PRESS);
- }
-
- ScanI2C::DeviceType acceleremoter_type;
- Adafruit_MPU6050 mpu;
- Adafruit_LIS3DH lis;
- Adafruit_LSM6DS3TRC lsm;
- SensorBMA423 bmaSensor;
- bool BMA_IRQ = false;
-#ifdef RAK_4631
- bool showingScreen = false;
- RAK_BMX160 bmx160;
- float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0;
-
- static void drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
- {
- int x_offset = display->width() / 2;
- int y_offset = display->height() <= 80 ? 0 : 32;
- display->setTextAlignment(TEXT_ALIGN_LEFT);
- display->setFont(FONT_MEDIUM);
- display->drawString(x, y, "Calibrating\nCompass");
- int16_t compassX = 0, compassY = 0;
- uint16_t compassDiam = graphics::Screen::getCompassDiam(display->getWidth(), display->getHeight());
-
- // coordinates for the center of the compass/circle
- if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
- compassX = x + display->getWidth() - compassDiam / 2 - 5;
- compassY = y + display->getHeight() / 2;
- } else {
- compassX = x + display->getWidth() - compassDiam / 2 - 5;
- compassY = y + FONT_HEIGHT_SMALL + (display->getHeight() - FONT_HEIGHT_SMALL) / 2;
- }
- display->drawCircle(compassX, compassY, compassDiam / 2);
- screen->drawCompassNorth(display, compassX, compassY, screen->getHeading() * PI / 180);
- }
-#endif
-};
-
-#endif
\ No newline at end of file
diff --git a/src/AmbientLightingThread.h b/src/AmbientLightingThread.h
index fdd4b53fa..07764e66e 100644
--- a/src/AmbientLightingThread.h
+++ b/src/AmbientLightingThread.h
@@ -42,18 +42,18 @@ class AmbientLightingThread : public concurrency::OSThread
#ifdef HAS_NCP5623
_type = type;
if (_type == ScanI2C::DeviceType::NONE) {
- LOG_DEBUG("AmbientLightingThread disabling due to no RGB leds found on I2C bus\n");
+ LOG_DEBUG("AmbientLightingThread disabling due to no RGB leds found on I2C bus");
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");
+ LOG_DEBUG("AmbientLightingThread disabling due to moduleConfig.ambient_lighting.led_state OFF");
disable();
return;
}
- LOG_DEBUG("AmbientLightingThread initializing\n");
+ LOG_DEBUG("AmbientLightingThread initializing");
#ifdef HAS_NCP5623
if (_type == ScanI2C::NCP5623) {
rgb.begin();
@@ -106,27 +106,27 @@ class AmbientLightingThread : public concurrency::OSThread
rgb.setRed(0);
rgb.setGreen(0);
rgb.setBlue(0);
- LOG_INFO("Turn Off NCP5623 Ambient lighting.\n");
+ LOG_INFO("Turn Off NCP5623 Ambient lighting.");
#endif
#ifdef HAS_NEOPIXEL
pixels.clear();
pixels.show();
- LOG_INFO("Turn Off NeoPixel Ambient lighting.\n");
+ LOG_INFO("Turn Off NeoPixel Ambient lighting.");
#endif
#ifdef RGBLED_CA
analogWrite(RGBLED_RED, 255 - 0);
analogWrite(RGBLED_GREEN, 255 - 0);
analogWrite(RGBLED_BLUE, 255 - 0);
- LOG_INFO("Turn Off Ambient lighting RGB Common Anode.\n");
+ LOG_INFO("Turn Off Ambient lighting RGB Common Anode.");
#elif defined(RGBLED_RED)
analogWrite(RGBLED_RED, 0);
analogWrite(RGBLED_GREEN, 0);
analogWrite(RGBLED_BLUE, 0);
- LOG_INFO("Turn Off Ambient lighting RGB Common Cathode.\n");
+ LOG_INFO("Turn Off Ambient lighting RGB Common Cathode.");
#endif
#ifdef UNPHONE
unphone.rgb(0, 0, 0);
- LOG_INFO("Turn Off unPhone Ambient lighting.\n");
+ LOG_INFO("Turn Off unPhone Ambient lighting.");
#endif
return 0;
}
@@ -138,7 +138,7 @@ 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 NCP5623 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",
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
moduleConfig.ambient_lighting.blue);
#endif
@@ -158,7 +158,7 @@ class AmbientLightingThread : public concurrency::OSThread
#endif
#endif
pixels.show();
- LOG_DEBUG("Initializing NeoPixel Ambient lighting w/ brightness(current)=%d, red=%d, green=%d, blue=%d\n",
+ LOG_DEBUG("Initializing NeoPixel Ambient lighting w/ brightness(current)=%d, red=%d, green=%d, blue=%d",
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
moduleConfig.ambient_lighting.blue);
#endif
@@ -166,18 +166,18 @@ class AmbientLightingThread : public concurrency::OSThread
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",
+ LOG_DEBUG("Initializing Ambient lighting RGB Common Anode w/ red=%d, green=%d, blue=%d",
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",
+ LOG_DEBUG("Initializing Ambient lighting RGB Common Cathode w/ red=%d, green=%d, blue=%d",
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,
+ LOG_DEBUG("Initializing unPhone Ambient lighting w/ red=%d, green=%d, blue=%d", moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif
}
diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp
index 5351fa049..ca4d40e15 100644
--- a/src/ButtonThread.cpp
+++ b/src/ButtonThread.cpp
@@ -36,7 +36,7 @@ ButtonThread::ButtonThread() : OSThread("Button")
#if defined(ARCH_PORTDUINO)
if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) {
this->userButton = OneButton(settingsMap[user], true, true);
- LOG_DEBUG("Using GPIO%02d for button\n", settingsMap[user]);
+ LOG_DEBUG("Using GPIO%02d for button", settingsMap[user]);
}
#elif defined(BUTTON_PIN)
int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; // Resolved button pin
@@ -47,7 +47,7 @@ ButtonThread::ButtonThread() : OSThread("Button")
#else
this->userButton = OneButton(pin, true, true);
#endif
- LOG_DEBUG("Using GPIO%02d for button\n", pin);
+ LOG_DEBUG("Using GPIO%02d for button", pin);
#endif
#ifdef INPUT_PULLUP_SENSE
@@ -123,7 +123,12 @@ int32_t ButtonThread::runOnce()
if (btnEvent != BUTTON_EVENT_NONE) {
switch (btnEvent) {
case BUTTON_EVENT_PRESSED: {
- LOG_BUTTON("press!\n");
+ LOG_BUTTON("press!");
+ // If a nag notification is running, stop it and prevent other actions
+ if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) {
+ externalNotificationModule->stopNow();
+ return 50;
+ }
#ifdef BUTTON_PIN
if (((config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN) !=
moduleConfig.canned_message.inputbroker_pin_press) ||
@@ -143,7 +148,7 @@ int32_t ButtonThread::runOnce()
}
case BUTTON_EVENT_DOUBLE_PRESSED: {
- LOG_BUTTON("Double press!\n");
+ LOG_BUTTON("Double press!");
service->refreshLocalMeshNode();
auto sentPosition = service->trySendPosition(NODENUM_BROADCAST, true);
if (screen) {
@@ -157,7 +162,7 @@ int32_t ButtonThread::runOnce()
}
case BUTTON_EVENT_MULTI_PRESSED: {
- LOG_BUTTON("Mulitipress! %hux\n", multipressClickCount);
+ LOG_BUTTON("Mulitipress! %hux", multipressClickCount);
switch (multipressClickCount) {
#if HAS_GPS
// 3 clicks: toggle GPS
@@ -184,7 +189,7 @@ int32_t ButtonThread::runOnce()
} // end multipress event
case BUTTON_EVENT_LONG_PRESSED: {
- LOG_BUTTON("Long press!\n");
+ LOG_BUTTON("Long press!");
powerFSM.trigger(EVENT_PRESS);
if (screen) {
screen->startAlert("Shutting down...");
@@ -196,7 +201,7 @@ int32_t ButtonThread::runOnce()
// Do actual shutdown when button released, otherwise the button release
// may wake the board immediatedly.
case BUTTON_EVENT_LONG_RELEASED: {
- LOG_INFO("Shutdown from long press\n");
+ LOG_INFO("Shutdown from long press");
playShutdownMelody();
delay(3000);
power->shutdown();
@@ -205,7 +210,7 @@ int32_t ButtonThread::runOnce()
#ifdef BUTTON_PIN_TOUCH
case BUTTON_EVENT_TOUCH_LONG_PRESSED: {
- LOG_BUTTON("Touch press!\n");
+ LOG_BUTTON("Touch press!");
if (screen) {
// Wake if asleep
if (powerFSM.getState() == &stateDARK)
diff --git a/src/DebugConfiguration.cpp b/src/DebugConfiguration.cpp
index 23b140daf..1c081ae29 100644
--- a/src/DebugConfiguration.cpp
+++ b/src/DebugConfiguration.cpp
@@ -97,12 +97,14 @@ Syslog &Syslog::logMask(uint8_t priMask)
void Syslog::enable()
{
+ this->_client->begin(this->_port);
this->_enabled = true;
}
void Syslog::disable()
{
this->_enabled = false;
+ this->_client->stop();
}
bool Syslog::isEnabled()
@@ -193,4 +195,4 @@ inline bool Syslog::_sendLog(uint16_t pri, const char *appName, const char *mess
return true;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/FSCommon.cpp b/src/FSCommon.cpp
index d6a542808..cea1130aa 100644
--- a/src/FSCommon.cpp
+++ b/src/FSCommon.cpp
@@ -53,7 +53,7 @@ bool lfs_assert_failed =
extern "C" void lfs_assert(const char *reason)
{
- LOG_ERROR("LFS assert: %s\n", reason);
+ LOG_ERROR("LFS assert: %s", reason);
lfs_assert_failed = true;
}
@@ -75,19 +75,19 @@ bool copyFile(const char *from, const char *to)
r = OSFS::getFile(from, cbuffer);
if (r == notfound) {
- LOG_ERROR("Failed to open source file %s\n", from);
+ LOG_ERROR("Failed to open source file %s", from);
return false;
} else if (r == noerr) {
r = OSFS::newFile(to, cbuffer, true);
if (r == noerr) {
return true;
} else {
- LOG_ERROR("OSFS Error %d\n", r);
+ LOG_ERROR("OSFS Error %d", r);
return false;
}
} else {
- LOG_ERROR("OSFS Error %d\n", r);
+ LOG_ERROR("OSFS Error %d", r);
return false;
}
return true;
@@ -97,13 +97,13 @@ bool copyFile(const char *from, const char *to)
File f1 = FSCom.open(from, FILE_O_READ);
if (!f1) {
- LOG_ERROR("Failed to open source file %s\n", from);
+ LOG_ERROR("Failed to open source file %s", from);
return false;
}
File f2 = FSCom.open(to, FILE_O_WRITE);
if (!f2) {
- LOG_ERROR("Failed to open destination file %s\n", to);
+ LOG_ERROR("Failed to open destination file %s", to);
return false;
}
@@ -231,7 +231,7 @@ void listDir(const char *dirname, uint8_t levels, bool del)
#ifdef ARCH_ESP32
listDir(file.path(), levels - 1, del);
if (del) {
- LOG_DEBUG("Removing %s\n", file.path());
+ LOG_DEBUG("Removing %s", file.path());
strncpy(buffer, file.path(), sizeof(buffer));
file.close();
FSCom.rmdir(buffer);
@@ -241,7 +241,7 @@ void listDir(const char *dirname, uint8_t levels, bool del)
#elif (defined(ARCH_RP2040) || defined(ARCH_PORTDUINO))
listDir(file.name(), levels - 1, del);
if (del) {
- LOG_DEBUG("Removing %s\n", file.name());
+ LOG_DEBUG("Removing %s", file.name());
strncpy(buffer, file.name(), sizeof(buffer));
file.close();
FSCom.rmdir(buffer);
@@ -249,7 +249,7 @@ void listDir(const char *dirname, uint8_t levels, bool del)
file.close();
}
#else
- LOG_DEBUG(" %s (directory)\n", file.name());
+ LOG_DEBUG(" %s (directory)", file.name());
listDir(file.name(), levels - 1, del);
file.close();
#endif
@@ -257,26 +257,26 @@ void listDir(const char *dirname, uint8_t levels, bool del)
} else {
#ifdef ARCH_ESP32
if (del) {
- LOG_DEBUG("Deleting %s\n", file.path());
+ LOG_DEBUG("Deleting %s", file.path());
strncpy(buffer, file.path(), sizeof(buffer));
file.close();
FSCom.remove(buffer);
} else {
- LOG_DEBUG(" %s (%i Bytes)\n", file.path(), file.size());
+ LOG_DEBUG(" %s (%i Bytes)", file.path(), file.size());
file.close();
}
#elif (defined(ARCH_RP2040) || defined(ARCH_PORTDUINO))
if (del) {
- LOG_DEBUG("Deleting %s\n", file.name());
+ LOG_DEBUG("Deleting %s", file.name());
strncpy(buffer, file.name(), sizeof(buffer));
file.close();
FSCom.remove(buffer);
} else {
- LOG_DEBUG(" %s (%i Bytes)\n", file.name(), file.size());
+ LOG_DEBUG(" %s (%i Bytes)", file.name(), file.size());
file.close();
}
#else
- LOG_DEBUG(" %s (%i Bytes)\n", file.name(), file.size());
+ LOG_DEBUG(" %s (%i Bytes)", file.name(), file.size());
file.close();
#endif
}
@@ -284,7 +284,7 @@ void listDir(const char *dirname, uint8_t levels, bool del)
}
#ifdef ARCH_ESP32
if (del) {
- LOG_DEBUG("Removing %s\n", root.path());
+ LOG_DEBUG("Removing %s", root.path());
strncpy(buffer, root.path(), sizeof(buffer));
root.close();
FSCom.rmdir(buffer);
@@ -293,7 +293,7 @@ void listDir(const char *dirname, uint8_t levels, bool del)
}
#elif (defined(ARCH_RP2040) || defined(ARCH_PORTDUINO))
if (del) {
- LOG_DEBUG("Removing %s\n", root.name());
+ LOG_DEBUG("Removing %s", root.name());
strncpy(buffer, root.name(), sizeof(buffer));
root.close();
FSCom.rmdir(buffer);
@@ -329,13 +329,13 @@ void fsInit()
{
#ifdef FSCom
if (!FSBegin()) {
- LOG_ERROR("Filesystem mount Failed.\n");
+ LOG_ERROR("Filesystem mount Failed.");
// assert(0); This auto-formats the partition, so no need to fail here.
}
#if defined(ARCH_ESP32)
- LOG_DEBUG("Filesystem files (%d/%d Bytes):\n", FSCom.usedBytes(), FSCom.totalBytes());
+ LOG_DEBUG("Filesystem files (%d/%d Bytes):", FSCom.usedBytes(), FSCom.totalBytes());
#else
- LOG_DEBUG("Filesystem files:\n");
+ LOG_DEBUG("Filesystem files:");
#endif
listDir("/", 10);
#endif
@@ -350,28 +350,28 @@ void setupSDCard()
SDHandler.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
if (!SD.begin(SDCARD_CS, SDHandler)) {
- LOG_DEBUG("No SD_MMC card detected\n");
+ LOG_DEBUG("No SD_MMC card detected");
return;
}
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) {
- LOG_DEBUG("No SD_MMC card attached\n");
+ LOG_DEBUG("No SD_MMC card attached");
return;
}
LOG_DEBUG("SD_MMC Card Type: ");
if (cardType == CARD_MMC) {
- LOG_DEBUG("MMC\n");
+ LOG_DEBUG("MMC");
} else if (cardType == CARD_SD) {
- LOG_DEBUG("SDSC\n");
+ LOG_DEBUG("SDSC");
} else if (cardType == CARD_SDHC) {
- LOG_DEBUG("SDHC\n");
+ LOG_DEBUG("SDHC");
} else {
- LOG_DEBUG("UNKNOWN\n");
+ LOG_DEBUG("UNKNOWN");
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
- LOG_DEBUG("SD Card Size: %lluMB\n", cardSize);
- LOG_DEBUG("Total space: %llu MB\n", SD.totalBytes() / (1024 * 1024));
- LOG_DEBUG("Used space: %llu MB\n", SD.usedBytes() / (1024 * 1024));
+ LOG_DEBUG("SD Card Size: %lu MB", (uint32_t)cardSize);
+ LOG_DEBUG("Total space: %lu MB", (uint32_t)(SD.totalBytes() / (1024 * 1024)));
+ LOG_DEBUG("Used space: %lu MB", (uint32_t)(SD.usedBytes() / (1024 * 1024)));
#endif
}
\ No newline at end of file
diff --git a/src/GPSStatus.h b/src/GPSStatus.h
index c2ab16c86..12f302baa 100644
--- a/src/GPSStatus.h
+++ b/src/GPSStatus.h
@@ -51,7 +51,7 @@ class GPSStatus : public Status
{
if (config.position.fixed_position) {
#ifdef GPS_EXTRAVERBOSE
- LOG_WARN("Using fixed latitude\n");
+ LOG_WARN("Using fixed latitude");
#endif
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
return node->position.latitude_i;
@@ -64,7 +64,7 @@ class GPSStatus : public Status
{
if (config.position.fixed_position) {
#ifdef GPS_EXTRAVERBOSE
- LOG_WARN("Using fixed longitude\n");
+ LOG_WARN("Using fixed longitude");
#endif
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
return node->position.longitude_i;
@@ -77,7 +77,7 @@ class GPSStatus : public Status
{
if (config.position.fixed_position) {
#ifdef GPS_EXTRAVERBOSE
- LOG_WARN("Using fixed altitude\n");
+ LOG_WARN("Using fixed altitude");
#endif
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
return node->position.altitude;
@@ -95,7 +95,7 @@ class GPSStatus : public Status
bool matches(const GPSStatus *newStatus) const
{
#ifdef GPS_EXTRAVERBOSE
- LOG_DEBUG("GPSStatus.match() new pos@%x to old pos@%x\n", newStatus->p.timestamp, p.timestamp);
+ LOG_DEBUG("GPSStatus.match() new pos@%x to old pos@%x", newStatus->p.timestamp, p.timestamp);
#endif
return (newStatus->hasLock != hasLock || newStatus->isConnected != isConnected ||
newStatus->isPowerSaving != isPowerSaving || newStatus->p.latitude_i != p.latitude_i ||
@@ -112,7 +112,7 @@ class GPSStatus : public Status
if (isDirty && p.timestamp && (newStatus->p.timestamp == p.timestamp)) {
// We can NEVER be in two locations at the same time! (also PR #886)
- LOG_ERROR("BUG: Positional timestamp unchanged from prev solution\n");
+ LOG_ERROR("BUG: Positional timestamp unchanged from prev solution");
}
initialized = true;
@@ -124,11 +124,11 @@ class GPSStatus : public Status
if (isDirty) {
if (hasLock) {
// In debug logs, identify position by @timestamp:stage (stage 3 = notify)
- LOG_DEBUG("New GPS pos@%x:3 lat=%f lon=%f alt=%d pdop=%.2f track=%.2f speed=%.2f sats=%d\n", p.timestamp,
+ LOG_DEBUG("New GPS pos@%x:3 lat=%f lon=%f alt=%d pdop=%.2f track=%.2f speed=%.2f sats=%d", p.timestamp,
p.latitude_i * 1e-7, p.longitude_i * 1e-7, p.altitude, p.PDOP * 1e-2, p.ground_track * 1e-5,
p.ground_speed * 1e-2, p.sats_in_view);
} else {
- LOG_DEBUG("No GPS lock\n");
+ LOG_DEBUG("No GPS lock");
}
onNewStatus.notifyObservers(this);
}
diff --git a/src/GpioLogic.cpp b/src/GpioLogic.cpp
index cbe26fc60..ba01d482d 100644
--- a/src/GpioLogic.cpp
+++ b/src/GpioLogic.cpp
@@ -12,7 +12,7 @@ void GpioVirtPin::set(bool value)
void GpioHwPin::set(bool value)
{
- // if (num == 3) LOG_DEBUG("Setting pin %d to %d\n", num, value);
+ // if (num == 3) LOG_DEBUG("Setting pin %d to %d", num, value);
pinMode(num, OUTPUT);
digitalWrite(num, value);
}
@@ -88,7 +88,7 @@ void GpioBinaryTransformer::update()
newValue = (GpioVirtPin::PinState)(p1 && p2);
break;
case Or:
- // LOG_DEBUG("Doing GPIO OR\n");
+ // LOG_DEBUG("Doing GPIO OR");
newValue = (GpioVirtPin::PinState)(p1 || p2);
break;
case Xor:
diff --git a/src/NodeStatus.h b/src/NodeStatus.h
index e6bf31aed..60d1bdd98 100644
--- a/src/NodeStatus.h
+++ b/src/NodeStatus.h
@@ -56,7 +56,7 @@ class NodeStatus : public Status
numTotal = newStatus->getNumTotal();
}
if (isDirty || newStatus->forceUpdate) {
- LOG_DEBUG("Node status update: %d online, %d total\n", numOnline, numTotal);
+ LOG_DEBUG("Node status update: %d online, %d total", numOnline, numTotal);
onNewStatus.notifyObservers(this);
}
return 0;
diff --git a/src/Power.cpp b/src/Power.cpp
index 61a6c987d..02a07e620 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -13,6 +13,7 @@
#include "power.h"
#include "NodeDB.h"
#include "PowerFSM.h"
+#include "Throttle.h"
#include "buzz/buzz.h"
#include "configuration.h"
#include "main.h"
@@ -30,6 +31,7 @@
#if HAS_WIFI
#include
#endif
+
#endif
#ifndef DELAY_FOREVER
@@ -75,6 +77,15 @@ INA219Sensor ina219Sensor;
INA3221Sensor ina3221Sensor;
#endif
+#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
+#include "modules/Telemetry/Sensor/MAX17048Sensor.h"
+#include
+extern std::pair nodeTelemetrySensorsMap[_meshtastic_TelemetrySensorType_MAX + 1];
+#if HAS_TELEMETRY && (!MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR || !MESHTASTIC_EXCLUDE_POWER_TELEMETRY)
+MAX17048Sensor max17048Sensor;
+#endif
+#endif
+
#if HAS_RAKPROT && !defined(ARCH_PORTDUINO)
RAK9154Sensor rak9154Sensor;
#endif
@@ -136,6 +147,8 @@ using namespace meshtastic;
*/
static HasBatteryLevel *batteryLevel; // Default to NULL for no battery level sensor
+#ifdef BATTERY_PIN
+
static void adcEnable()
{
#ifdef ADC_CTRL // enable adc voltage divider when we need to read
@@ -160,11 +173,14 @@ static void adcDisable()
#endif
}
+#endif
+
/**
* A simple battery level sensor that assumes the battery voltage is attached via a voltage-divider to an analog input
*/
class AnalogBatteryLevel : public HasBatteryLevel
{
+ public:
/**
* Battery state of charge, from 0 to 100 or -1 for unknown
*/
@@ -224,7 +240,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !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);
+ LOG_DEBUG("Using INA on I2C addr 0x%x for device battery voltage", config.power.device_battery_ina_address);
return getINAVoltage();
}
#endif
@@ -244,7 +260,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
config.power.adc_multiplier_override > 0 ? config.power.adc_multiplier_override : ADC_MULTIPLIER;
// Do not call analogRead() often.
const uint32_t min_read_interval = 5000;
- if (millis() - last_read_time_ms > min_read_interval) {
+ if (!Throttle::isWithinTimespanMs(last_read_time_ms, min_read_interval)) {
last_read_time_ms = millis();
uint32_t raw = 0;
@@ -274,7 +290,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
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)
+ // LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u", BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t)
// (last_read_value));
}
return last_read_value;
@@ -319,7 +335,7 @@ class AnalogBatteryLevel : public HasBatteryLevel
raw += adc_buf;
raw_c++; // Count valid samples
} else {
- LOG_DEBUG("An attempt to sample ADC2 failed\n");
+ LOG_DEBUG("An attempt to sample ADC2 failed");
}
}
@@ -344,7 +360,12 @@ class AnalogBatteryLevel : public HasBatteryLevel
/**
* return true if there is a battery installed in this unit
*/
+ // if we have a integrated device with a battery, we can assume that the battery is always connected
+#ifdef BATTERY_IMMUTABLE
+ virtual bool isBatteryConnect() override { return true; }
+#else
virtual bool isBatteryConnect() override { return getBatteryPercent() != -1; }
+#endif
/// If we see a battery voltage higher than physics allows - assume charger is pumping
/// in power
@@ -479,7 +500,7 @@ bool Power::analogInit()
#endif
#ifdef BATTERY_PIN
- LOG_DEBUG("Using analog input %d for battery level\n", BATTERY_PIN);
+ LOG_DEBUG("Using analog input %d for battery level", BATTERY_PIN);
// disable any internal pullups
pinMode(BATTERY_PIN, INPUT);
@@ -510,18 +531,18 @@ bool Power::analogInit()
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_characs);
// show ADC characterization base
if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
- LOG_INFO("ADCmod: ADC characterization based on Two Point values stored in eFuse\n");
+ LOG_INFO("ADCmod: ADC characterization based on Two Point values stored in eFuse");
} else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
- LOG_INFO("ADCmod: ADC characterization based on reference voltage stored in eFuse\n");
+ LOG_INFO("ADCmod: ADC characterization based on reference voltage stored in eFuse");
}
#ifdef CONFIG_IDF_TARGET_ESP32S3
// ESP32S3
else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) {
- LOG_INFO("ADCmod: ADC Characterization based on Two Point values and fitting curve coefficients stored in eFuse\n");
+ LOG_INFO("ADCmod: ADC Characterization based on Two Point values and fitting curve coefficients stored in eFuse");
}
#endif
else {
- LOG_INFO("ADCmod: ADC characterization based on default reference voltage\n");
+ LOG_INFO("ADCmod: ADC characterization based on default reference voltage");
}
#endif // ARCH_ESP32
@@ -551,7 +572,12 @@ bool Power::analogInit()
*/
bool Power::setup()
{
- bool found = axpChipInit() || analogInit();
+ // initialise one power sensor (only)
+ bool found = axpChipInit();
+ if (!found)
+ found = lipoInit();
+ if (!found)
+ found = analogInit();
#ifdef NRF_APM
found = true;
@@ -565,9 +591,9 @@ bool Power::setup()
void Power::shutdown()
{
- LOG_INFO("Shutting down\n");
+ LOG_INFO("Shutting down");
-#if defined(ARCH_NRF52) || defined(ARCH_ESP32)
+#if defined(ARCH_NRF52) || defined(ARCH_ESP32) || defined(ARCH_RP2040)
#ifdef PIN_LED1
ledOff(PIN_LED1);
#endif
@@ -620,7 +646,7 @@ void Power::readPowerStatus()
// changes.
nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get();
- // LOG_DEBUG("NRF Power %d\n", nrf_usb_state);
+ // LOG_DEBUG("NRF Power %d", nrf_usb_state);
// If changed to DISCONNECTED
if (nrf_usb_state == NRFX_POWER_USB_STATE_DISCONNECTED)
@@ -633,22 +659,22 @@ void Power::readPowerStatus()
// Notify any status instances that are observing us
const PowerStatus powerStatus2 = PowerStatus(hasBattery, usbPowered, isCharging, batteryVoltageMv, batteryChargePercent);
- LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d\n", powerStatus2.getHasUSB(),
- powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
+ LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d", powerStatus2.getHasUSB(), powerStatus2.getIsCharging(),
+ powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
newStatus.notifyObservers(&powerStatus2);
#ifdef DEBUG_HEAP
if (lastheap != memGet.getFreeHeap()) {
- LOG_DEBUG("Threads running:");
+ std::string threadlist = "Threads running:";
int running = 0;
for (int i = 0; i < MAX_THREADS; i++) {
auto thread = concurrency::mainController.get(i);
if ((thread != nullptr) && (thread->enabled)) {
- LOG_DEBUG(" %s", thread->ThreadName.c_str());
+ threadlist += vformat(" %s", thread->ThreadName.c_str());
running++;
}
}
- LOG_DEBUG("\n");
- LOG_DEBUG("Heap status: %d/%d bytes free (%d), running %d/%d threads\n", memGet.getFreeHeap(), memGet.getHeapSize(),
+ LOG_DEBUG(threadlist.c_str());
+ LOG_DEBUG("Heap status: %d/%d bytes free (%d), running %d/%d threads", memGet.getFreeHeap(), memGet.getHeapSize(),
memGet.getFreeHeap() - lastheap, running, concurrency::mainController.size(false));
lastheap = memGet.getFreeHeap();
}
@@ -681,13 +707,13 @@ void Power::readPowerStatus()
if (batteryLevel && powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) {
if (batteryLevel->getBattVoltage() < OCV[NUM_OCV_POINTS - 1]) {
low_voltage_counter++;
- LOG_DEBUG("Low voltage counter: %d/10\n", low_voltage_counter);
+ LOG_DEBUG("Low voltage counter: %d/10", low_voltage_counter);
if (low_voltage_counter > 10) {
#ifdef ARCH_NRF52
// We can't trigger deep sleep on NRF52, it's freezing the board
- LOG_DEBUG("Low voltage detected, but not triggering deep sleep\n");
+ LOG_DEBUG("Low voltage detected, but not triggering deep sleep");
#else
- LOG_INFO("Low voltage detected, triggering deep sleep\n");
+ LOG_INFO("Low voltage detected, triggering deep sleep");
powerFSM.trigger(EVENT_LOW_BATTERY);
#endif
}
@@ -709,12 +735,12 @@ int32_t Power::runOnce()
PMU->getIrqStatus();
if (PMU->isVbusRemoveIrq()) {
- LOG_INFO("USB unplugged\n");
+ LOG_INFO("USB unplugged");
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
}
if (PMU->isVbusInsertIrq()) {
- LOG_INFO("USB plugged In\n");
+ LOG_INFO("USB plugged In");
powerFSM.trigger(EVENT_POWER_CONNECTED);
}
@@ -722,21 +748,21 @@ int32_t Power::runOnce()
Other things we could check if we cared...
if (PMU->isBatChagerStartIrq()) {
- LOG_DEBUG("Battery start charging\n");
+ LOG_DEBUG("Battery start charging");
}
if (PMU->isBatChagerDoneIrq()) {
- LOG_DEBUG("Battery fully charged\n");
+ LOG_DEBUG("Battery fully charged");
}
if (PMU->isBatInsertIrq()) {
- LOG_DEBUG("Battery inserted\n");
+ LOG_DEBUG("Battery inserted");
}
if (PMU->isBatRemoveIrq()) {
- LOG_DEBUG("Battery removed\n");
+ LOG_DEBUG("Battery removed");
}
*/
#ifndef T_WATCH_S3 // FIXME - why is this triggering on the T-Watch S3?
if (PMU->isPekeyLongPressIrq()) {
- LOG_DEBUG("PEK long button press\n");
+ LOG_DEBUG("PEK long button press");
screen->setOn(false);
}
#endif
@@ -779,22 +805,22 @@ bool Power::axpChipInit()
if (!PMU) {
PMU = new XPowersAXP2101(*w);
if (!PMU->init()) {
- LOG_WARN("Failed to find AXP2101 power management\n");
+ LOG_WARN("Failed to find AXP2101 power management");
delete PMU;
PMU = NULL;
} else {
- LOG_INFO("AXP2101 PMU init succeeded, using AXP2101 PMU\n");
+ LOG_INFO("AXP2101 PMU init succeeded, using AXP2101 PMU");
}
}
if (!PMU) {
PMU = new XPowersAXP192(*w);
if (!PMU->init()) {
- LOG_WARN("Failed to find AXP192 power management\n");
+ LOG_WARN("Failed to find AXP192 power management");
delete PMU;
PMU = NULL;
} else {
- LOG_INFO("AXP192 PMU init succeeded, using AXP192 PMU\n");
+ LOG_INFO("AXP192 PMU init succeeded, using AXP192 PMU");
}
}
@@ -950,56 +976,54 @@ bool Power::axpChipInit()
PMU->enableVbusVoltageMeasure();
PMU->enableBattVoltageMeasure();
- LOG_DEBUG("=======================================================================\n");
if (PMU->isChannelAvailable(XPOWERS_DCDC1)) {
- LOG_DEBUG("DC1 : %s Voltage:%u mV \n", PMU->isPowerChannelEnable(XPOWERS_DCDC1) ? "+" : "-",
+ LOG_DEBUG("DC1 : %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_DCDC1) ? "+" : "-",
PMU->getPowerChannelVoltage(XPOWERS_DCDC1));
}
if (PMU->isChannelAvailable(XPOWERS_DCDC2)) {
- LOG_DEBUG("DC2 : %s Voltage:%u mV \n", PMU->isPowerChannelEnable(XPOWERS_DCDC2) ? "+" : "-",
+ LOG_DEBUG("DC2 : %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_DCDC2) ? "+" : "-",
PMU->getPowerChannelVoltage(XPOWERS_DCDC2));
}
if (PMU->isChannelAvailable(XPOWERS_DCDC3)) {
- LOG_DEBUG("DC3 : %s Voltage:%u mV \n", PMU->isPowerChannelEnable(XPOWERS_DCDC3) ? "+" : "-",
+ LOG_DEBUG("DC3 : %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_DCDC3) ? "+" : "-",
PMU->getPowerChannelVoltage(XPOWERS_DCDC3));
}
if (PMU->isChannelAvailable(XPOWERS_DCDC4)) {
- LOG_DEBUG("DC4 : %s Voltage:%u mV \n", PMU->isPowerChannelEnable(XPOWERS_DCDC4) ? "+" : "-",
+ LOG_DEBUG("DC4 : %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_DCDC4) ? "+" : "-",
PMU->getPowerChannelVoltage(XPOWERS_DCDC4));
}
if (PMU->isChannelAvailable(XPOWERS_LDO2)) {
- LOG_DEBUG("LDO2 : %s Voltage:%u mV \n", PMU->isPowerChannelEnable(XPOWERS_LDO2) ? "+" : "-",
+ LOG_DEBUG("LDO2 : %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_LDO2) ? "+" : "-",
PMU->getPowerChannelVoltage(XPOWERS_LDO2));
}
if (PMU->isChannelAvailable(XPOWERS_LDO3)) {
- LOG_DEBUG("LDO3 : %s Voltage:%u mV \n", PMU->isPowerChannelEnable(XPOWERS_LDO3) ? "+" : "-",
+ LOG_DEBUG("LDO3 : %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_LDO3) ? "+" : "-",
PMU->getPowerChannelVoltage(XPOWERS_LDO3));
}
if (PMU->isChannelAvailable(XPOWERS_ALDO1)) {
- LOG_DEBUG("ALDO1: %s Voltage:%u mV \n", PMU->isPowerChannelEnable(XPOWERS_ALDO1) ? "+" : "-",
+ LOG_DEBUG("ALDO1: %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_ALDO1) ? "+" : "-",
PMU->getPowerChannelVoltage(XPOWERS_ALDO1));
}
if (PMU->isChannelAvailable(XPOWERS_ALDO2)) {
- LOG_DEBUG("ALDO2: %s Voltage:%u mV \n", PMU->isPowerChannelEnable(XPOWERS_ALDO2) ? "+" : "-",
+ LOG_DEBUG("ALDO2: %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_ALDO2) ? "+" : "-",
PMU->getPowerChannelVoltage(XPOWERS_ALDO2));
}
if (PMU->isChannelAvailable(XPOWERS_ALDO3)) {
- LOG_DEBUG("ALDO3: %s Voltage:%u mV \n", PMU->isPowerChannelEnable(XPOWERS_ALDO3) ? "+" : "-",
+ LOG_DEBUG("ALDO3: %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_ALDO3) ? "+" : "-",
PMU->getPowerChannelVoltage(XPOWERS_ALDO3));
}
if (PMU->isChannelAvailable(XPOWERS_ALDO4)) {
- LOG_DEBUG("ALDO4: %s Voltage:%u mV \n", PMU->isPowerChannelEnable(XPOWERS_ALDO4) ? "+" : "-",
+ LOG_DEBUG("ALDO4: %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_ALDO4) ? "+" : "-",
PMU->getPowerChannelVoltage(XPOWERS_ALDO4));
}
if (PMU->isChannelAvailable(XPOWERS_BLDO1)) {
- LOG_DEBUG("BLDO1: %s Voltage:%u mV \n", PMU->isPowerChannelEnable(XPOWERS_BLDO1) ? "+" : "-",
+ LOG_DEBUG("BLDO1: %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_BLDO1) ? "+" : "-",
PMU->getPowerChannelVoltage(XPOWERS_BLDO1));
}
if (PMU->isChannelAvailable(XPOWERS_BLDO2)) {
- LOG_DEBUG("BLDO2: %s Voltage:%u mV \n", PMU->isPowerChannelEnable(XPOWERS_BLDO2) ? "+" : "-",
+ LOG_DEBUG("BLDO2: %s Voltage:%u mV ", PMU->isPowerChannelEnable(XPOWERS_BLDO2) ? "+" : "-",
PMU->getPowerChannelVoltage(XPOWERS_BLDO2));
}
- LOG_DEBUG("=======================================================================\n");
// We can safely ignore this approach for most (or all) boards because MCU turned off
// earlier than battery discharged to 2.6V.
@@ -1042,4 +1066,82 @@ bool Power::axpChipInit()
#else
return false;
#endif
-}
\ No newline at end of file
+}
+
+#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
+
+/**
+ * Wrapper class for an I2C MAX17048 Lipo battery sensor.
+ */
+class LipoBatteryLevel : public HasBatteryLevel
+{
+ private:
+ MAX17048Singleton *max17048 = nullptr;
+
+ public:
+ /**
+ * Init the I2C MAX17048 Lipo battery level sensor
+ */
+ bool runOnce()
+ {
+ if (max17048 == nullptr) {
+ max17048 = MAX17048Singleton::GetInstance();
+ }
+
+ // try to start if the sensor has been detected
+ if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX17048].first != 0) {
+ return max17048->runOnce(nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_MAX17048].second);
+ }
+ return false;
+ }
+
+ /**
+ * Battery state of charge, from 0 to 100 or -1 for unknown
+ */
+ virtual int getBatteryPercent() override { return max17048->getBusBatteryPercent(); }
+
+ /**
+ * The raw voltage of the battery in millivolts, or NAN if unknown
+ */
+ virtual uint16_t getBattVoltage() override { return max17048->getBusVoltageMv(); }
+
+ /**
+ * return true if there is a battery installed in this unit
+ */
+ virtual bool isBatteryConnect() override { return max17048->isBatteryConnected(); }
+
+ /**
+ * return true if there is an external power source detected
+ */
+ virtual bool isVbusIn() override { return max17048->isExternallyPowered(); }
+
+ /**
+ * return true if the battery is currently charging
+ */
+ virtual bool isCharging() override { return max17048->isBatteryCharging(); }
+};
+
+LipoBatteryLevel lipoLevel;
+
+/**
+ * Init the Lipo battery level sensor
+ */
+bool Power::lipoInit()
+{
+ bool result = lipoLevel.runOnce();
+ LOG_DEBUG("Power::lipoInit lipo sensor is %s", result ? "ready" : "not ready yet");
+ if (!result)
+ return false;
+ batteryLevel = &lipoLevel;
+ return true;
+}
+
+#else
+/**
+ * The Lipo battery level sensor is unavailable - default to AnalogBatteryLevel
+ */
+bool Power::lipoInit()
+{
+ return false;
+}
+#endif
\ No newline at end of file
diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp
index 8bf7d3218..35ef2624a 100644
--- a/src/PowerFSM.cpp
+++ b/src/PowerFSM.cpp
@@ -53,7 +53,7 @@ static bool isPowered()
static void sdsEnter()
{
- LOG_DEBUG("Enter state: SDS\n");
+ LOG_DEBUG("Enter state: SDS");
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false);
}
@@ -62,7 +62,7 @@ extern Power *power;
static void shutdownEnter()
{
- LOG_DEBUG("Enter state: SHUTDOWN\n");
+ LOG_DEBUG("Enter state: SHUTDOWN");
power->shutdown();
}
@@ -72,16 +72,16 @@ static uint32_t secsSlept;
static void lsEnter()
{
- LOG_INFO("lsEnter begin, ls_secs=%u\n", config.power.ls_secs);
+ LOG_INFO("lsEnter begin, ls_secs=%u", config.power.ls_secs);
screen->setOn(false);
secsSlept = 0; // How long have we been sleeping this time
- // LOG_INFO("lsEnter end\n");
+ // LOG_INFO("lsEnter end");
}
static void lsIdle()
{
- // LOG_INFO("lsIdle begin ls_secs=%u\n", getPref_ls_secs());
+ // LOG_INFO("lsIdle begin ls_secs=%u", getPref_ls_secs());
#ifdef ARCH_ESP32
@@ -105,7 +105,7 @@ static void lsIdle()
wakeCause2 = doLightSleep(100); // leave led on for 1ms
secsSlept += sleepTime;
- // LOG_INFO("sleeping, flash led!\n");
+ // LOG_INFO("sleeping, flash led!");
break;
case ESP_SLEEP_WAKEUP_UART:
@@ -137,7 +137,7 @@ static void lsIdle()
} else {
// Time to stop sleeping!
ledBlink.set(false);
- LOG_INFO("Reached ls_secs, servicing loop()\n");
+ LOG_INFO("Reached ls_secs, servicing loop()");
powerFSM.trigger(EVENT_WAKE_TIMER);
}
#endif
@@ -145,12 +145,12 @@ static void lsIdle()
static void lsExit()
{
- LOG_INFO("Exit state: LS\n");
+ LOG_INFO("Exit state: LS");
}
static void nbEnter()
{
- LOG_DEBUG("Enter state: NB\n");
+ LOG_DEBUG("Enter state: NB");
screen->setOn(false);
#ifdef ARCH_ESP32
// Only ESP32 should turn off bluetooth
@@ -168,7 +168,7 @@ static void darkEnter()
static void serialEnter()
{
- LOG_DEBUG("Enter state: SERIAL\n");
+ LOG_DEBUG("Enter state: SERIAL");
setBluetoothEnable(false);
screen->setOn(true);
screen->print("Serial connected\n");
@@ -183,10 +183,10 @@ static void serialExit()
static void powerEnter()
{
- // LOG_DEBUG("Enter state: POWER\n");
+ // LOG_DEBUG("Enter state: POWER");
if (!isPowered()) {
// If we got here, we are in the wrong state - we should be in powered, let that state ahndle things
- LOG_INFO("Loss of power in Powered\n");
+ LOG_INFO("Loss of power in Powered");
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
} else {
screen->setOn(true);
@@ -205,7 +205,7 @@ static void powerIdle()
{
if (!isPowered()) {
// If we got here, we are in the wrong state
- LOG_INFO("Loss of power in Powered\n");
+ LOG_INFO("Loss of power in Powered");
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
}
}
@@ -222,7 +222,7 @@ static void powerExit()
static void onEnter()
{
- LOG_DEBUG("Enter state: ON\n");
+ LOG_DEBUG("Enter state: ON");
screen->setOn(true);
setBluetoothEnable(true);
}
@@ -242,7 +242,7 @@ static void screenPress()
static void bootEnter()
{
- LOG_DEBUG("Enter state: BOOT\n");
+ LOG_DEBUG("Enter state: BOOT");
}
State stateSHUTDOWN(shutdownEnter, NULL, NULL, "SHUTDOWN");
@@ -264,7 +264,7 @@ void PowerFSM_setup()
config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR;
bool hasPower = isPowered();
- LOG_INFO("PowerFSM init, USB power=%d\n", hasPower ? 1 : 0);
+ LOG_INFO("PowerFSM init, USB power=%d", hasPower ? 1 : 0);
powerFSM.add_timed_transition(&stateBOOT, hasPower ? &statePOWER : &stateON, 3 * 1000, NULL, "boot timeout");
// wake timer expired or a packet arrived
diff --git a/src/PowerMon.cpp b/src/PowerMon.cpp
index 16909262d..38740b6ae 100644
--- a/src/PowerMon.cpp
+++ b/src/PowerMon.cpp
@@ -35,7 +35,7 @@ void PowerMon::emitLog(const char *reason)
{
#ifdef USE_POWERMON
// The nrf52 printf doesn't understand 64 bit ints, so if we ever reach that point this function will need to change.
- LOG_INFO("S:PM:0x%08lx,%s\n", (uint32_t)states, reason);
+ LOG_INFO("S:PM:0x%08lx,%s", (uint32_t)states, reason);
#endif
}
diff --git a/src/PowerStatus.h b/src/PowerStatus.h
index 592a03328..fe4543e08 100644
--- a/src/PowerStatus.h
+++ b/src/PowerStatus.h
@@ -91,7 +91,7 @@ class PowerStatus : public Status
isCharging = newStatus->isCharging;
}
if (isDirty) {
- // LOG_DEBUG("Battery %dmV %d%%\n", batteryVoltageMv, batteryChargePercent);
+ // LOG_DEBUG("Battery %dmV %d%%", batteryVoltageMv, batteryChargePercent);
onNewStatus.notifyObservers(this);
}
return 0;
diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp
index 6eb6f8319..57f53019d 100644
--- a/src/RedirectablePrint.cpp
+++ b/src/RedirectablePrint.cpp
@@ -98,81 +98,75 @@ void RedirectablePrint::log_to_serial(const char *logLevel, const char *format,
{
size_t r = 0;
- // Cope with 0 len format strings, but look for new line terminator
- bool hasNewline = *format && format[strlen(format) - 1] == '\n';
#ifdef ARCH_PORTDUINO
bool color = !settingsMap[ascii_logs];
#else
bool color = true;
#endif
- // If we are the first message on a report, include the header
- if (!isContinuationMessage) {
+ // include the header
+ if (color) {
+ if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
+ Print::write("\u001b[34m", 6);
+ if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
+ Print::write("\u001b[32m", 6);
+ if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
+ Print::write("\u001b[33m", 6);
+ if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0)
+ Print::write("\u001b[31m", 6);
+ if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0)
+ Print::write("\u001b[35m", 6);
+ }
+
+ uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile
+ if (rtc_sec > 0) {
+ long hms = rtc_sec % SEC_PER_DAY;
+ // hms += tz.tz_dsttime * SEC_PER_HOUR;
+ // hms -= tz.tz_minuteswest * SEC_PER_MIN;
+ // mod `hms` to ensure in positive range of [0...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 min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
+ int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
+#ifdef ARCH_PORTDUINO
+ ::printf("%s ", logLevel);
if (color) {
- if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
- Print::write("\u001b[34m", 6);
- if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
- Print::write("\u001b[32m", 6);
- if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
- Print::write("\u001b[33m", 6);
- if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_ERROR) == 0)
- Print::write("\u001b[31m", 6);
- if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0)
- Print::write("\u001b[35m", 6);
+ ::printf("\u001b[0m");
}
-
- uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile
- if (rtc_sec > 0) {
- long hms = rtc_sec % SEC_PER_DAY;
- // hms += tz.tz_dsttime * SEC_PER_HOUR;
- // hms -= tz.tz_minuteswest * SEC_PER_MIN;
- // mod `hms` to ensure in positive range of [0...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 min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
- int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
-#ifdef ARCH_PORTDUINO
- ::printf("%s ", logLevel);
- if (color) {
- ::printf("\u001b[0m");
- }
- ::printf("| %02d:%02d:%02d %u ", hour, min, sec, millis() / 1000);
+ ::printf("| %02d:%02d:%02d %u ", hour, min, sec, millis() / 1000);
#else
- printf("%s ", logLevel);
- if (color) {
- printf("\u001b[0m");
- }
- printf("| %02d:%02d:%02d %u ", hour, min, sec, millis() / 1000);
+ printf("%s ", logLevel);
+ if (color) {
+ printf("\u001b[0m");
+ }
+ printf("| %02d:%02d:%02d %u ", hour, min, sec, millis() / 1000);
#endif
- } else {
+ } else {
#ifdef ARCH_PORTDUINO
- ::printf("%s ", logLevel);
- if (color) {
- ::printf("\u001b[0m");
- }
- ::printf("| ??:??:?? %u ", millis() / 1000);
+ ::printf("%s ", logLevel);
+ if (color) {
+ ::printf("\u001b[0m");
+ }
+ ::printf("| ??:??:?? %u ", millis() / 1000);
#else
- printf("%s ", logLevel);
- if (color) {
- printf("\u001b[0m");
- }
- printf("| ??:??:?? %u ", millis() / 1000);
+ printf("%s ", logLevel);
+ if (color) {
+ printf("\u001b[0m");
+ }
+ printf("| ??:??:?? %u ", millis() / 1000);
#endif
- }
- auto thread = concurrency::OSThread::currentThread;
- if (thread) {
- print("[");
- // printf("%p ", thread);
- // assert(thread->ThreadName.length());
- print(thread->ThreadName);
- print("] ");
- }
+ }
+ auto thread = concurrency::OSThread::currentThread;
+ if (thread) {
+ print("[");
+ // printf("%p ", thread);
+ // assert(thread->ThreadName.length());
+ print(thread->ThreadName);
+ print("] ");
}
r += vprintf(logLevel, format, arg);
-
- isContinuationMessage = !hasNewline;
}
void RedirectablePrint::log_to_syslog(const char *logLevel, const char *format, va_list arg)
@@ -283,6 +277,14 @@ meshtastic_LogRecord_Level RedirectablePrint::getLogLevel(const char *logLevel)
void RedirectablePrint::log(const char *logLevel, const char *format, ...)
{
+
+ // append \n to format
+ size_t len = strlen(format);
+ char *newFormat = new char[len + 2];
+ strcpy(newFormat, format);
+ newFormat[len] = '\n';
+ newFormat[len + 1] = '\0';
+
#if ARCH_PORTDUINO
// level trace is special, two possible ways to handle it.
if (strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
@@ -295,17 +297,24 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...)
}
va_end(arg);
}
- if (settingsMap[logoutputlevel] < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0)
+ if (settingsMap[logoutputlevel] < level_trace && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_TRACE) == 0) {
+ delete[] newFormat;
return;
+ }
}
- if (settingsMap[logoutputlevel] < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
+ if (settingsMap[logoutputlevel] < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) {
+ delete[] newFormat;
return;
- else if (settingsMap[logoutputlevel] < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
+ } else if (settingsMap[logoutputlevel] < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0) {
+ delete[] newFormat;
return;
- else if (settingsMap[logoutputlevel] < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
+ } else if (settingsMap[logoutputlevel] < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0) {
+ delete[] newFormat;
return;
+ }
#endif
if (moduleConfig.serial.override_console_serial_port && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) {
+ delete[] newFormat;
return;
}
@@ -319,9 +328,9 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...)
va_list arg;
va_start(arg, format);
- log_to_serial(logLevel, format, arg);
- log_to_syslog(logLevel, format, arg);
- log_to_ble(logLevel, format, arg);
+ log_to_serial(logLevel, newFormat, arg);
+ log_to_syslog(logLevel, newFormat, arg);
+ log_to_ble(logLevel, newFormat, arg);
va_end(arg);
#ifdef HAS_FREE_RTOS
@@ -331,17 +340,18 @@ void RedirectablePrint::log(const char *logLevel, const char *format, ...)
#endif
}
+ delete[] newFormat;
return;
}
void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16_t len)
{
const char alphabet[17] = "0123456789abcdef";
- log(logLevel, " +------------------------------------------------+ +----------------+\n");
- log(logLevel, " |.0 .1 .2 .3 .4 .5 .6 .7 .8 .9 .a .b .c .d .e .f | | ASCII |\n");
+ log(logLevel, " +------------------------------------------------+ +----------------+");
+ log(logLevel, " |.0 .1 .2 .3 .4 .5 .6 .7 .8 .9 .a .b .c .d .e .f | | ASCII |");
for (uint16_t i = 0; i < len; i += 16) {
if (i % 128 == 0)
- log(logLevel, " +------------------------------------------------+ +----------------+\n");
+ log(logLevel, " +------------------------------------------------+ +----------------+");
char s[] = "| | | |\n";
uint8_t ix = 1, iy = 52;
for (uint8_t j = 0; j < 16; j++) {
@@ -363,7 +373,7 @@ void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16
log(logLevel, ".");
log(logLevel, s);
}
- log(logLevel, " +------------------------------------------------+ +----------------+\n");
+ log(logLevel, " +------------------------------------------------+ +----------------+");
}
std::string RedirectablePrint::mt_sprintf(const std::string fmt_str, ...)
diff --git a/src/RedirectablePrint.h b/src/RedirectablePrint.h
index 23ae3c44d..45b62b7af 100644
--- a/src/RedirectablePrint.h
+++ b/src/RedirectablePrint.h
@@ -15,9 +15,6 @@ class RedirectablePrint : public Print
{
Print *dest;
- /// Used to allow multiple logDebug messages to appear on a single log line
- bool isContinuationMessage = false;
-
#ifdef HAS_FREE_RTOS
SemaphoreHandle_t inDebugPrint = nullptr;
StaticSemaphore_t _MutexStorageSpace;
@@ -54,9 +51,9 @@ class RedirectablePrint : public Print
protected:
/// Subclasses can override if they need to change how we format over the serial port
virtual void log_to_serial(const char *logLevel, const char *format, va_list arg);
+ meshtastic_LogRecord_Level getLogLevel(const char *logLevel);
private:
void log_to_syslog(const char *logLevel, const char *format, va_list arg);
void log_to_ble(const char *logLevel, const char *format, va_list arg);
- meshtastic_LogRecord_Level getLogLevel(const char *logLevel);
};
\ No newline at end of file
diff --git a/src/SafeFile.cpp b/src/SafeFile.cpp
index c17d422bd..c76ff8054 100644
--- a/src/SafeFile.cpp
+++ b/src/SafeFile.cpp
@@ -59,14 +59,14 @@ bool SafeFile::close()
// brief window of risk here ;-)
if (fullAtomic && FSCom.exists(filename.c_str()) && !FSCom.remove(filename.c_str())) {
- LOG_ERROR("Can't remove old pref file\n");
+ LOG_ERROR("Can't remove old pref file");
return false;
}
String filenameTmp = filename;
filenameTmp += ".tmp";
if (!renameFile(filenameTmp.c_str(), filename.c_str())) {
- LOG_ERROR("Error: can't rename new pref file\n");
+ LOG_ERROR("Error: can't rename new pref file");
return false;
}
@@ -83,7 +83,7 @@ bool SafeFile::testReadback()
filenameTmp += ".tmp";
auto f2 = FSCom.open(filenameTmp.c_str(), FILE_O_READ);
if (!f2) {
- LOG_ERROR("Can't open tmp file for readback\n");
+ LOG_ERROR("Can't open tmp file for readback");
return false;
}
@@ -95,7 +95,7 @@ bool SafeFile::testReadback()
f2.close();
if (test_hash != hash) {
- LOG_ERROR("Readback failed hash mismatch\n");
+ LOG_ERROR("Readback failed hash mismatch");
return false;
}
diff --git a/src/SafeFile.h b/src/SafeFile.h
index 7088074cd..61361d312 100644
--- a/src/SafeFile.h
+++ b/src/SafeFile.h
@@ -24,7 +24,7 @@
class SafeFile : public Print
{
public:
- SafeFile(char const *filepath, bool fullAtomic = false);
+ explicit SafeFile(char const *filepath, bool fullAtomic = false);
virtual size_t write(uint8_t);
virtual size_t write(const uint8_t *buffer, size_t size);
diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp
index b911e15da..68c41980d 100644
--- a/src/SerialConsole.cpp
+++ b/src/SerialConsole.cpp
@@ -1,6 +1,8 @@
#include "SerialConsole.h"
+#include "Default.h"
#include "NodeDB.h"
#include "PowerFSM.h"
+#include "Throttle.h"
#include "configuration.h"
#include "time.h"
@@ -44,10 +46,11 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
Port.setRX(SERIAL2_RX);
#endif
Port.begin(SERIAL_BAUD);
-#if defined(ARCH_NRF52) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(ARCH_RP2040)
+#if defined(ARCH_NRF52) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(ARCH_RP2040) || \
+ defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32C6)
time_t timeout = millis();
while (!Port) {
- if ((millis() - timeout) < 5000) {
+ if (Throttle::isWithinTimespanMs(timeout, FIVE_SECONDS_MS)) {
delay(100);
} else {
break;
@@ -72,8 +75,7 @@ void SerialConsole::flush()
// For the serial port we can't really detect if any client is on the other side, so instead just look for recent messages
bool SerialConsole::checkIsConnected()
{
- uint32_t now = millis();
- return (now - lastContactMsec) < SERIAL_CONNECTION_TIMEOUT;
+ return Throttle::isWithinTimespanMs(lastContactMsec, SERIAL_CONNECTION_TIMEOUT);
}
/**
@@ -97,27 +99,9 @@ bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len)
void SerialConsole::log_to_serial(const char *logLevel, const char *format, va_list arg)
{
if (usingProtobufs && config.security.debug_log_api_enabled) {
- meshtastic_LogRecord_Level ll = meshtastic_LogRecord_Level_UNSET; // default to unset
- switch (logLevel[0]) {
- case 'D':
- ll = meshtastic_LogRecord_Level_DEBUG;
- break;
- case 'I':
- ll = meshtastic_LogRecord_Level_INFO;
- break;
- case 'W':
- ll = meshtastic_LogRecord_Level_WARNING;
- break;
- case 'E':
- ll = meshtastic_LogRecord_Level_ERROR;
- break;
- case 'C':
- ll = meshtastic_LogRecord_Level_CRITICAL;
- break;
- }
-
+ meshtastic_LogRecord_Level ll = RedirectablePrint::getLogLevel(logLevel);
auto thread = concurrency::OSThread::currentThread;
emitLogRecord(ll, thread ? thread->ThreadName.c_str() : "", format, arg);
} else
RedirectablePrint::log_to_serial(logLevel, format, arg);
-}
+}
\ No newline at end of file
diff --git a/src/airtime.cpp b/src/airtime.cpp
index 2702ee2bf..7478debb9 100644
--- a/src/airtime.cpp
+++ b/src/airtime.cpp
@@ -13,17 +13,17 @@ void AirTime::logAirtime(reportTypes reportType, uint32_t airtime_ms)
{
if (reportType == TX_LOG) {
- LOG_DEBUG("AirTime - Packet transmitted : %ums\n", airtime_ms);
+ LOG_DEBUG("Packet transmitted : %ums", airtime_ms);
this->airtimes.periodTX[0] = this->airtimes.periodTX[0] + airtime_ms;
air_period_tx[0] = air_period_tx[0] + airtime_ms;
this->utilizationTX[this->getPeriodUtilHour()] = this->utilizationTX[this->getPeriodUtilHour()] + airtime_ms;
} else if (reportType == RX_LOG) {
- LOG_DEBUG("AirTime - Packet received : %ums\n", airtime_ms);
+ LOG_DEBUG("Packet received : %ums", airtime_ms);
this->airtimes.periodRX[0] = this->airtimes.periodRX[0] + airtime_ms;
air_period_rx[0] = air_period_rx[0] + airtime_ms;
} else if (reportType == RX_ALL_LOG) {
- LOG_DEBUG("AirTime - Packet received (noise?) : %ums\n", airtime_ms);
+ LOG_DEBUG("Packet received (noise?) : %ums", airtime_ms);
this->airtimes.periodRX_ALL[0] = this->airtimes.periodRX_ALL[0] + airtime_ms;
}
@@ -50,7 +50,7 @@ void AirTime::airtimeRotatePeriod()
{
if (this->airtimes.lastPeriodIndex != this->currentPeriodIndex()) {
- LOG_DEBUG("Rotating airtimes to a new period = %u\n", this->currentPeriodIndex());
+ LOG_DEBUG("Rotating airtimes to a new period = %u", this->currentPeriodIndex());
for (int i = PERIODS_TO_LOG - 2; i >= 0; --i) {
this->airtimes.periodTX[i + 1] = this->airtimes.periodTX[i];
@@ -105,7 +105,7 @@ float AirTime::channelUtilizationPercent()
uint32_t sum = 0;
for (uint32_t i = 0; i < CHANNEL_UTILIZATION_PERIODS; i++) {
sum += this->channelUtilization[i];
- // LOG_DEBUG("ChanUtilArray %u %u\n", i, this->channelUtilization[i]);
+ // LOG_DEBUG("ChanUtilArray %u %u", i, this->channelUtilization[i]);
}
return (float(sum) / float(CHANNEL_UTILIZATION_PERIODS * 10 * 1000)) * 100;
@@ -127,7 +127,7 @@ bool AirTime::isTxAllowedChannelUtil(bool polite)
if (channelUtilizationPercent() < percentage) {
return true;
} else {
- LOG_WARN("Channel utilization is >%d percent. Skipping this opportunity to send.\n", percentage);
+ LOG_WARN("Channel utilization is >%d percent. Skipping this opportunity to send.", percentage);
return false;
}
}
@@ -138,7 +138,7 @@ bool AirTime::isTxAllowedAirUtil()
if (utilizationTXPercent() < myRegion->dutyCycle * polite_duty_cycle_percent / 100) {
return true;
} else {
- LOG_WARN("Tx air utilization is >%f percent. Skipping this opportunity to send.\n",
+ LOG_WARN("Tx air utilization is >%f percent. Skipping this opportunity to send.",
myRegion->dutyCycle * polite_duty_cycle_percent / 100);
return false;
}
@@ -209,13 +209,13 @@ int32_t AirTime::runOnce()
}
}
/*
- LOG_DEBUG("utilPeriodTX %d TX Airtime %3.2f%\n", utilPeriodTX, airTime->utilizationTXPercent());
+ LOG_DEBUG("utilPeriodTX %d TX Airtime %3.2f%", utilPeriodTX, airTime->utilizationTXPercent());
for (uint32_t i = 0; i < MINUTES_IN_HOUR; i++) {
LOG_DEBUG(
"%d,", this->utilizationTX[i]
);
}
- LOG_DEBUG("\n");
+ LOG_DEBUG("");
*/
return (1000 * 1);
}
\ No newline at end of file
diff --git a/src/buzz/buzz.cpp b/src/buzz/buzz.cpp
index e42a9c203..8db9602bc 100644
--- a/src/buzz/buzz.cpp
+++ b/src/buzz/buzz.cpp
@@ -55,6 +55,18 @@ void playBeep()
playTones(melody, sizeof(melody) / sizeof(ToneDuration));
}
+void playGPSEnableBeep()
+{
+ ToneDuration melody[] = {{NOTE_C3, DURATION_1_8}, {NOTE_FS3, DURATION_1_4}, {NOTE_CS4, DURATION_1_4}};
+ playTones(melody, sizeof(melody) / sizeof(ToneDuration));
+}
+
+void playGPSDisableBeep()
+{
+ ToneDuration melody[] = {{NOTE_CS4, DURATION_1_8}, {NOTE_FS3, DURATION_1_4}, {NOTE_C3, DURATION_1_4}};
+ playTones(melody, sizeof(melody) / sizeof(ToneDuration));
+}
+
void playStartMelody()
{
ToneDuration melody[] = {{NOTE_FS3, DURATION_1_8}, {NOTE_AS3, DURATION_1_8}, {NOTE_CS4, DURATION_1_4}};
diff --git a/src/buzz/buzz.h b/src/buzz/buzz.h
index 3883bd057..c52c3020c 100644
--- a/src/buzz/buzz.h
+++ b/src/buzz/buzz.h
@@ -3,3 +3,5 @@
void playBeep();
void playStartMelody();
void playShutdownMelody();
+void playGPSEnableBeep();
+void playGPSDisableBeep();
\ No newline at end of file
diff --git a/src/concurrency/InterruptableDelay.cpp b/src/concurrency/InterruptableDelay.cpp
index b9606e23a..8bc85436b 100644
--- a/src/concurrency/InterruptableDelay.cpp
+++ b/src/concurrency/InterruptableDelay.cpp
@@ -18,7 +18,7 @@ bool InterruptableDelay::delay(uint32_t msec)
// sem take will return false if we timed out (i.e. were not interrupted)
bool r = semaphore.take(msec);
- // LOG_DEBUG("interrupt=%d\n", r);
+ // LOG_DEBUG("interrupt=%d", r);
return !r;
}
diff --git a/src/concurrency/NotifiedWorkerThread.cpp b/src/concurrency/NotifiedWorkerThread.cpp
index 271e3e60d..d84ff0903 100644
--- a/src/concurrency/NotifiedWorkerThread.cpp
+++ b/src/concurrency/NotifiedWorkerThread.cpp
@@ -32,12 +32,12 @@ IRAM_ATTR bool NotifiedWorkerThread::notifyCommon(uint32_t v, bool overwrite)
notification = v;
if (debugNotification) {
- LOG_DEBUG("setting notification %d\n", v);
+ LOG_DEBUG("setting notification %d", v);
}
return true;
} else {
if (debugNotification) {
- LOG_DEBUG("dropping notification %d\n", v);
+ LOG_DEBUG("dropping notification %d", v);
}
return false;
}
@@ -67,7 +67,7 @@ bool NotifiedWorkerThread::notifyLater(uint32_t delay, uint32_t v, bool overwrit
if (didIt) { // If we didn't already have something queued, override the delay to be larger
setIntervalFromNow(delay); // a new version of setInterval relative to the current time
if (debugNotification) {
- LOG_DEBUG("delaying notification %u\n", delay);
+ LOG_DEBUG("delaying notification %u", delay);
}
}
diff --git a/src/concurrency/OSThread.cpp b/src/concurrency/OSThread.cpp
index f23cbe1dc..d9bb901b2 100644
--- a/src/concurrency/OSThread.cpp
+++ b/src/concurrency/OSThread.cpp
@@ -62,15 +62,15 @@ bool OSThread::shouldRun(unsigned long time)
bool r = Thread::shouldRun(time);
if (showRun && r) {
- LOG_DEBUG("Thread %s: run\n", ThreadName.c_str());
+ LOG_DEBUG("Thread %s: run", ThreadName.c_str());
}
if (showWaiting && enabled && !r) {
- LOG_DEBUG("Thread %s: wait %lu\n", ThreadName.c_str(), interval);
+ LOG_DEBUG("Thread %s: wait %lu", ThreadName.c_str(), interval);
}
if (showDisabled && !enabled) {
- LOG_DEBUG("Thread %s: disabled\n", ThreadName.c_str());
+ LOG_DEBUG("Thread %s: disabled", ThreadName.c_str());
}
return r;
@@ -86,9 +86,9 @@ void OSThread::run()
#ifdef DEBUG_HEAP
auto newHeap = memGet.getFreeHeap();
if (newHeap < heap)
- LOG_DEBUG("------ Thread %s leaked heap %d -> %d (%d) ------\n", ThreadName.c_str(), heap, newHeap, newHeap - heap);
+ LOG_DEBUG("------ Thread %s leaked heap %d -> %d (%d) ------", ThreadName.c_str(), heap, newHeap, newHeap - heap);
if (heap < newHeap)
- LOG_DEBUG("++++++ Thread %s freed heap %d -> %d (%d) ++++++\n", ThreadName.c_str(), heap, newHeap, newHeap - heap);
+ LOG_DEBUG("++++++ Thread %s freed heap %d -> %d (%d) ++++++", ThreadName.c_str(), heap, newHeap, newHeap - heap);
#endif
runned();
diff --git a/src/configuration.h b/src/configuration.h
index 047dbd727..cb2326470 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -69,6 +69,9 @@ along with this program. If not, see .
#ifndef RTC_DATA_ATTR
#define RTC_DATA_ATTR
#endif
+#ifndef EXT_RAM_BSS_ATTR
+#define EXT_RAM_BSS_ATTR EXT_RAM_ATTR
+#endif
// -----------------------------------------------------------------------------
// Regulatory overrides
@@ -111,6 +114,7 @@ along with this program. If not, see .
#define CARDKB_ADDR 0x5F
#define TDECK_KB_ADDR 0x55
#define BBQ10_KB_ADDR 0x1F
+#define MPR121_KB_ADDR 0x5A
// -----------------------------------------------------------------------------
// SENSOR
@@ -122,14 +126,17 @@ along with this program. If not, see .
#define INA_ADDR_ALTERNATE 0x41
#define INA_ADDR_WAVESHARE_UPS 0x43
#define INA3221_ADDR 0x42
+#define MAX1704X_ADDR 0x36
#define QMC6310_ADDR 0x1C
#define QMI8658_ADDR 0x6B
-#define QMC5883L_ADDR 0x1E
+#define QMC5883L_ADDR 0x0D
+#define HMC5883L_ADDR 0x1E
#define SHTC3_ADDR 0x70
#define LPS22HB_ADDR 0x5C
#define LPS22HB_ADDR_ALT 0x5D
#define SHT31_4x_ADDR 0x44
#define PMSA0031_ADDR 0x12
+#define QMA6100P_ADDR 0x12
#define AHT10_ADDR 0x38
#define RCWL9620_ADDR 0x57
#define VEML7700_ADDR 0x10
@@ -139,15 +146,20 @@ along with this program. If not, see .
#define MLX90632_ADDR 0x3A
#define DFROBOT_LARK_ADDR 0x42
#define NAU7802_ADDR 0x2A
+#define MAX30102_ADDR 0x57
+#define MLX90614_ADDR_DEF 0x5A
// -----------------------------------------------------------------------------
// ACCELEROMETER
// -----------------------------------------------------------------------------
#define MPU6050_ADDR 0x68
+#define STK8BXX_ADR 0x18
#define LIS3DH_ADR 0x18
#define BMA423_ADDR 0x19
#define LSM6DS3_ADDR 0x6A
#define BMX160_ADDR 0x69
+#define ICM20948_ADDR 0x69
+#define ICM20948_ADDR_ALT 0x68
// -----------------------------------------------------------------------------
// LED
@@ -162,6 +174,7 @@ along with this program. If not, see .
// -----------------------------------------------------------------------------
// IO Expander
// -----------------------------------------------------------------------------
+#define TCA9535_ADDR 0x20
#define TCA9555_ADDR 0x26
// -----------------------------------------------------------------------------
@@ -171,6 +184,16 @@ along with this program. If not, see .
#define GPS_THREAD_INTERVAL 200
#endif
+// -----------------------------------------------------------------------------
+// Touchscreen
+// -----------------------------------------------------------------------------
+#define FT6336U_ADDR 0x48
+
+// -----------------------------------------------------------------------------
+// BIAS-T Generator
+// -----------------------------------------------------------------------------
+#define TPS65233_ADDR 0x60
+
// convert 24-bit color to 16-bit (56K)
#define COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3))
@@ -202,6 +225,16 @@ along with this program. If not, see .
#define MINIMUM_SAFE_FREE_HEAP 1500
#endif
+#ifndef WIRE_INTERFACES_COUNT
+// Officially an NRF52 macro
+// Repurposed cross-platform to identify devices using Wire1
+#if defined(I2C_SDA1) || defined(PIN_WIRE1_SDA)
+#define WIRE_INTERFACES_COUNT 2
+#elif HAS_WIRE
+#define WIRE_INTERFACES_COUNT 1
+#endif
+#endif
+
/* Step #3: mop up with disabled values for HAS_ options not handled by the above two */
#ifndef HAS_WIFI
@@ -324,4 +357,4 @@ along with this program. If not, see .
#endif
#include "DebugConfiguration.h"
-#include "RF95Configuration.h"
\ No newline at end of file
+#include "RF95Configuration.h"
diff --git a/src/detect/LoRaRadioType.h b/src/detect/LoRaRadioType.h
index 3975153b5..a059a3668 100644
--- a/src/detect/LoRaRadioType.h
+++ b/src/detect/LoRaRadioType.h
@@ -10,7 +10,8 @@ enum LoRaRadioType {
LLCC68_RADIO,
SX1280_RADIO,
LR1110_RADIO,
- LR1120_RADIO
+ LR1120_RADIO,
+ LR1121_RADIO
};
extern LoRaRadioType radioType;
\ No newline at end of file
diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp
index 73bdf973b..4caa0f730 100644
--- a/src/detect/ScanI2C.cpp
+++ b/src/detect/ScanI2C.cpp
@@ -31,14 +31,14 @@ ScanI2C::FoundDevice ScanI2C::firstRTC() const
ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
{
- ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004};
- return firstOfOrNONE(4, types);
+ ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004, MPR121KB};
+ return firstOfOrNONE(5, types);
}
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
{
- ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160};
- return firstOfOrNONE(5, types);
+ ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P};
+ return firstOfOrNONE(8, types);
}
ScanI2C::FoundDevice ScanI2C::find(ScanI2C::DeviceType) const
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index 0a5b360de..8591b8433 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -28,6 +28,7 @@ class ScanI2C
INA260,
INA219,
INA3221,
+ MAX17048,
MCP9808,
SHT31,
SHT4X,
@@ -36,12 +37,15 @@ class ScanI2C
QMC6310,
QMI8658,
QMC5883L,
+ HMC5883L,
PMSA0031,
+ QMA6100P,
MPU6050,
LIS3DH,
BMA423,
BQ24295,
LSM6DS3,
+ TCA9535,
TCA9555,
VEML7700,
RCWL9620,
@@ -49,10 +53,17 @@ class ScanI2C
TSL2591,
OPT3001,
MLX90632,
+ MLX90614,
AHT10,
BMX160,
DFROBOT_LARK,
- NAU7802
+ NAU7802,
+ FT6336U,
+ STK8BAXX,
+ ICM20948,
+ MAX30102,
+ TPS65233,
+ MPR121KB
} DeviceType;
// typedef uint8_t DeviceAddress;
@@ -63,8 +74,9 @@ class ScanI2C
} I2CPort;
typedef struct DeviceAddress {
- I2CPort port;
- uint8_t address;
+ // set default values for ADDRESS_NONE
+ I2CPort port = I2CPort::NO_I2C;
+ uint8_t address = 0;
explicit DeviceAddress(I2CPort port, uint8_t address);
DeviceAddress();
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index 21e7ca8ac..d39c9899c 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -7,7 +7,8 @@
#include "linux/LinuxHardwareI2C.h"
#endif
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
-#include "main.h" // atecc
+#include "main.h" // atecc
+#include "meshUtils.h" // vformat
#endif
// AXP192 and AXP2101 have the same device address, we just need to identify it in Power.cpp
@@ -71,15 +72,15 @@ ScanI2C::DeviceType ScanI2CTwoWire::probeOLED(ScanI2C::DeviceAddress addr) const
r &= 0x0f;
if (r == 0x08 || r == 0x00) {
- LOG_INFO("sh1106 display found\n");
+ LOG_INFO("sh1106 display found");
o_probe = SCREEN_SH1106; // SH1106
} else if (r == 0x03 || r == 0x04 || r == 0x06 || r == 0x07) {
- LOG_INFO("ssd1306 display found\n");
+ LOG_INFO("ssd1306 display found");
o_probe = SCREEN_SSD1306; // SSD1306
}
c++;
} while ((r != r_prev) && (c < 4));
- LOG_DEBUG("0x%x subtype probed in %i tries \n", r, c);
+ LOG_DEBUG("0x%x subtype probed in %i tries ", r, c);
return o_probe;
}
@@ -88,31 +89,31 @@ void ScanI2CTwoWire::printATECCInfo() const
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
atecc.readConfigZone(false);
- LOG_DEBUG("ATECC608B Serial Number: ");
+ std::string atecc_numbers = "ATECC608B Serial Number: ";
for (int i = 0; i < 9; i++) {
- LOG_DEBUG("%02x", atecc.serialNumber[i]);
+ atecc_numbers += vformat("%02x", atecc.serialNumber[i]);
}
- LOG_DEBUG(", Rev Number: ");
+ atecc_numbers += ", Rev Number: ";
for (int i = 0; i < 4; i++) {
- LOG_DEBUG("%02x", atecc.revisionNumber[i]);
+ atecc_numbers += vformat("%02x", atecc.revisionNumber[i]);
}
- LOG_DEBUG("\n");
+ LOG_DEBUG(atecc_numbers.c_str());
- LOG_DEBUG("ATECC608B Config %s", atecc.configLockStatus ? "Locked" : "Unlocked");
- LOG_DEBUG(", Data %s", atecc.dataOTPLockStatus ? "Locked" : "Unlocked");
- LOG_DEBUG(", Slot 0 %s\n", atecc.slot0LockStatus ? "Locked" : "Unlocked");
+ LOG_DEBUG("ATECC608B Config %s, Data %s, Slot 0 %s", atecc.configLockStatus ? "Locked" : "Unlocked",
+ atecc.dataOTPLockStatus ? "Locked" : "Unlocked", atecc.slot0LockStatus ? "Locked" : "Unlocked");
+ std::string atecc_publickey = "";
if (atecc.configLockStatus && atecc.dataOTPLockStatus && atecc.slot0LockStatus) {
if (atecc.generatePublicKey() == false) {
- LOG_DEBUG("ATECC608B Error generating public key\n");
+ atecc_publickey += "ATECC608B Error generating public key";
} else {
- LOG_DEBUG("ATECC608B Public Key: ");
+ atecc_publickey += "ATECC608B Public Key: ";
for (int i = 0; i < 64; i++) {
- LOG_DEBUG("%02x", atecc.publicKey64Bytes[i]);
+ atecc_publickey += vformat("%02x", atecc.publicKey64Bytes[i]);
}
- LOG_DEBUG("\n");
}
+ LOG_DEBUG(atecc_publickey.c_str());
}
#endif
}
@@ -128,7 +129,7 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation
i2cBus->endTransmission();
delay(20);
i2cBus->requestFrom(registerLocation.i2cAddress.address, responseWidth);
- LOG_DEBUG("Wire.available() = %d\n", i2cBus->available());
+ LOG_DEBUG("Wire.available() = %d", i2cBus->available());
if (i2cBus->available() == 2) {
// Read MSB, then LSB
value = (uint16_t)i2cBus->read() << 8;
@@ -149,7 +150,7 @@ 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", port);
uint8_t err;
@@ -162,21 +163,30 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
Melopero_RV3028 rtc;
#endif
-#ifdef I2C_SDA1
+#if WIRE_INTERFACES_COUNT == 2
if (port == I2CPort::WIRE1) {
i2cBus = &Wire1;
} else {
#endif
i2cBus = &Wire;
-#ifdef I2C_SDA1
+#if WIRE_INTERFACES_COUNT == 2
}
#endif
- for (addr.address = 1; addr.address < 127; addr.address++) {
+ // We only need to scan 112 addresses, the rest is reserved for special purposes
+ // 0x00 General Call
+ // 0x01 CBUS addresses
+ // 0x02 Reserved for different bus formats
+ // 0x03 Reserved for future purposes
+ // 0x04-0x07 High Speed Master Code
+ // 0x78-0x7B 10-bit slave addressing
+ // 0x7C-0x7F Reserved for future purposes
+
+ for (addr.address = 8; addr.address < 120; addr.address++) {
if (asize != 0) {
if (!in_array(address, asize, addr.address))
continue;
- LOG_DEBUG("Scanning address 0x%x\n", addr.address);
+ LOG_DEBUG("Scanning address 0x%x", addr.address);
}
i2cBus->beginTransmission(addr.address);
#ifdef ARCH_PORTDUINO
@@ -189,7 +199,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
#endif
type = NONE;
if (err == 0) {
- LOG_DEBUG("I2C device found at address 0x%x\n", addr.address);
+ LOG_DEBUG("I2C device found at address 0x%x", addr.address);
switch (addr.address) {
case SSD1306_ADDRESS:
@@ -205,9 +215,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
#endif
{
- LOG_INFO("ATECC608B initialized\n");
+ LOG_INFO("ATECC608B initialized");
} else {
- LOG_WARN("ATECC608B initialization failed\n");
+ LOG_WARN("ATECC608B initialization failed");
}
printATECCInfo();
break;
@@ -217,7 +227,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
case RV3028_RTC:
// foundDevices[addr] = RTC_RV3028;
type = RTC_RV3028;
- LOG_INFO("RV3028 RTC found\n");
+ LOG_INFO("RV3028 RTC found");
rtc.initI2C(*i2cBus);
rtc.writeToRegister(0x35, 0x07); // no Clkout
rtc.writeToRegister(0x37, 0xB4);
@@ -225,7 +235,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
#endif
#ifdef PCF8563_RTC
- SCAN_SIMPLE_CASE(PCF8563_RTC, RTC_PCF8563, "PCF8563 RTC found\n")
+ SCAN_SIMPLE_CASE(PCF8563_RTC, RTC_PCF8563, "PCF8563 RTC found")
#endif
case CARDKB_ADDR:
@@ -233,49 +243,50 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x04), 1);
if (registerValue == 0x02) {
// KEYPAD_VERSION
- LOG_INFO("RAK14004 found\n");
+ LOG_INFO("RAK14004 found");
type = RAK14004;
} else {
- LOG_INFO("m5 cardKB found\n");
+ LOG_INFO("m5 cardKB found");
type = CARDKB;
}
break;
- SCAN_SIMPLE_CASE(TDECK_KB_ADDR, TDECKKB, "T-Deck keyboard found\n");
- SCAN_SIMPLE_CASE(BBQ10_KB_ADDR, BBQ10KB, "BB Q10 keyboard found\n");
- SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "st7567 display found\n");
+ SCAN_SIMPLE_CASE(TDECK_KB_ADDR, TDECKKB, "T-Deck keyboard found");
+ SCAN_SIMPLE_CASE(BBQ10_KB_ADDR, BBQ10KB, "BB Q10 keyboard found");
+
+ SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "st7567 display found");
#ifdef HAS_NCP5623
- SCAN_SIMPLE_CASE(NCP5623_ADDR, NCP5623, "NCP5623 RGB LED found\n");
+ SCAN_SIMPLE_CASE(NCP5623_ADDR, NCP5623, "NCP5623 RGB LED found");
#endif
#ifdef HAS_PMU
- SCAN_SIMPLE_CASE(XPOWERS_AXP192_AXP2101_ADDRESS, PMU_AXP192_AXP2101, "axp192/axp2101 PMU found\n")
+ SCAN_SIMPLE_CASE(XPOWERS_AXP192_AXP2101_ADDRESS, PMU_AXP192_AXP2101, "axp192/axp2101 PMU found")
#endif
case BME_ADDR:
case BME_ADDR_ALTERNATE:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xD0), 1); // GET_ID
switch (registerValue) {
case 0x61:
- LOG_INFO("BME-680 sensor found at address 0x%x\n", (uint8_t)addr.address);
+ LOG_INFO("BME-680 sensor found at address 0x%x", (uint8_t)addr.address);
type = BME_680;
break;
case 0x60:
- LOG_INFO("BME-280 sensor found at address 0x%x\n", (uint8_t)addr.address);
+ LOG_INFO("BME-280 sensor found at address 0x%x", (uint8_t)addr.address);
type = BME_280;
break;
case 0x55:
- LOG_INFO("BMP-085 or BMP-180 sensor found at address 0x%x\n", (uint8_t)addr.address);
+ LOG_INFO("BMP-085 or BMP-180 sensor found at address 0x%x", (uint8_t)addr.address);
type = BMP_085;
break;
default:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1); // GET_ID
switch (registerValue) {
case 0x50: // BMP-388 should be 0x50
- LOG_INFO("BMP-388 sensor found at address 0x%x\n", (uint8_t)addr.address);
+ LOG_INFO("BMP-388 sensor found at address 0x%x", (uint8_t)addr.address);
type = BMP_3XX;
break;
case 0x58: // BMP-280 should be 0x58
default:
- LOG_INFO("BMP-280 sensor found at address 0x%x\n", (uint8_t)addr.address);
+ LOG_INFO("BMP-280 sensor found at address 0x%x", (uint8_t)addr.address);
type = BMP_280;
break;
}
@@ -284,7 +295,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
break;
#ifndef HAS_NCP5623
case AHT10_ADDR:
- LOG_INFO("AHT10 sensor found at address 0x%x\n", (uint8_t)addr.address);
+ LOG_INFO("AHT10 sensor found at address 0x%x", (uint8_t)addr.address);
type = AHT10;
break;
#endif
@@ -292,97 +303,161 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
case INA_ADDR_ALTERNATE:
case INA_ADDR_WAVESHARE_UPS:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2);
- LOG_DEBUG("Register MFG_UID: 0x%x\n", registerValue);
+ LOG_DEBUG("Register MFG_UID: 0x%x", registerValue);
if (registerValue == 0x5449) {
- LOG_INFO("INA260 sensor found at address 0x%x\n", (uint8_t)addr.address);
+ LOG_INFO("INA260 sensor found at address 0x%x", (uint8_t)addr.address);
type = INA260;
} else { // Assume INA219 if INA260 ID is not found
- LOG_INFO("INA219 sensor found at address 0x%x\n", (uint8_t)addr.address);
+ LOG_INFO("INA219 sensor found at address 0x%x", (uint8_t)addr.address);
type = INA219;
}
break;
case INA3221_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2);
- LOG_DEBUG("Register MFG_UID: 0x%x\n", registerValue);
+ LOG_DEBUG("Register MFG_UID: 0x%x", registerValue);
if (registerValue == 0x5449) {
- LOG_INFO("INA3221 sensor found at address 0x%x\n", (uint8_t)addr.address);
+ LOG_INFO("INA3221 sensor found at address 0x%x", (uint8_t)addr.address);
type = INA3221;
} else {
- LOG_INFO("DFRobot Lark weather station found at address 0x%x\n", (uint8_t)addr.address);
+ LOG_INFO("DFRobot Lark weather station found at address 0x%x", (uint8_t)addr.address);
type = DFROBOT_LARK;
}
break;
case MCP9808_ADDR:
- registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x07), 2);
- if (registerValue == 0x0400) {
- type = MCP9808;
- LOG_INFO("MCP9808 sensor found\n");
- } else {
- type = LIS3DH;
- LOG_INFO("LIS3DH accelerometer found\n");
+ // We need to check for STK8BAXX first, since register 0x07 is new data flag for the z-axis and can produce some
+ // weird result. and register 0x00 doesn't seems to be colliding with MCP9808 and LIS3DH chips.
+ {
+ // Check register 0x00 for 0x8700 response to ID STK8BA53 chip.
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 2);
+ if (registerValue == 0x8700) {
+ type = STK8BAXX;
+ LOG_INFO("STK8BAXX accelerometer found");
+ break;
+ }
+
+ // Check register 0x07 for 0x0400 response to ID MCP9808 chip.
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x07), 2);
+ if (registerValue == 0x0400) {
+ type = MCP9808;
+ LOG_INFO("MCP9808 sensor found");
+ break;
+ }
+
+ // Check register 0x0F for 0x3300 response to ID LIS3DH chip.
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 2);
+ if (registerValue == 0x3300 || registerValue == 0x3333) { // RAK4631 WisBlock has LIS3DH register at 0x3333
+ type = LIS3DH;
+ LOG_INFO("LIS3DH accelerometer found");
+ }
+ break;
}
-
- break;
-
case SHT31_4x_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
if (registerValue == 0x11a2 || registerValue == 0x11da || registerValue == 0xe9c) {
type = SHT4X;
- LOG_INFO("SHT4X sensor found\n");
+ LOG_INFO("SHT4X sensor found");
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
type = OPT3001;
- LOG_INFO("OPT3001 light sensor found\n");
+ LOG_INFO("OPT3001 light sensor found");
} else {
type = SHT31;
- LOG_INFO("SHT31 sensor found\n");
+ LOG_INFO("SHT31 sensor found");
}
break;
- SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTC3, "SHTC3 sensor found\n")
- SCAN_SIMPLE_CASE(RCWL9620_ADDR, RCWL9620, "RCWL9620 sensor found\n")
+ SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTC3, "SHTC3 sensor found")
+ case RCWL9620_ADDR:
+ // get MAX30102 PARTID
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFF), 1);
+ if (registerValue == 0x15) {
+ type = MAX30102;
+ LOG_INFO("MAX30102 Health sensor found");
+ break;
+ } else {
+ type = RCWL9620;
+ LOG_INFO("RCWL9620 sensor found");
+ }
+ break;
case LPS22HB_ADDR_ALT:
- SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB sensor found\n")
+ SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB sensor found")
- SCAN_SIMPLE_CASE(QMC6310_ADDR, QMC6310, "QMC6310 Highrate 3-Axis magnetic sensor found\n")
+ SCAN_SIMPLE_CASE(QMC6310_ADDR, QMC6310, "QMC6310 Highrate 3-Axis magnetic sensor found")
case QMI8658_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0A), 1); // get ID
if (registerValue == 0xC0) {
type = BQ24295;
- LOG_INFO("BQ24295 PMU found\n");
+ LOG_INFO("BQ24295 PMU found");
break;
}
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 1); // get ID
if (registerValue == 0x6A) {
type = LSM6DS3;
- LOG_INFO("LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address);
+ LOG_INFO("LSM6DS3 accelerometer found at address 0x%x", (uint8_t)addr.address);
} else {
type = QMI8658;
- LOG_INFO("QMI8658 Highrate 6-Axis inertial measurement sensor found\n");
+ LOG_INFO("QMI8658 Highrate 6-Axis inertial measurement sensor found");
}
break;
- SCAN_SIMPLE_CASE(QMC5883L_ADDR, QMC5883L, "QMC5883L Highrate 3-Axis magnetic sensor found\n")
+ SCAN_SIMPLE_CASE(QMC5883L_ADDR, QMC5883L, "QMC5883L Highrate 3-Axis magnetic sensor found")
+ SCAN_SIMPLE_CASE(HMC5883L_ADDR, HMC5883L, "HMC5883L 3-Axis digital compass found")
+#ifdef HAS_QMA6100P
+ SCAN_SIMPLE_CASE(QMA6100P_ADDR, QMA6100P, "QMA6100P accelerometer found")
+#else
+ SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031 air quality sensor found")
+#endif
+ SCAN_SIMPLE_CASE(BMA423_ADDR, BMA423, "BMA423 accelerometer found");
+ SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3 accelerometer found at address 0x%x", (uint8_t)addr.address);
+ SCAN_SIMPLE_CASE(TCA9535_ADDR, TCA9535, "TCA9535 I2C expander found");
+ SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555 I2C expander found");
+ SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700 light sensor found");
+ SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591 light sensor found");
+ SCAN_SIMPLE_CASE(OPT3001_ADDR, OPT3001, "OPT3001 light sensor found");
+ SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632 IR temp sensor found");
+ SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802 based scale found");
+ SCAN_SIMPLE_CASE(FT6336U_ADDR, FT6336U, "FT6336U touchscreen found");
+ SCAN_SIMPLE_CASE(MAX1704X_ADDR, MAX17048, "MAX17048 lipo fuel gauge found");
+#ifdef HAS_TPS65233
+ SCAN_SIMPLE_CASE(TPS65233_ADDR, TPS65233, "TPS65233 BIAS-T found");
+#endif
- 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");
+ case MLX90614_ADDR_DEF:
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0e), 1);
+ if (registerValue == 0x5a) {
+ type = MLX90614;
+ LOG_INFO("MLX90614 IR temp sensor found");
+ } else {
+ type = MPR121KB;
+ LOG_INFO("MPR121KB keyboard found");
+ }
+ break;
+
+ case ICM20948_ADDR: // same as BMX160_ADDR
+ case ICM20948_ADDR_ALT: // same as MPU6050_ADDR
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
+ if (registerValue == 0xEA) {
+ type = ICM20948;
+ LOG_INFO("ICM20948 9-dof motion processor found");
+ break;
+ } else if (addr.address == BMX160_ADDR) {
+ type = BMX160;
+ LOG_INFO("BMX160 accelerometer found");
+ break;
+ } else {
+ type = MPU6050;
+ LOG_INFO("MPU6050 accelerometer found");
+ break;
+ }
+ break;
default:
- LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address);
+ LOG_INFO("Device found at address 0x%x was not able to be enumerated", addr.address);
}
} else if (err == 4) {
- LOG_ERROR("Unknown error at address 0x%x\n", addr.address);
+ LOG_ERROR("Unknown error at address 0x%x", addr.address);
}
// Check if a type was found for the enumerated device - save, if so
@@ -403,7 +478,7 @@ TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const
if (address.port == ScanI2C::I2CPort::WIRE) {
return &Wire;
} else {
-#ifdef I2C_SDA1
+#if WIRE_INTERFACES_COUNT == 2
return &Wire1;
#else
return &Wire;
@@ -415,4 +490,4 @@ size_t ScanI2CTwoWire::countDevices() const
{
return foundDevices.size();
}
-#endif
+#endif
\ No newline at end of file
diff --git a/src/detect/axpDebug.h b/src/detect/axpDebug.h
deleted file mode 100644
index fc95447aa..000000000
--- a/src/detect/axpDebug.h
+++ /dev/null
@@ -1,19 +0,0 @@
-#if 0
-// Turn off for now
-uint32_t axpDebugRead()
-{
- axp.debugCharging();
- LOG_DEBUG("vbus current %f\n", axp.getVbusCurrent());
- LOG_DEBUG("charge current %f\n", axp.getBattChargeCurrent());
- LOG_DEBUG("bat voltage %f\n", axp.getBattVoltage());
- LOG_DEBUG("batt pct %d\n", axp.getBattPercentage());
- LOG_DEBUG("is battery connected %d\n", axp.isBatteryConnect());
- LOG_DEBUG("is USB connected %d\n", axp.isVBUSPlug());
- LOG_DEBUG("is charging %d\n", axp.isChargeing());
-
- return 30 * 1000;
-}
-
-Periodic axpDebugOutput(axpDebugRead);
-axpDebugOutput.setup();
-#endif
\ No newline at end of file
diff --git a/src/detect/einkScan.h b/src/detect/einkScan.h
index 6915709de..d20c7b6e5 100644
--- a/src/detect/einkScan.h
+++ b/src/detect/einkScan.h
@@ -59,9 +59,9 @@ void scanEInkDevice(void)
d_writeCommand(0x20);
eink_found = (d_waitWhileBusy(150) > 0) ? true : false;
if (eink_found)
- LOG_DEBUG("EInk display found\n");
+ LOG_DEBUG("EInk display found");
else
- LOG_DEBUG("EInk display not found\n");
+ LOG_DEBUG("EInk display not found");
SPI1.end();
}
#endif
\ No newline at end of file
diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index 12ef34c52..1e9e21224 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -6,6 +6,9 @@
#include "NodeDB.h"
#include "PowerMon.h"
#include "RTC.h"
+#include "Throttle.h"
+#include "buzz.h"
+#include "meshUtils.h"
#include "main.h" // pmu_found
#include "sleep.h"
@@ -17,6 +20,7 @@
#ifdef ARCH_PORTDUINO
#include "PortduinoGlue.h"
#include "meshUtils.h"
+#include
#include
#endif
@@ -26,6 +30,8 @@
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
HardwareSerial *GPS::_serial_gps = &Serial1;
+#elif defined(ARCH_RP2040)
+SerialUART *GPS::_serial_gps = &Serial1;
#else
HardwareSerial *GPS::_serial_gps = NULL;
#endif
@@ -58,7 +64,8 @@ const char *getGPSPowerStateString(GPSPowerState state)
case GPS_OFF:
return "OFF";
default:
- assert(false); // Unhandled enum value..
+ assert(false); // Unhandled enum value..
+ return "FALSE"; // to make new ESP-IDF happy
}
}
@@ -86,9 +93,9 @@ 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 const *payload = (uint32_t *)(message + 6);
for (size_t i = 0; i < (length - 10) / 4; i++) {
- uint32_t pl = payload[i];
+ uint32_t pl = 0;
+ memcpy(&pl, (message + 6) + (i * sizeof(uint32_t)), sizeof(uint32_t)); // avoid pointer dereference
cksum += pl;
}
@@ -149,7 +156,7 @@ uint8_t GPS::makeCASPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_siz
CASChecksum(UBXscratch, (payload_size + 10));
#if defined(GPS_DEBUG) && defined(DEBUG_PORT)
- LOG_DEBUG("Constructed CAS packet: \n");
+ LOG_DEBUG("Constructed CAS packet: ");
DEBUG_PORT.hexDump(MESHTASTIC_LOG_LEVEL_DEBUG, UBXscratch, payload_size + 10);
#endif
return (payload_size + 10);
@@ -161,30 +168,33 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
uint8_t b;
int bytesRead = 0;
uint32_t startTimeout = millis() + waitMillis;
+#ifdef GPS_DEBUG
+ std::string debugmsg = "";
+#endif
while (millis() < startTimeout) {
if (_serial_gps->available()) {
b = _serial_gps->read();
#ifdef GPS_DEBUG
- LOG_DEBUG("%02X", (char *)buffer);
+ debugmsg += vformat("%c", (b >= 32 && b <= 126) ? b : '.');
#endif
buffer[bytesRead] = b;
bytesRead++;
if ((bytesRead == 767) || (b == '\r')) {
if (strnstr((char *)buffer, message, bytesRead) != nullptr) {
#ifdef GPS_DEBUG
- LOG_DEBUG("\r");
+ LOG_DEBUG("Found: %s", message); // Log the found message
#endif
return GNSS_RESPONSE_OK;
} else {
bytesRead = 0;
+#ifdef GPS_DEBUG
+ LOG_DEBUG(debugmsg.c_str());
+#endif
}
}
}
}
-#ifdef GPS_DEBUG
- LOG_DEBUG("\n");
-#endif
return GNSS_RESPONSE_NONE;
}
@@ -201,7 +211,7 @@ GPS_RESPONSE GPS::getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMilli
// ACK-NACK| 0xBA | 0xCE | 0x04 | 0x00 | 0x05 | 0x00 | 0xXX | 0xXX | 0x00 | 0x00 | 0xXX | 0xXX | 0xXX | 0xXX |
// ACK-ACK | 0xBA | 0xCE | 0x04 | 0x00 | 0x05 | 0x01 | 0xXX | 0xXX | 0x00 | 0x00 | 0xXX | 0xXX | 0xXX | 0xXX |
- while (millis() - startTime < waitMillis) {
+ while (Throttle::isWithinTimespanMs(startTime, waitMillis)) {
if (_serial_gps->available()) {
buffer[bufferPos++] = _serial_gps->read();
@@ -227,7 +237,7 @@ GPS_RESPONSE GPS::getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMilli
// Check for an ACK-ACK for the specified class and message id
if ((msg_cls == 0x05) && (msg_msg_id == 0x01) && payload_cls == class_id && payload_msg == msg_id) {
#ifdef GPS_DEBUG
- LOG_INFO("Got ACK for class %02X message %02X in %d millis.\n", class_id, msg_id, millis() - startTime);
+ LOG_INFO("Got ACK for class %02X message %02X in %d millis.", class_id, msg_id, millis() - startTime);
#endif
return GNSS_RESPONSE_OK;
}
@@ -235,7 +245,7 @@ GPS_RESPONSE GPS::getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMilli
// Check for an ACK-NACK for the specified class and message id
if ((msg_cls == 0x05) && (msg_msg_id == 0x00) && payload_cls == class_id && payload_msg == msg_id) {
#ifdef GPS_DEBUG
- LOG_WARN("Got NACK for class %02X message %02X in %d millis.\n", class_id, msg_id, millis() - startTime);
+ LOG_WARN("Got NACK for class %02X message %02X in %d millis.", class_id, msg_id, millis() - startTime);
#endif
return GNSS_RESPONSE_NAK;
}
@@ -258,6 +268,9 @@ GPS_RESPONSE GPS::getACK(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis)
uint32_t startTime = millis();
const char frame_errors[] = "More than 100 frame errors";
int sCounter = 0;
+#ifdef GPS_DEBUG
+ std::string debugmsg = "";
+#endif
for (int j = 2; j < 6; j++) {
buf[8] += buf[j];
@@ -270,11 +283,11 @@ GPS_RESPONSE GPS::getACK(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis)
buf[9] += buf[8];
}
- while (millis() - startTime < waitMillis) {
+ while (Throttle::isWithinTimespanMs(startTime, waitMillis)) {
if (ack > 9) {
#ifdef GPS_DEBUG
- LOG_DEBUG("\n");
- LOG_INFO("Got ACK for class %02X message %02X in %d millis.\n", class_id, msg_id, millis() - startTime);
+ LOG_DEBUG("");
+ LOG_INFO("Got ACK for class %02X message %02X in %d millis.", class_id, msg_id, millis() - startTime);
#endif
return GNSS_RESPONSE_OK; // ACK received
}
@@ -283,22 +296,26 @@ GPS_RESPONSE GPS::getACK(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis)
if (b == frame_errors[sCounter]) {
sCounter++;
if (sCounter == 26) {
+#ifdef GPS_DEBUG
+
+ LOG_DEBUG(debugmsg.c_str());
+#endif
return GNSS_RESPONSE_FRAME_ERRORS;
}
} else {
sCounter = 0;
}
#ifdef GPS_DEBUG
- LOG_DEBUG("%02X", b);
+ debugmsg += vformat("%02X", b);
#endif
if (b == buf[ack]) {
ack++;
} else {
if (ack == 3 && b == 0x00) { // UBX-ACK-NAK message
#ifdef GPS_DEBUG
- LOG_DEBUG("\n");
+ LOG_DEBUG(debugmsg.c_str());
#endif
- LOG_WARN("Got NAK for class %02X message %02X\n", class_id, msg_id);
+ LOG_WARN("Got NAK for class %02X message %02X", class_id, msg_id);
return GNSS_RESPONSE_NAK; // NAK received
}
ack = 0; // Reset the acknowledgement counter
@@ -306,8 +323,8 @@ GPS_RESPONSE GPS::getACK(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis)
}
}
#ifdef GPS_DEBUG
- LOG_DEBUG("\n");
- LOG_WARN("No response for class %02X message %02X\n", class_id, msg_id);
+ LOG_DEBUG(debugmsg.c_str());
+ LOG_WARN("No response for class %02X message %02X", class_id, msg_id);
#endif
return GNSS_RESPONSE_NONE; // No response received within timeout
}
@@ -325,9 +342,9 @@ int GPS::getACK(uint8_t *buffer, uint16_t size, uint8_t requestedClass, uint8_t
{
uint16_t ubxFrameCounter = 0;
uint32_t startTime = millis();
- uint16_t needRead;
+ uint16_t needRead = 0;
- while (millis() - startTime < waitMillis) {
+ while (Throttle::isWithinTimespanMs(startTime, waitMillis)) {
if (_serial_gps->available()) {
int c = _serial_gps->read();
switch (ubxFrameCounter) {
@@ -380,7 +397,7 @@ int GPS::getACK(uint8_t *buffer, uint16_t size, uint8_t requestedClass, uint8_t
} else {
// return payload length
#ifdef GPS_DEBUG
- LOG_INFO("Got ACK for class %02X message %02X in %d millis.\n", requestedClass, requestedID,
+ LOG_INFO("Got ACK for class %02X message %02X in %d millis.", requestedClass, requestedID,
millis() - startTime);
#endif
return needRead;
@@ -392,29 +409,29 @@ int GPS::getACK(uint8_t *buffer, uint16_t size, uint8_t requestedClass, uint8_t
}
}
}
- // LOG_WARN("No response for class %02X message %02X\n", requestedClass, requestedID);
+ // LOG_WARN("No response for class %02X message %02X", requestedClass, requestedID);
return 0;
}
bool GPS::setup()
{
- int msglen = 0;
if (!didSerialInit) {
+ int msglen = 0;
if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
// if GPS_BAUDRATE is specified in variant (i.e. not 9600), skip to the specified rate.
- if (speedSelect == 0 && GPS_BAUDRATE != serialSpeeds[speedSelect]) {
+ if (speedSelect == 0 && probeTries == 2 && GPS_BAUDRATE != serialSpeeds[speedSelect]) {
speedSelect = std::find(serialSpeeds, std::end(serialSpeeds), GPS_BAUDRATE) - serialSpeeds;
}
- LOG_DEBUG("Probing for GPS at %d \n", serialSpeeds[speedSelect]);
+ LOG_DEBUG("Probing for GPS at %d", serialSpeeds[speedSelect]);
gnssModel = probe(serialSpeeds[speedSelect]);
if (gnssModel == GNSS_MODEL_UNKNOWN) {
if (++speedSelect == sizeof(serialSpeeds) / sizeof(int)) {
speedSelect = 0;
if (--probeTries == 0) {
- LOG_WARN("Giving up on GPS probe and setting to 9600.\n");
+ LOG_WARN("Giving up on GPS probe and setting to %d", GPS_BAUDRATE);
return true;
}
}
@@ -459,6 +476,18 @@ bool GPS::setup()
// Switch to Fitness Mode, for running and walking purpose with low speed (<5 m/s)
_serial_gps->write("$PMTK886,1*29\r\n");
delay(250);
+ } else if (gnssModel == GNSS_MODEL_MTK_PA1616S) {
+ // PA1616S is used in some GPS breakout boards from Adafruit
+ // PA1616S does not have GLONASS capability. PA1616D does, but is not implemented here.
+ _serial_gps->write("$PMTK353,1,0,0,0,0*2A\r\n");
+ // Above command will reset the GPS and takes longer before it will accept new commands
+ delay(1000);
+ // Only ask for RMC and GGA (GNRMC and GNGGA)
+ _serial_gps->write("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n");
+ delay(250);
+ // Enable SBAS / WAAS
+ _serial_gps->write("$PMTK301,2*2E\r\n");
+ delay(250);
} else if (gnssModel == GNSS_MODEL_ATGM336H) {
// Set the intial configuration of the device - these _should_ work for most AT6558 devices
msglen = makeCASPacket(0x06, 0x07, sizeof(_message_CAS_CFG_NAVX_CONF), _message_CAS_CFG_NAVX_CONF);
@@ -483,15 +512,15 @@ bool GPS::setup()
msglen = makeCASPacket(0x06, 0x01, sizeof(cas_cfg_msg_packet), cas_cfg_msg_packet);
_serial_gps->write(UBXscratch, msglen);
if (getACKCas(0x06, 0x01, 250) != GNSS_RESPONSE_OK) {
- LOG_WARN("ATGM336H - Could not enable NMEA MSG: %d\n", fields[i]);
+ LOG_WARN("ATGM336H - Could not enable NMEA MSG: %d", fields[i]);
}
}
} else if (gnssModel == GNSS_MODEL_UC6580) {
// The Unicore UC6580 can use a lot of sat systems, enable it to
- // use GPS L1 & L5 + BDS B1I & B2a + GLONASS L1 + GALILEO E1 & E5a + SBAS
+ // use GPS L1 & L5 + BDS B1I & B2a + GLONASS L1 + GALILEO E1 & E5a + SBAS + QZSS
// This will reset the receiver, so wait a bit afterwards
// The paranoid will wait for the OK*04 confirmation response after each command.
- _serial_gps->write("$CFGSYS,h25155\r\n");
+ _serial_gps->write("$CFGSYS,h35155\r\n");
delay(750);
// Must be done after the CFGSYS command
// Turn off GSV messages, we don't really care about which and where the sats are, maybe someday.
@@ -505,289 +534,163 @@ bool GPS::setup()
delay(250);
_serial_gps->write("$CFGMSG,6,1,0\r\n");
delay(250);
- } else if (gnssModel == GNSS_MODEL_AG3335) {
+ } else if (IS_ONE_OF(gnssModel, GNSS_MODEL_AG3335, GNSS_MODEL_AG3352)) {
- _serial_gps->write("$PAIR066,1,0,1,0,0,1*3B"); // Enable GPS+GALILEO+NAVIC
+ _serial_gps->write("$PAIR066,1,0,1,0,0,1*3B\r\n"); // Enable GPS+GALILEO+NAVIC
// Configure NMEA (sentences will output once per fix)
- _serial_gps->write("$PAIR062,0,0*3F"); // GGA ON
- _serial_gps->write("$PAIR062,1,0*3F"); // GLL OFF
- _serial_gps->write("$PAIR062,2,1*3D"); // GSA ON
- _serial_gps->write("$PAIR062,3,0*3D"); // GSV OFF
- _serial_gps->write("$PAIR062,4,0*3B"); // RMC ON
- _serial_gps->write("$PAIR062,5,0*3B"); // VTG OFF
- _serial_gps->write("$PAIR062,6,1*39"); // ZDA ON
+ _serial_gps->write("$PAIR062,0,1*3F\r\n"); // GGA ON
+ _serial_gps->write("$PAIR062,1,0*3F\r\n"); // GLL OFF
+ _serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF
+ _serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF
+ _serial_gps->write("$PAIR062,4,1*3B\r\n"); // RMC ON
+ _serial_gps->write("$PAIR062,5,0*3B\r\n"); // VTG OFF
+ _serial_gps->write("$PAIR062,6,0*38\r\n"); // ZDA ON
delay(250);
- _serial_gps->write("$PAIR513*3D"); // save configuration
+ _serial_gps->write("$PAIR513*3D\r\n"); // save configuration
+ } else if (gnssModel == GNSS_MODEL_UBLOX6) {
+ clearBuffer();
+ SEND_UBX_PACKET(0x06, 0x02, _message_DISABLE_TXT_INFO, "disable text info messages", 500);
+ SEND_UBX_PACKET(0x06, 0x39, _message_JAM_6_7, "enable interference resistance", 500);
+ SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5, "configure NAVX5 settings", 500);
- } else if (gnssModel == GNSS_MODEL_UBLOX) {
- // Configure GNSS system to GPS+SBAS+GLONASS (Module may restart after this command)
- // We need set it because by default it is GPS only, and we want to use GLONASS too
- // Also we need SBAS for better accuracy and extra features
- // ToDo: Dynamic configure GNSS systems depending of LoRa region
+ // Turn off unwanted NMEA messages, set update rate
+ SEND_UBX_PACKET(0x06, 0x08, _message_1HZ, "set GPS update rate", 500);
+ SEND_UBX_PACKET(0x06, 0x01, _message_GLL, "disable NMEA GLL", 500);
+ SEND_UBX_PACKET(0x06, 0x01, _message_GSA, "enable NMEA GSA", 500);
+ SEND_UBX_PACKET(0x06, 0x01, _message_GSV, "disable NMEA GSV", 500);
+ SEND_UBX_PACKET(0x06, 0x01, _message_VTG, "disable NMEA VTG", 500);
+ SEND_UBX_PACKET(0x06, 0x01, _message_RMC, "enable NMEA RMC", 500);
+ SEND_UBX_PACKET(0x06, 0x01, _message_GGA, "enable NMEA GGA", 500);
- if (strncmp(info.hwVersion, "000A0000", 8) != 0) {
- if (strncmp(info.hwVersion, "00040007", 8) != 0) {
- // The original ublox Neo-6 is GPS only and doesn't support the UBX-CFG-GNSS message
- // Max7 seems to only support GPS *or* GLONASS
- // Neo-7 is supposed to support GPS *and* GLONASS but NAKs the CFG-GNSS command to do it
- // So treat all the u-blox 7 series as GPS only
- // M8 can support 3 constallations at once so turn on GPS, GLONASS and Galileo (or BeiDou)
+ clearBuffer();
+ SEND_UBX_PACKET(0x06, 0x11, _message_CFG_RXM_ECO, "enable powersaving ECO mode for Neo-6", 500);
+ SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "enable powersaving details for GPS", 500);
+ SEND_UBX_PACKET(0x06, 0x01, _message_AID, "disable UBX-AID", 500);
- if (strncmp(info.hwVersion, "00070000", 8) == 0) {
- LOG_DEBUG("Setting GPS+SBAS\n");
- msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS_7), _message_GNSS_7);
- _serial_gps->write(UBXscratch, msglen);
- } else {
- msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS_8), _message_GNSS_8);
- _serial_gps->write(UBXscratch, msglen);
- }
-
- if (getACK(0x06, 0x3e, 800) == GNSS_RESPONSE_NAK) {
- // It's not critical if the module doesn't acknowledge this configuration.
- LOG_INFO("Unable to reconfigure GNSS - defaults maintained. Is this module GPS-only?\n");
- } else {
- if (strncmp(info.hwVersion, "00070000", 8) == 0) {
- LOG_INFO("GNSS configured for GPS+SBAS. Pause for 0.75s before sending next command.\n");
- } else {
- LOG_INFO(
- "GNSS configured for GPS+SBAS+GLONASS+Galileo. Pause for 0.75s before sending next command.\n");
- }
- // Documentation say, we need wait atleast 0.5s after reconfiguration of GNSS module, before sending next
- // commands for the M8 it tends to be more... 1 sec should be enough ;>)
- delay(1000);
- }
- }
- // Disable Text Info messages
- msglen = makeUBXPacket(0x06, 0x02, sizeof(_message_DISABLE_TXT_INFO), _message_DISABLE_TXT_INFO);
- clearBuffer();
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x02, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to disable text info messages.\n");
- }
- // ToDo add M10 tests for below
- if (strncmp(info.hwVersion, "00080000", 8) == 0) {
- msglen = makeUBXPacket(0x06, 0x39, sizeof(_message_JAM_8), _message_JAM_8);
- clearBuffer();
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x39, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable interference resistance.\n");
- }
-
- msglen = makeUBXPacket(0x06, 0x23, sizeof(_message_NAVX5_8), _message_NAVX5_8);
- clearBuffer();
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x23, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to configure NAVX5_8 settings.\n");
- }
- } else {
- msglen = makeUBXPacket(0x06, 0x39, sizeof(_message_JAM_6_7), _message_JAM_6_7);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x39, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable interference resistance.\n");
- }
-
- msglen = makeUBXPacket(0x06, 0x23, sizeof(_message_NAVX5), _message_NAVX5);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x23, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to configure NAVX5 settings.\n");
- }
- }
- // Turn off unwanted NMEA messages, set update rate
-
- msglen = makeUBXPacket(0x06, 0x08, sizeof(_message_1HZ), _message_1HZ);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x08, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to set GPS update rate.\n");
- }
-
- msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_GLL), _message_GLL);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to disable NMEA GLL.\n");
- }
-
- msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_GSA), _message_GSA);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to Enable NMEA GSA.\n");
- }
-
- msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_GSV), _message_GSV);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to disable NMEA GSV.\n");
- }
-
- msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_VTG), _message_VTG);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to disable NMEA VTG.\n");
- }
-
- msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_RMC), _message_RMC);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable NMEA RMC.\n");
- }
-
- msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_GGA), _message_GGA);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable NMEA GGA.\n");
- }
-
- if (uBloxProtocolVersion >= 18) {
- msglen = makeUBXPacket(0x06, 0x86, sizeof(_message_PMS), _message_PMS);
- clearBuffer();
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x86, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable powersaving for GPS.\n");
- }
- msglen = makeUBXPacket(0x06, 0x3B, sizeof(_message_CFG_PM2), _message_CFG_PM2);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x3B, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable powersaving details for GPS.\n");
- }
- // For M8 we want to enable NMEA vserion 4.10 so we can see the additional sats.
- if (strncmp(info.hwVersion, "00080000", 8) == 0) {
- msglen = makeUBXPacket(0x06, 0x17, sizeof(_message_NMEA), _message_NMEA);
- clearBuffer();
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x17, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable NMEA 4.10.\n");
- }
- }
- } else {
- if (strncmp(info.hwVersion, "00040007", 8) == 0) { // This PSM mode is only for Neo-6
- msglen = makeUBXPacket(0x06, 0x11, 0x2, _message_CFG_RXM_ECO);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x11, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable powersaving ECO mode for Neo-6.\n");
- }
- msglen = makeUBXPacket(0x06, 0x3B, sizeof(_message_CFG_PM2), _message_CFG_PM2);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x3B, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable powersaving details for GPS.\n");
- }
- msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_AID), _message_AID);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to disable UBX-AID.\n");
- }
- } else {
- msglen = makeUBXPacket(0x06, 0x11, 0x2, _message_CFG_RXM_PSM);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x11, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable powersaving mode for GPS.\n");
- }
-
- msglen = makeUBXPacket(0x06, 0x3B, sizeof(_message_CFG_PM2), _message_CFG_PM2);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x3B, 500) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable powersaving details for GPS.\n");
- }
- }
- }
- } else {
- // LOG_INFO("u-blox M10 hardware found.\n");
- delay(1000);
- // First disable all NMEA messages in RAM layer
- msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_NMEA_RAM), _message_VALSET_DISABLE_NMEA_RAM);
- clearBuffer();
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to disable NMEA messages for M10 GPS RAM.\n");
- }
- delay(250);
- // Next disable unwanted NMEA messages in BBR layer
- msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_NMEA_BBR), _message_VALSET_DISABLE_NMEA_BBR);
- clearBuffer();
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to disable NMEA messages for M10 GPS BBR.\n");
- }
- delay(250);
- // Disable Info txt messages in RAM layer
- msglen =
- makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_TXT_INFO_RAM), _message_VALSET_DISABLE_TXT_INFO_RAM);
- clearBuffer();
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to disable Info messages for M10 GPS RAM.\n");
- }
- delay(250);
- // Next disable Info txt messages in BBR layer
- msglen =
- makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_TXT_INFO_BBR), _message_VALSET_DISABLE_TXT_INFO_BBR);
- clearBuffer();
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to disable Info messages for M10 GPS BBR.\n");
- }
- // Do M10 configuration for Power Management.
-
- msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_PM_RAM), _message_VALSET_PM_RAM);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable powersaving for M10 GPS RAM.\n");
- }
- msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_PM_BBR), _message_VALSET_PM_BBR);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable powersaving for M10 GPS BBR.\n");
- }
-
- delay(250);
- msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_ITFM_RAM), _message_VALSET_ITFM_RAM);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable Jamming detection M10 GPS RAM.\n");
- }
- msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_ITFM_BBR), _message_VALSET_ITFM_BBR);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable Jamming detection M10 GPS BBR.\n");
- }
-
- // Here is where the init commands should go to do further M10 initialization.
- delay(250);
- msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_SBAS_RAM), _message_VALSET_DISABLE_SBAS_RAM);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to disable SBAS M10 GPS RAM.\n");
- }
- delay(750); // will cause a receiver restart so wait a bit
- msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_SBAS_BBR), _message_VALSET_DISABLE_SBAS_BBR);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to disable SBAS M10 GPS BBR.\n");
- }
- delay(750); // will cause a receiver restart so wait a bit
- // Done with initialization, Now enable wanted NMEA messages in BBR layer so they will survive a periodic sleep.
- msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_ENABLE_NMEA_BBR), _message_VALSET_ENABLE_NMEA_BBR);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable messages for M10 GPS BBR.\n");
- }
- delay(250);
- // Next enable wanted NMEA messages in RAM layer
- msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_ENABLE_NMEA_RAM), _message_VALSET_ENABLE_NMEA_RAM);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable messages for M10 GPS RAM.\n");
- }
- // 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.
- }
msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE);
_serial_gps->write(UBXscratch, msglen);
if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to save GNSS module configuration.\n");
+ LOG_WARN("Unable to save GNSS module configuration.");
} else {
- LOG_INFO("GNSS module configuration saved!\n");
+ LOG_INFO("GNSS module configuration saved!");
+ }
+ } else if (IS_ONE_OF(gnssModel, GNSS_MODEL_UBLOX7, GNSS_MODEL_UBLOX8, GNSS_MODEL_UBLOX9)) {
+ if (gnssModel == GNSS_MODEL_UBLOX7) {
+ LOG_DEBUG("Setting GPS+SBAS");
+ msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS_7), _message_GNSS_7);
+ _serial_gps->write(UBXscratch, msglen);
+ } else { // 8,9
+ msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS_8), _message_GNSS_8);
+ _serial_gps->write(UBXscratch, msglen);
+ }
+
+ if (getACK(0x06, 0x3e, 800) == GNSS_RESPONSE_NAK) {
+ // It's not critical if the module doesn't acknowledge this configuration.
+ LOG_INFO("reconfigure GNSS - defaults maintained. Is this module GPS-only?");
+ } else {
+ if (gnssModel == GNSS_MODEL_UBLOX7) {
+ LOG_INFO("GNSS configured for GPS+SBAS.");
+ } else { // 8,9
+ LOG_INFO("GNSS configured for GPS+SBAS+GLONASS+Galileo.");
+ }
+ // Documentation say, we need wait atleast 0.5s after reconfiguration of GNSS module, before sending next
+ // commands for the M8 it tends to be more... 1 sec should be enough ;>)
+ delay(1000);
+ }
+
+ // Disable Text Info messages //6,7,8,9
+ clearBuffer();
+ SEND_UBX_PACKET(0x06, 0x02, _message_DISABLE_TXT_INFO, "disable text info messages", 500);
+
+ if (gnssModel == GNSS_MODEL_UBLOX8) { // 8
+ clearBuffer();
+ SEND_UBX_PACKET(0x06, 0x39, _message_JAM_8, "enable interference resistance", 500);
+
+ clearBuffer();
+ SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5_8, "configure NAVX5_8 settings", 500);
+ } else { // 6,7,9
+ SEND_UBX_PACKET(0x06, 0x39, _message_JAM_6_7, "enable interference resistance", 500);
+ SEND_UBX_PACKET(0x06, 0x23, _message_NAVX5, "configure NAVX5 settings", 500);
+ }
+ // Turn off unwanted NMEA messages, set update rate
+ SEND_UBX_PACKET(0x06, 0x08, _message_1HZ, "set GPS update rate", 500);
+ SEND_UBX_PACKET(0x06, 0x01, _message_GLL, "disable NMEA GLL", 500);
+ SEND_UBX_PACKET(0x06, 0x01, _message_GSA, "enable NMEA GSA", 500);
+ SEND_UBX_PACKET(0x06, 0x01, _message_GSV, "disable NMEA GSV", 500);
+ SEND_UBX_PACKET(0x06, 0x01, _message_VTG, "disable NMEA VTG", 500);
+ SEND_UBX_PACKET(0x06, 0x01, _message_RMC, "enable NMEA RMC", 500);
+ SEND_UBX_PACKET(0x06, 0x01, _message_GGA, "enable NMEA GGA", 500);
+
+ if (uBloxProtocolVersion >= 18) {
+ clearBuffer();
+ SEND_UBX_PACKET(0x06, 0x86, _message_PMS, "enable powersaving for GPS", 500);
+ SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "enable powersaving details for GPS", 500);
+
+ // For M8 we want to enable NMEA vserion 4.10 so we can see the additional sats.
+ if (gnssModel == GNSS_MODEL_UBLOX8) {
+ clearBuffer();
+ SEND_UBX_PACKET(0x06, 0x17, _message_NMEA, "enable NMEA 4.10", 500);
+ }
+ } else {
+ SEND_UBX_PACKET(0x06, 0x11, _message_CFG_RXM_PSM, "enable powersaving mode for GPS", 500);
+ SEND_UBX_PACKET(0x06, 0x3B, _message_CFG_PM2, "enable powersaving details for GPS", 500);
+ }
+
+ msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to save GNSS module configuration.");
+ } else {
+ LOG_INFO("GNSS module configuration saved!");
+ }
+ } else if (gnssModel == GNSS_MODEL_UBLOX10) {
+ delay(1000);
+ clearBuffer();
+ SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_NMEA_RAM, "disable NMEA messages in M10 RAM", 300);
+ delay(750);
+ clearBuffer();
+ SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_NMEA_BBR, "disable NMEA messages in M10 BBR", 300);
+ delay(750);
+ clearBuffer();
+ SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_TXT_INFO_RAM, "disable Info messages for M10 GPS RAM", 300);
+ delay(750);
+ // Next disable Info txt messages in BBR layer
+ clearBuffer();
+ SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_TXT_INFO_BBR, "disable Info messages for M10 GPS BBR", 300);
+ delay(750);
+ // Do M10 configuration for Power Management.
+ SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_PM_RAM, "enable powersaving for M10 GPS RAM", 300);
+ delay(750);
+ SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_PM_BBR, "enable powersaving for M10 GPS BBR", 300);
+ delay(750);
+ SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ITFM_RAM, "enable Jamming detection M10 GPS RAM", 300);
+ delay(750);
+ SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ITFM_BBR, "enable Jamming detection M10 GPS BBR", 300);
+ delay(750);
+ // Here is where the init commands should go to do further M10 initialization.
+ SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_SBAS_RAM, "disable SBAS M10 GPS RAM", 300);
+ delay(750); // will cause a receiver restart so wait a bit
+ SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_DISABLE_SBAS_BBR, "disable SBAS M10 GPS BBR", 300);
+ delay(750); // will cause a receiver restart so wait a bit
+
+ // Done with initialization, Now enable wanted NMEA messages in BBR layer so they will survive a periodic sleep.
+ SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ENABLE_NMEA_BBR, "enable messages for M10 GPS BBR", 300);
+ delay(750);
+ // Next enable wanted NMEA messages in RAM layer
+ SEND_UBX_PACKET(0x06, 0x8A, _message_VALSET_ENABLE_NMEA_RAM, "enable messages for M10 GPS RAM", 500);
+ delay(750);
+
+ // 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.
+ msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE_10), _message_SAVE_10);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to save GNSS module configuration.");
+ } else {
+ LOG_INFO("GNSS module configuration saved!");
}
}
didSerialInit = true;
@@ -809,7 +712,7 @@ void GPS::setPowerState(GPSPowerState newState, uint32_t sleepTime)
// Update the stored GPSPowerstate, and create local copies
GPSPowerState oldState = powerState;
powerState = newState;
- LOG_INFO("GPS power state moving from %s to %s\n", getGPSPowerStateString(oldState), getGPSPowerStateString(newState));
+ LOG_INFO("GPS power state moving from %s to %s", getGPSPowerStateString(oldState), getGPSPowerStateString(newState));
#ifdef HELTEC_MESH_NODE_T114
if ((oldState == GPS_OFF || oldState == GPS_HARDSLEEP) && (newState != GPS_OFF && newState != GPS_HARDSLEEP)) {
@@ -879,7 +782,7 @@ void GPS::writePinEN(bool on)
// Write and log
enablePin->set(on);
#ifdef GPS_EXTRAVERBOSE
- LOG_DEBUG("Pin EN %s\n", val == HIGH ? "HIGH" : "LOW");
+ LOG_DEBUG("Pin EN %s", val == HIGH ? "HIGH" : "LOW");
#endif
}
@@ -901,7 +804,7 @@ void GPS::writePinStandby(bool standby)
pinMode(PIN_GPS_STANDBY, OUTPUT);
digitalWrite(PIN_GPS_STANDBY, val);
#ifdef GPS_EXTRAVERBOSE
- LOG_DEBUG("Pin STANDBY %s\n", val == HIGH ? "HIGH" : "LOW");
+ LOG_DEBUG("Pin STANDBY %s", val == HIGH ? "HIGH" : "LOW");
#endif
#endif
}
@@ -935,7 +838,7 @@ void GPS::setPowerPMU(bool on)
}
#ifdef GPS_EXTRAVERBOSE
- LOG_DEBUG("PMU %s\n", on ? "on" : "off");
+ LOG_DEBUG("PMU %s", on ? "on" : "off");
#endif
#endif
}
@@ -944,7 +847,7 @@ void GPS::setPowerPMU(bool on)
void GPS::setPowerUBLOX(bool on, uint32_t sleepMs)
{
// Abort: if not UBLOX hardware
- if (gnssModel != GNSS_MODEL_UBLOX)
+ if (!IS_ONE_OF(gnssModel, GNSS_MODEL_UBLOX6, GNSS_MODEL_UBLOX7, GNSS_MODEL_UBLOX8, GNSS_MODEL_UBLOX9, GNSS_MODEL_UBLOX10))
return;
// If waking
@@ -952,7 +855,7 @@ void GPS::setPowerUBLOX(bool on, uint32_t sleepMs)
gps->_serial_gps->write(0xFF);
clearBuffer(); // This often returns old data, so drop it
#ifdef GPS_EXTRAVERBOSE
- LOG_DEBUG("UBLOX: wake\n");
+ LOG_DEBUG("UBLOX: wake");
#endif
}
@@ -967,7 +870,7 @@ void GPS::setPowerUBLOX(bool on, uint32_t sleepMs)
}
// Determine hardware version
- if (strncmp(info.hwVersion, "000A0000", 8) != 0) {
+ if (gnssModel != GNSS_MODEL_UBLOX10) {
// Encode the sleep time in millis into the packet
for (int i = 0; i < 4; i++)
gps->_message_PMREQ[0 + i] = sleepMs >> (i * 8);
@@ -987,7 +890,7 @@ void GPS::setPowerUBLOX(bool on, uint32_t sleepMs)
gps->_serial_gps->write(gps->UBXscratch, msglen);
#ifdef GPS_EXTRAVERBOSE
- LOG_DEBUG("UBLOX: sleep for %dmS\n", sleepMs);
+ LOG_DEBUG("UBLOX: sleep for %dmS", sleepMs);
#endif
}
}
@@ -1016,36 +919,39 @@ void GPS::down()
uint32_t sleepTime = scheduling.msUntilNextSearch();
uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval);
- LOG_DEBUG("%us until next search\n", sleepTime / 1000);
+ LOG_DEBUG("%us until next search", sleepTime / 1000);
// If update interval less than 10 seconds, no attempt to sleep
- if (updateInterval <= 10 * 1000UL)
+ if (updateInterval <= 10 * 1000UL || sleepTime == 0)
setPowerState(GPS_IDLE);
else {
// Check whether the GPS hardware is capable of GPS_SOFTSLEEP
// If not, fallback to GPS_HARDSLEEP instead
bool softsleepSupported = false;
- if (gnssModel == GNSS_MODEL_UBLOX) // U-blox is supported via PMREQ
+ // U-blox is supported via PMREQ
+ if (IS_ONE_OF(gnssModel, GNSS_MODEL_UBLOX6, GNSS_MODEL_UBLOX7, GNSS_MODEL_UBLOX8, GNSS_MODEL_UBLOX9, GNSS_MODEL_UBLOX10))
softsleepSupported = true;
#ifdef PIN_GPS_STANDBY // L76B, L76K and clones have a standby pin
softsleepSupported = true;
#endif
- // How long does gps_update_interval need to be, for GPS_HARDSLEEP to become more efficient than GPS_SOFTSLEEP?
- // Heuristic equation. A compromise manually fitted to power observations from U-blox NEO-6M and M10050
- // https://www.desmos.com/calculator/6gvjghoumr
- // This is not particularly accurate, but probably an impromevement over a single, fixed threshold
- uint32_t hardsleepThreshold = (2750 * pow(predictedSearchDuration / 1000, 1.22));
- LOG_DEBUG("gps_update_interval >= %us needed to justify hardsleep\n", hardsleepThreshold / 1000);
-
- // If update interval too short: softsleep (if supported by hardware)
- if (softsleepSupported && updateInterval < hardsleepThreshold)
- setPowerState(GPS_SOFTSLEEP, sleepTime);
+ if (softsleepSupported) {
+ // How long does gps_update_interval need to be, for GPS_HARDSLEEP to become more efficient than GPS_SOFTSLEEP?
+ // Heuristic equation. A compromise manually fitted to power observations from U-blox NEO-6M and M10050
+ // https://www.desmos.com/calculator/6gvjghoumr
+ // This is not particularly accurate, but probably an impromevement over a single, fixed threshold
+ uint32_t hardsleepThreshold = (2750 * pow(predictedSearchDuration / 1000, 1.22));
+ LOG_DEBUG("gps_update_interval >= %us needed to justify hardsleep", hardsleepThreshold / 1000);
+ // If update interval too short: softsleep (if supported by hardware)
+ if (updateInterval < hardsleepThreshold) {
+ setPowerState(GPS_SOFTSLEEP, sleepTime);
+ return;
+ }
+ }
// If update interval long enough (or softsleep unsupported): hardsleep instead
- else
- setPowerState(GPS_HARDSLEEP, sleepTime);
+ setPowerState(GPS_HARDSLEEP, sleepTime);
}
}
@@ -1055,7 +961,7 @@ void GPS::publishUpdate()
shouldPublish = false;
// In debug logs, identify position by @timestamp:stage (stage 2 = publish)
- LOG_DEBUG("publishing pos@%x:2, hasVal=%d, Sats=%d, GPSlock=%d\n", p.timestamp, hasValidLocation, p.sats_in_view,
+ LOG_DEBUG("publishing pos@%x:2, hasVal=%d, Sats=%d, GPSlock=%d", p.timestamp, hasValidLocation, p.sats_in_view,
hasLock());
// Notify any status instances that are observing us
@@ -1071,7 +977,7 @@ int32_t GPS::runOnce()
{
if (!GPSInitFinished) {
if (!_serial_gps || config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
- LOG_INFO("GPS set to not-present. Skipping probe.\n");
+ LOG_INFO("GPS set to not-present. Skipping probe.");
return disable();
}
if (!setup())
@@ -1083,7 +989,7 @@ int32_t GPS::runOnce()
}
// ONCE we will factory reset the GPS for bug #327
if (!devicestate.did_gps_reset) {
- LOG_WARN("GPS FactoryReset requested\n");
+ LOG_WARN("GPS FactoryReset requested");
if (gps->factoryReset()) { // If we don't succeed try again next time
devicestate.did_gps_reset = true;
nodeDB->saveToDisk(SEGMENT_DEVICESTATE);
@@ -1101,10 +1007,12 @@ int32_t GPS::runOnce()
// if we have received valid NMEA claim we are connected
setConnected();
} else {
- if ((config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) && (gnssModel == GNSS_MODEL_UBLOX)) {
+ if ((config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) &&
+ IS_ONE_OF(gnssModel, GNSS_MODEL_UBLOX6, GNSS_MODEL_UBLOX7, GNSS_MODEL_UBLOX8, GNSS_MODEL_UBLOX9,
+ GNSS_MODEL_UBLOX10)) {
// reset the GPS on next bootup
if (devicestate.did_gps_reset && scheduling.elapsedSearchMs() > 60 * 1000UL && !hasFlow()) {
- LOG_DEBUG("GPS is not communicating, trying factory reset on next bootup.\n");
+ LOG_DEBUG("GPS is not communicating, trying factory reset on next bootup.");
devicestate.did_gps_reset = false;
nodeDB->saveToDisk(SEGMENT_DEVICESTATE);
return disable(); // Stop the GPS thread as it can do nothing useful until next reboot.
@@ -1114,7 +1022,7 @@ int32_t GPS::runOnce()
// At least one GPS has a bad habit of losing its mind from time to time
if (rebootsSeen > 2) {
rebootsSeen = 0;
- LOG_DEBUG("Would normally factoryReset()\n");
+ LOG_DEBUG("Would normally factoryReset()");
// gps->factoryReset();
}
@@ -1131,23 +1039,23 @@ int32_t GPS::runOnce()
bool gotLoc = lookForLocation();
if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
- LOG_DEBUG("hasValidLocation RISING EDGE\n");
+ LOG_DEBUG("hasValidLocation RISING EDGE");
hasValidLocation = true;
shouldPublish = true;
}
bool tooLong = scheduling.searchedTooLong();
if (tooLong)
- LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time.\n");
+ LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time.");
// Once we get a location we no longer desperately want an update
- // LOG_DEBUG("gotLoc %d, tooLong %d, gotTime %d\n", gotLoc, tooLong, gotTime);
+ // LOG_DEBUG("gotLoc %d, tooLong %d, gotTime %d", gotLoc, tooLong, gotTime);
if ((gotLoc && gotTime) || tooLong) {
if (tooLong) {
// we didn't get a location during this ack window, therefore declare loss of lock
if (hasValidLocation) {
- LOG_DEBUG("hasValidLocation FALLING EDGE\n");
+ LOG_DEBUG("hasValidLocation FALLING EDGE");
}
p = meshtastic_Position_init_default;
hasValidLocation = false;
@@ -1179,30 +1087,39 @@ void GPS::clearBuffer()
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
int GPS::prepareDeepSleep(void *unused)
{
- LOG_INFO("GPS deep sleep!\n");
+ LOG_INFO("GPS deep sleep!");
disable();
return 0;
}
+const char *PROBE_MESSAGE = "Trying %s (%s)...";
+const char *DETECTED_MESSAGE = "%s detected, using %s Module";
+
+#define PROBE_SIMPLE(CHIP, TOWRITE, RESPONSE, DRIVER, TIMEOUT, ...) \
+ LOG_DEBUG(PROBE_MESSAGE, TOWRITE, CHIP); \
+ clearBuffer(); \
+ _serial_gps->write(TOWRITE "\r\n"); \
+ if (getACK(RESPONSE, TIMEOUT) == GNSS_RESPONSE_OK) { \
+ LOG_INFO(DETECTED_MESSAGE, CHIP, #DRIVER); \
+ return DRIVER; \
+ }
+
GnssModel_t GPS::probe(int serialSpeed)
{
-#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)
+#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_STM32WL)
_serial_gps->end();
_serial_gps->begin(serialSpeed);
+#elif defined(ARCH_RP2040)
+ _serial_gps->end();
+ _serial_gps->setFIFOSize(256);
+ _serial_gps->begin(serialSpeed);
#else
if (_serial_gps->baudRate() != serialSpeed) {
- LOG_DEBUG("Setting Baud to %i\n", serialSpeed);
+ LOG_DEBUG("Setting Baud to %i", serialSpeed);
_serial_gps->updateBaudRate(serialSpeed);
}
#endif
-#ifdef GNSS_AIROHA
- return GNSS_MODEL_AG3335;
-#endif
-#ifdef GPS_DEBUG
- for (int i = 0; i < 20; i++) {
- getACK("$GP", 200);
- }
-#endif
+
memset(&info, 0, sizeof(struct uBloxGnssModelInfo));
uint8_t buffer[768] = {0};
delay(100);
@@ -1210,71 +1127,36 @@ GnssModel_t GPS::probe(int serialSpeed)
// Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
_serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
delay(20);
+ // Close NMEA sequences on Ublox
+ _serial_gps->write("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n");
+ _serial_gps->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n");
+ _serial_gps->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n");
+ delay(20);
- // get version information from Unicore UFirebirdII Series
- // Works for: UC6580, UM620, UM621, UM670A, UM680A, or UM681A
- _serial_gps->write("$PDTINFO\r\n");
- delay(750);
- if (getACK("UC6580", 500) == GNSS_RESPONSE_OK) {
- LOG_INFO("UC6580 detected, using UC6580 Module\n");
- return GNSS_MODEL_UC6580;
- }
-
- clearBuffer();
- _serial_gps->write("$PDTINFO\r\n");
- delay(750);
- if (getACK("UM600", 500) == GNSS_RESPONSE_OK) {
- LOG_INFO("UM600 detected, using UC6580 Module\n");
- return GNSS_MODEL_UC6580;
- }
-
- // Get version information for ATGM336H
- clearBuffer();
- _serial_gps->write("$PCAS06,1*1A\r\n");
- if (getACK("$GPTXT,01,01,02,HW=ATGM336H", 500) == GNSS_RESPONSE_OK) {
- LOG_INFO("ATGM336H GNSS init succeeded, using ATGM336H Module\n");
- return GNSS_MODEL_ATGM336H;
- }
-
+ // Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A
+ PROBE_SIMPLE("UC6580", "$PDTINFO", "UC6580", GNSS_MODEL_UC6580, 500);
+ PROBE_SIMPLE("UM600", "$PDTINFO", "UM600", GNSS_MODEL_UC6580, 500);
+ PROBE_SIMPLE("ATGM336H", "$PCAS06,1*1A", "$GPTXT,01,01,02,HW=ATGM336H", GNSS_MODEL_ATGM336H, 500);
/* ATGM332D series (-11(GPS), -21(BDS), -31(GPS+BDS), -51(GPS+GLONASS), -71-0(GPS+BDS+GLONASS))
based on AT6558 */
- clearBuffer();
- _serial_gps->write("$PCAS06,1*1A\r\n");
- if (getACK("$GPTXT,01,01,02,HW=ATGM332D", 500) == GNSS_RESPONSE_OK) {
- LOG_INFO("ATGM332D detected, using ATGM336H Module\n");
- return GNSS_MODEL_ATGM336H;
- }
+ PROBE_SIMPLE("ATGM332D", "$PCAS06,1*1A", "$GPTXT,01,01,02,HW=ATGM332D", GNSS_MODEL_ATGM336H, 500);
/* Airoha (Mediatek) AG3335A/M/S, A3352Q, Quectel L89 2.0, SimCom SIM65M */
- clearBuffer();
- _serial_gps->write("PAIR020*38\r\n");
- if (getACK("$PAIR020,AG3335", 500) == GNSS_RESPONSE_OK) {
- LOG_INFO("Aioha AG3335 detected, using AG3335 Module\n");
- return GNSS_MODEL_AG3335;
- }
- // Get version information for Airoha AG3335
- clearBuffer();
- _serial_gps->write("$PMTK605*31\r\n");
+ _serial_gps->write("$PAIR062,2,0*3C\r\n"); // GSA OFF to reduce volume
+ _serial_gps->write("$PAIR062,3,0*3D\r\n"); // GSV OFF to reduce volume
+ _serial_gps->write("$PAIR513*3D\r\n"); // save configuration
+ PROBE_SIMPLE("AG3335", "$PAIR021*39", "$PAIR021,AG3335", GNSS_MODEL_AG3335, 500);
+ PROBE_SIMPLE("AG3352", "$PAIR021*39", "$PAIR021,AG3352", GNSS_MODEL_AG3352, 500);
+ PROBE_SIMPLE("LC86", "$PQTMVERNO*58", "$PQTMVERNO,LC86", GNSS_MODEL_AG3352, 500);
- // Get version information
- clearBuffer();
- _serial_gps->write("$PCAS06,0*1B\r\n");
- if (getACK("$GPTXT,01,01,02,SW=", 500) == GNSS_RESPONSE_OK) {
- LOG_INFO("L76K GNSS init succeeded, using L76K GNSS Module\n");
- return GNSS_MODEL_MTK;
- }
+ PROBE_SIMPLE("L76K", "$PCAS06,0*1B", "$GPTXT,01,01,02,SW=", GNSS_MODEL_MTK, 500);
// Close all NMEA sentences, valid for L76B MTK platform (Waveshare Pico GPS)
_serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
delay(20);
- // Get version information
- clearBuffer();
- _serial_gps->write("$PMTK605*31\r\n");
- if (getACK("Quectel-L76B", 500) == GNSS_RESPONSE_OK) {
- LOG_INFO("L76B GNSS init succeeded, using L76B GNSS Module\n");
- return GNSS_MODEL_MTK_L76B;
- }
+ PROBE_SIMPLE("L76B", "$PMTK605*31", "Quectel-L76B", GNSS_MODEL_MTK_L76B, 500);
+ PROBE_SIMPLE("PA1616S", "$PMTK605*31", "1616S", GNSS_MODEL_MTK_PA1616S, 500);
uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
UBXChecksum(cfg_rate, sizeof(cfg_rate));
@@ -1283,31 +1165,10 @@ GnssModel_t GPS::probe(int serialSpeed)
// Check that the returned response class and message ID are correct
GPS_RESPONSE response = getACK(0x06, 0x08, 750);
if (response == GNSS_RESPONSE_NONE) {
- LOG_WARN("Failed to find UBlox & MTK GNSS Module using baudrate %d\n", serialSpeed);
+ LOG_WARN("Failed to find GNSS Module (baudrate %d)", serialSpeed);
return GNSS_MODEL_UNKNOWN;
} else if (response == GNSS_RESPONSE_FRAME_ERRORS) {
- LOG_INFO("UBlox Frame Errors using baudrate %d\n", serialSpeed);
- } else if (response == GNSS_RESPONSE_OK) {
- LOG_INFO("Found a UBlox Module using baudrate %d\n", serialSpeed);
- }
-
- // tips: NMEA Only should not be set here, otherwise initializing Ublox gnss module again after
- // setting will not output command messages in UART1, resulting in unrecognized module information
- if (serialSpeed != 9600) {
- // Set the UART port to 9600
- uint8_t _message_prt[] = {0xB5, 0x62, 0x06, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0xD0, 0x08, 0x00, 0x00,
- 0x80, 0x25, 0x00, 0x00, 0x07, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
- UBXChecksum(_message_prt, sizeof(_message_prt));
- _serial_gps->write(_message_prt, sizeof(_message_prt));
- delay(500);
- serialSpeed = 9600;
-#if defined(ARCH_NRF52) || defined(ARCH_PORTDUINO) || defined(ARCH_RP2040) || defined(ARCH_STM32WL)
- _serial_gps->end();
- _serial_gps->begin(serialSpeed);
-#else
- _serial_gps->updateBaudRate(serialSpeed);
-#endif
- delay(200);
+ LOG_INFO("UBlox Frame Errors (baudrate %d)", serialSpeed);
}
memset(buffer, 0, sizeof(buffer));
@@ -1324,7 +1185,7 @@ GnssModel_t GPS::probe(int serialSpeed)
uint16_t len = getACK(buffer, sizeof(buffer), 0x0A, 0x04, 1200);
if (len) {
- // LOG_DEBUG("monver reply size = %d\n", len);
+ // LOG_DEBUG("monver reply size = %d", len);
uint16_t position = 0;
for (int i = 0; i < 30; i++) {
info.swVersion[i] = buffer[position];
@@ -1345,12 +1206,12 @@ GnssModel_t GPS::probe(int serialSpeed)
break;
}
- LOG_DEBUG("Module Info : \n");
- LOG_DEBUG("Soft version: %s\n", info.swVersion);
- LOG_DEBUG("Hard version: %s\n", info.hwVersion);
- LOG_DEBUG("Extensions:%d\n", info.extensionNo);
+ LOG_DEBUG("Module Info : ");
+ LOG_DEBUG("Soft version: %s", info.swVersion);
+ LOG_DEBUG("Hard version: %s", info.hwVersion);
+ LOG_DEBUG("Extensions:%d", info.extensionNo);
for (int i = 0; i < info.extensionNo; i++) {
- LOG_DEBUG(" %s\n", info.extension[i]);
+ LOG_DEBUG(" %s", info.extension[i]);
}
memset(buffer, 0, sizeof(buffer));
@@ -1359,28 +1220,38 @@ GnssModel_t GPS::probe(int serialSpeed)
for (int i = 0; i < info.extensionNo; ++i) {
if (!strncmp(info.extension[i], "MOD=", 4)) {
strncpy((char *)buffer, &(info.extension[i][4]), sizeof(buffer));
- // LOG_DEBUG("GetModel:%s\n", (char *)buffer);
- if (strlen((char *)buffer)) {
- LOG_INFO("UBlox GNSS probe succeeded, using UBlox %s GNSS Module\n", (char *)buffer);
- } else {
- LOG_INFO("UBlox GNSS probe succeeded, using UBlox GNSS Module\n");
- }
} else if (!strncmp(info.extension[i], "PROTVER", 7)) {
char *ptr = nullptr;
memset(buffer, 0, sizeof(buffer));
strncpy((char *)buffer, &(info.extension[i][8]), sizeof(buffer));
- LOG_DEBUG("Protocol Version:%s\n", (char *)buffer);
+ LOG_DEBUG("Protocol Version:%s", (char *)buffer);
if (strlen((char *)buffer)) {
uBloxProtocolVersion = strtoul((char *)buffer, &ptr, 10);
- LOG_DEBUG("ProtVer=%d\n", uBloxProtocolVersion);
+ LOG_DEBUG("ProtVer=%d", uBloxProtocolVersion);
} else {
uBloxProtocolVersion = 0;
}
}
}
+ if (strncmp(info.hwVersion, "00040007", 8) == 0) {
+ LOG_INFO(DETECTED_MESSAGE, "U-blox 6", "6");
+ return GNSS_MODEL_UBLOX6;
+ } else if (strncmp(info.hwVersion, "00070000", 8) == 0) {
+ LOG_INFO(DETECTED_MESSAGE, "U-blox 7", "7");
+ return GNSS_MODEL_UBLOX7;
+ } else if (strncmp(info.hwVersion, "00080000", 8) == 0) {
+ LOG_INFO(DETECTED_MESSAGE, "U-blox 8", "8");
+ return GNSS_MODEL_UBLOX8;
+ } else if (strncmp(info.hwVersion, "00190000", 8) == 0) {
+ LOG_INFO(DETECTED_MESSAGE, "U-blox 9", "9");
+ return GNSS_MODEL_UBLOX9;
+ } else if (strncmp(info.hwVersion, "000A0000", 8) == 0) {
+ LOG_INFO(DETECTED_MESSAGE, "U-blox 10", "10");
+ return GNSS_MODEL_UBLOX10;
+ }
}
-
- return GNSS_MODEL_UBLOX;
+ LOG_WARN("Failed to find GNSS Module (baudrate %d)", serialSpeed);
+ return GNSS_MODEL_UNKNOWN;
}
GPS *GPS::createGps()
@@ -1440,7 +1311,7 @@ GPS *GPS::createGps()
// see NMEAGPS.h
gsafixtype.begin(reader, NMEA_MSG_GXGSA, 2);
gsapdop.begin(reader, NMEA_MSG_GXGSA, 15);
- LOG_DEBUG("Using " NMEA_MSG_GXGSA " for 3DFIX and PDOP\n");
+ LOG_DEBUG("Using " NMEA_MSG_GXGSA " for 3DFIX and PDOP");
#endif
// Make sure the GPS is awake before performing any init.
@@ -1461,9 +1332,12 @@ GPS *GPS::createGps()
// ESP32 has a special set of parameters vs other arduino ports
#if defined(ARCH_ESP32)
- LOG_DEBUG("Using GPIO%d for GPS RX\n", new_gps->rx_gpio);
- LOG_DEBUG("Using GPIO%d for GPS TX\n", new_gps->tx_gpio);
+ LOG_DEBUG("Using GPIO%d for GPS RX", new_gps->rx_gpio);
+ LOG_DEBUG("Using GPIO%d for GPS TX", new_gps->tx_gpio);
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio);
+#elif defined(ARCH_RP2040)
+ _serial_gps->setFIFOSize(256);
+ _serial_gps->begin(GPS_BAUDRATE);
#else
_serial_gps->begin(GPS_BAUDRATE);
#endif
@@ -1495,21 +1369,21 @@ bool GPS::factoryReset()
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x1C, 0xA2};
_serial_gps->write(_message_reset1, sizeof(_message_reset1));
if (getACK(0x05, 0x01, 10000)) {
- LOG_INFO("Get ack success!\n");
+ LOG_INFO(ACK_SUCCESS_MESSAGE);
}
delay(100);
byte _message_reset2[] = {0xB5, 0x62, 0x06, 0x09, 0x0D, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x1B, 0xA1};
_serial_gps->write(_message_reset2, sizeof(_message_reset2));
if (getACK(0x05, 0x01, 10000)) {
- LOG_INFO("Get ack success!\n");
+ LOG_INFO(ACK_SUCCESS_MESSAGE);
}
delay(100);
byte _message_reset3[] = {0xB5, 0x62, 0x06, 0x09, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0x1D, 0xB3};
_serial_gps->write(_message_reset3, sizeof(_message_reset3));
if (getACK(0x05, 0x01, 10000)) {
- LOG_INFO("Get ack success!\n");
+ LOG_INFO(ACK_SUCCESS_MESSAGE);
}
// Reset device ram to COLDSTART state
// byte _message_CFG_RST_COLDSTART[] = {0xB5, 0x62, 0x06, 0x04, 0x04, 0x00, 0xFF, 0xB9, 0x00, 0x00, 0xC6, 0x8B};
@@ -1517,11 +1391,11 @@ bool GPS::factoryReset()
// delay(1000);
} else if (gnssModel == GNSS_MODEL_MTK) {
// send the CAS10 to perform a factory restart of the device (and other device that support PCAS statements)
- LOG_INFO("GNSS Factory Reset via PCAS10,3\n");
+ LOG_INFO("GNSS Factory Reset via PCAS10,3");
_serial_gps->write("$PCAS10,3*1F\r\n");
delay(100);
} else if (gnssModel == GNSS_MODEL_ATGM336H) {
- LOG_INFO("Factory Reset via CAS-CFG-RST\n");
+ LOG_INFO("Factory Reset via CAS-CFG-RST");
uint8_t msglen = makeCASPacket(0x06, 0x02, sizeof(_message_CAS_CFG_RST_FACTORY), _message_CAS_CFG_RST_FACTORY);
_serial_gps->write(UBXscratch, msglen);
delay(100);
@@ -1551,16 +1425,15 @@ bool GPS::lookForTime()
#ifdef GNSS_AIROHA
uint8_t fix = reader.fixQuality();
- uint32_t now = millis();
if (fix > 0) {
if (lastFixStartMsec > 0) {
- if ((now - lastFixStartMsec) < GPS_FIX_HOLD_TIME) {
+ if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) {
return false;
} else {
clearBuffer();
}
} else {
- lastFixStartMsec = now;
+ lastFixStartMsec = millis();
return false;
}
} else {
@@ -1575,7 +1448,7 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
*/
struct tm t;
- t.tm_sec = ti.second();
+ t.tm_sec = ti.second() + round(ti.age() / 1000);
t.tm_min = ti.minute();
t.tm_hour = ti.hour();
t.tm_mday = d.day();
@@ -1583,8 +1456,8 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s
t.tm_year = d.year() - 1900;
t.tm_isdst = false;
if (t.tm_mon > -1) {
- LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d\n", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min,
- t.tm_sec);
+ LOG_DEBUG("NMEA GPS time %02d-%02d-%02d %02d:%02d:%02d age %d", d.year(), d.month(), t.tm_mday, t.tm_hour, t.tm_min,
+ t.tm_sec, ti.age());
perhapsSetRTC(RTCQualityGPS, t);
return true;
} else
@@ -1604,16 +1477,15 @@ bool GPS::lookForLocation()
#ifdef GNSS_AIROHA
if ((config.position.gps_update_interval * 1000) >= (GPS_FIX_HOLD_TIME * 2)) {
uint8_t fix = reader.fixQuality();
- uint32_t now = millis();
if (fix > 0) {
if (lastFixStartMsec > 0) {
- if ((now - lastFixStartMsec) < GPS_FIX_HOLD_TIME) {
+ if (Throttle::isWithinTimespanMs(lastFixStartMsec, GPS_FIX_HOLD_TIME)) {
return false;
} else {
clearBuffer();
}
} else {
- lastFixStartMsec = now;
+ lastFixStartMsec = millis();
return false;
}
} else {
@@ -1628,7 +1500,7 @@ bool GPS::lookForLocation()
#ifndef TINYGPS_OPTION_NO_STATISTICS
if (reader.failedChecksum() > lastChecksumFailCount) {
- LOG_WARN("%u new GPS checksum failures, for a total of %u.\n", reader.failedChecksum() - lastChecksumFailCount,
+ LOG_WARN("%u new GPS checksum failures, for a total of %u.", reader.failedChecksum() - lastChecksumFailCount,
reader.failedChecksum());
lastChecksumFailCount = reader.failedChecksum();
}
@@ -1636,7 +1508,7 @@ bool GPS::lookForLocation()
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
fixType = atoi(gsafixtype.value()); // will set to zero if no data
- // LOG_DEBUG("FIX QUAL=%d, TYPE=%d\n", fixQual, fixType);
+ // LOG_DEBUG("FIX QUAL=%d, TYPE=%d", fixQual, fixType);
#endif
// check if GPS has an acceptable lock
@@ -1644,7 +1516,7 @@ bool GPS::lookForLocation()
return false;
#ifdef GPS_EXTRAVERBOSE
- LOG_DEBUG("AGE: LOC=%d FIX=%d DATE=%d TIME=%d\n", reader.location.age(),
+ LOG_DEBUG("AGE: LOC=%d FIX=%d DATE=%d TIME=%d", reader.location.age(),
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
gsafixtype.age(),
#else
@@ -1665,7 +1537,7 @@ bool GPS::lookForLocation()
(gsafixtype.age() < GPS_SOL_EXPIRY_MS) &&
#endif
(reader.time.age() < GPS_SOL_EXPIRY_MS) && (reader.date.age() < GPS_SOL_EXPIRY_MS))) {
- LOG_WARN("SOME data is TOO OLD: LOC %u, TIME %u, DATE %u\n", reader.location.age(), reader.time.age(), reader.date.age());
+ LOG_WARN("SOME data is TOO OLD: LOC %u, TIME %u, DATE %u", reader.location.age(), reader.time.age(), reader.date.age());
return false;
}
@@ -1675,13 +1547,13 @@ bool GPS::lookForLocation()
// Bail out EARLY to avoid overwriting previous good data (like #857)
if (toDegInt(loc.lat) > 900000000) {
#ifdef GPS_EXTRAVERBOSE
- LOG_DEBUG("Bail out EARLY on LAT %i\n", toDegInt(loc.lat));
+ LOG_DEBUG("Bail out EARLY on LAT %i", toDegInt(loc.lat));
#endif
return false;
}
if (toDegInt(loc.lng) > 1800000000) {
#ifdef GPS_EXTRAVERBOSE
- LOG_DEBUG("Bail out EARLY on LNG %i\n", toDegInt(loc.lng));
+ LOG_DEBUG("Bail out EARLY on LNG %i", toDegInt(loc.lng));
#endif
return false;
}
@@ -1692,7 +1564,7 @@ bool GPS::lookForLocation()
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
p.HDOP = reader.hdop.value();
p.PDOP = TinyGPSPlus::parseDecimal(gsapdop.value());
- // LOG_DEBUG("PDOP=%d, HDOP=%d\n", p.PDOP, p.HDOP);
+ // LOG_DEBUG("PDOP=%d, HDOP=%d", p.PDOP, p.HDOP);
#else
// FIXME! naive PDOP emulation (assumes VDOP==HDOP)
// correct formula is PDOP = SQRT(HDOP^2 + VDOP^2)
@@ -1702,7 +1574,7 @@ bool GPS::lookForLocation()
// Discard incomplete or erroneous readings
if (reader.hdop.value() == 0) {
- LOG_WARN("BOGUS hdop.value() REJECTED: %d\n", reader.hdop.value());
+ LOG_WARN("BOGUS hdop.value() REJECTED: %d", reader.hdop.value());
return false;
}
@@ -1739,7 +1611,7 @@ bool GPS::lookForLocation()
p.ground_track =
reader.course.value() * 1e3; // Scale the heading (in degrees * 10^-2) to match the expected degrees * 10^-5
} else {
- LOG_WARN("BOGUS course.value() REJECTED: %d\n", reader.course.value());
+ LOG_WARN("BOGUS course.value() REJECTED: %d", reader.course.value());
}
}
@@ -1773,24 +1645,27 @@ bool GPS::whileActive()
{
unsigned int charsInBuf = 0;
bool isValid = false;
+#ifdef GPS_DEBUG
+ std::string debugmsg = "";
+#endif
if (powerState != GPS_ACTIVE) {
clearBuffer();
return false;
}
#ifdef SERIAL_BUFFER_SIZE
if (_serial_gps->available() >= SERIAL_BUFFER_SIZE - 1) {
- LOG_WARN("GPS Buffer full with %u bytes waiting. Flushing to avoid corruption.\n", _serial_gps->available());
+ LOG_WARN("GPS Buffer full with %u bytes waiting. Flushing to avoid corruption.", _serial_gps->available());
clearBuffer();
}
#endif
// if (_serial_gps->available() > 0)
- // LOG_DEBUG("GPS Bytes Waiting: %u\n", _serial_gps->available());
+ // LOG_DEBUG("GPS Bytes Waiting: %u", _serial_gps->available());
// First consume any chars that have piled up at the receiver
while (_serial_gps->available() > 0) {
int c = _serial_gps->read();
UBXscratch[charsInBuf] = c;
#ifdef GPS_DEBUG
- LOG_DEBUG("%c", c);
+ debugmsg += vformat("%c", (c >= 32 && c <= 126) ? c : '.');
#endif
isValid |= reader.encode(c);
if (charsInBuf > sizeof(UBXscratch) - 10 || c == '\r') {
@@ -1802,6 +1677,9 @@ bool GPS::whileActive()
charsInBuf++;
}
}
+#ifdef GPS_DEBUG
+ LOG_DEBUG(debugmsg.c_str());
+#endif
return isValid;
}
void GPS::enable()
@@ -1829,17 +1707,19 @@ void GPS::toggleGpsMode()
{
if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED;
- LOG_INFO("User toggled GpsMode. Now DISABLED.\n");
+ LOG_INFO("User toggled GpsMode. Now DISABLED.");
+ playGPSDisableBeep();
#ifdef GNSS_AIROHA
if (powerState == GPS_ACTIVE) {
- LOG_DEBUG("User power Off GPS\n");
+ LOG_DEBUG("User power Off GPS");
digitalWrite(PIN_GPS_EN, LOW);
}
#endif
disable();
} else if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) {
config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED;
- LOG_INFO("User toggled GpsMode. Now ENABLED\n");
+ LOG_INFO("User toggled GpsMode. Now ENABLED");
+ playGPSEnableBeep();
enable();
}
}
diff --git a/src/gps/GPS.h b/src/gps/GPS.h
index c0e9fb8b6..240d087f2 100644
--- a/src/gps/GPS.h
+++ b/src/gps/GPS.h
@@ -26,11 +26,17 @@ struct uBloxGnssModelInfo {
typedef enum {
GNSS_MODEL_ATGM336H,
GNSS_MODEL_MTK,
- GNSS_MODEL_UBLOX,
+ GNSS_MODEL_UBLOX6,
+ GNSS_MODEL_UBLOX7,
+ GNSS_MODEL_UBLOX8,
+ GNSS_MODEL_UBLOX9,
+ GNSS_MODEL_UBLOX10,
GNSS_MODEL_UC6580,
GNSS_MODEL_UNKNOWN,
GNSS_MODEL_MTK_L76B,
- GNSS_MODEL_AG3335
+ GNSS_MODEL_MTK_PA1616S,
+ GNSS_MODEL_AG3335,
+ GNSS_MODEL_AG3352
} GnssModel_t;
typedef enum {
@@ -70,7 +76,7 @@ class GPS : private concurrency::OSThread
uint8_t fixType = 0; // fix type from GPGSA
#endif
private:
- const int serialSpeeds[6] = {9600, 4800, 38400, 57600, 115200, 9600};
+ const int serialSpeeds[6] = {9600, 115200, 38400, 4800, 57600, GPS_BAUDRATE};
uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastFixStartMsec = 0;
uint32_t rx_gpio = 0;
uint32_t tx_gpio = 0;
@@ -101,7 +107,7 @@ class GPS : private concurrency::OSThread
public:
/** If !NULL we will use this serial port to construct our GPS */
-#if defined(RPI_PICO_WAVESHARE)
+#if defined(ARCH_RP2040)
static SerialUART *_serial_gps;
#else
static HardwareSerial *_serial_gps;
@@ -129,6 +135,7 @@ class GPS : private concurrency::OSThread
static const uint8_t _message_GGA[];
static const uint8_t _message_PMS[];
static const uint8_t _message_SAVE[];
+ static const uint8_t _message_SAVE_10[];
// VALSET Commands for M10
static const uint8_t _message_VALSET_PM[];
@@ -150,6 +157,8 @@ class GPS : private concurrency::OSThread
static const uint8_t _message_CAS_CFG_NAVX_CONF[];
static const uint8_t _message_CAS_CFG_RATE_1HZ[];
+ const char *ACK_SUCCESS_MESSAGE = "Get ack success!";
+
meshtastic_Position p = meshtastic_Position_init_default;
/** This is normally bound to config.position.gps_en_gpio but some rare boards (like heltec tracker) need more advanced
@@ -297,7 +306,6 @@ class GPS : private concurrency::OSThread
virtual int32_t runOnce() override;
// Get GNSS model
- String getNMEA();
GnssModel_t probe(int serialSpeed);
// delay counter to allow more sats before fixed position stops GPS thread
diff --git a/src/gps/GPSUpdateScheduling.cpp b/src/gps/GPSUpdateScheduling.cpp
index 949ef6039..abcf6b196 100644
--- a/src/gps/GPSUpdateScheduling.cpp
+++ b/src/gps/GPSUpdateScheduling.cpp
@@ -13,7 +13,7 @@ void GPSUpdateScheduling::informSearching()
void GPSUpdateScheduling::informGotLock()
{
searchEndedMs = millis();
- LOG_DEBUG("Took %us to get lock\n", (searchEndedMs - searchStartedMs) / 1000);
+ LOG_DEBUG("Took %us to get lock", (searchEndedMs - searchStartedMs) / 1000);
updateLockTimePrediction();
}
@@ -70,9 +70,9 @@ bool GPSUpdateScheduling::isUpdateDue()
// Have we been searching for a GPS position for too long?
bool GPSUpdateScheduling::searchedTooLong()
{
- uint32_t maxSearchMs =
- Default::getConfiguredOrDefaultMs(config.position.position_broadcast_secs, default_broadcast_interval_secs);
-
+ uint32_t minimumOrConfiguredSecs =
+ Default::getConfiguredOrMinimumValue(config.position.position_broadcast_secs, default_broadcast_interval_secs);
+ uint32_t maxSearchMs = Default::getConfiguredOrDefaultMs(minimumOrConfiguredSecs, default_broadcast_interval_secs);
// If broadcast interval set to max, no such thing as "too long"
if (maxSearchMs == UINT32_MAX)
return false;
@@ -108,11 +108,11 @@ void GPSUpdateScheduling::updateLockTimePrediction()
searchCount++; // Only tracked so we can diregard initial lock-times
- LOG_DEBUG("Predicting %us to get next lock\n", predictedMsToGetLock / 1000);
+ LOG_DEBUG("Predicting %us to get next lock", predictedMsToGetLock / 1000);
}
// How long do we expect to spend searching for a lock?
uint32_t GPSUpdateScheduling::predictedSearchDurationMs()
{
return GPSUpdateScheduling::predictedMsToGetLock;
-}
\ No newline at end of file
+}
diff --git a/src/gps/NMEAWPL.cpp b/src/gps/NMEAWPL.cpp
index 71943b76c..f528c4607 100644
--- a/src/gps/NMEAWPL.cpp
+++ b/src/gps/NMEAWPL.cpp
@@ -75,10 +75,13 @@ uint32_t printWPL(char *buf, size_t bufsz, const meshtastic_Position &pos, const
uint32_t printGGA(char *buf, size_t bufsz, const meshtastic_Position &pos)
{
GeoCoord geoCoord(pos.latitude_i, pos.longitude_i, pos.altitude);
- tm *t = gmtime((time_t *)&pos.timestamp);
+ time_t timestamp = pos.timestamp;
+
+ tm *t = gmtime(×tamp);
if (getRTCQuality() > 0) { // use the device clock if we got time from somewhere. If not, use the GPS timestamp.
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice);
- t = gmtime((time_t *)&rtc_sec);
+ timestamp = rtc_sec;
+ t = gmtime(×tamp);
}
uint32_t len = snprintf(
diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp
index 070038672..8130d7668 100644
--- a/src/gps/RTC.cpp
+++ b/src/gps/RTC.cpp
@@ -2,6 +2,7 @@
#include "configuration.h"
#include "detect/ScanI2C.h"
#include "main.h"
+#include
#include
#include
@@ -29,7 +30,7 @@ void readFromRTC()
if (rtc_found.address == RV3028_RTC) {
uint32_t now = millis();
Melopero_RV3028 rtc;
-#ifdef I2C_SDA1
+#if WIRE_INTERFACES_COUNT == 2
rtc.initI2C(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire);
#else
rtc.initI2C();
@@ -43,7 +44,10 @@ void readFromRTC()
t.tm_sec = rtc.getSecond();
tv.tv_sec = gm_mktime(&t);
tv.tv_usec = 0;
- LOG_DEBUG("Read RTC time from RV3028 as %ld\n", tv.tv_sec);
+
+ uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
+ LOG_DEBUG("Read RTC time from RV3028 getTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1,
+ t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
if (currentQuality == RTCQualityNone) {
@@ -55,7 +59,7 @@ void readFromRTC()
uint32_t now = millis();
PCF8563_Class rtc;
-#ifdef I2C_SDA1
+#if WIRE_INTERFACES_COUNT == 2
rtc.begin(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire);
#else
rtc.begin();
@@ -71,7 +75,10 @@ void readFromRTC()
t.tm_sec = tc.second;
tv.tv_sec = gm_mktime(&t);
tv.tv_usec = 0;
- LOG_DEBUG("Read RTC time from PCF8563 as %ld\n", tv.tv_sec);
+
+ uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
+ LOG_DEBUG("Read RTC time from PCF8563 getDateTime as %02d-%02d-%02d %02d:%02d:%02d (%ld)", t.tm_year + 1900, t.tm_mon + 1,
+ t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec, printableEpoch);
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
if (currentQuality == RTCQualityNone) {
@@ -81,7 +88,8 @@ void readFromRTC()
#else
if (!gettimeofday(&tv, NULL)) {
uint32_t now = millis();
- LOG_DEBUG("Read RTC time as %ld\n", tv.tv_sec);
+ uint32_t printableEpoch = tv.tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
+ LOG_DEBUG("Read RTC time as %ld", printableEpoch);
timeStartMsec = now;
zeroOffsetSecs = tv.tv_sec;
}
@@ -101,22 +109,32 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
{
static uint32_t lastSetMsec = 0;
uint32_t now = millis();
+ uint32_t printableEpoch = tv->tv_sec; // Print lib only supports 32 bit but time_t can be 64 bit on some platforms
+#ifdef BUILD_EPOCH
+ if (tv->tv_sec < BUILD_EPOCH) {
+ LOG_WARN("Ignoring time (%ld) before build epoch (%ld)!", printableEpoch, BUILD_EPOCH);
+ return false;
+ }
+#endif
bool shouldSet;
if (forceUpdate) {
shouldSet = true;
- LOG_DEBUG("Overriding current RTC quality (%s) with incoming time of RTC quality of %s\n", RtcName(currentQuality),
+ LOG_DEBUG("Overriding current RTC quality (%s) with incoming time of RTC quality of %s", 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)) {
- // Every 12 hrs we will slam in a new GPS or Phone GPS / NTP time, to correct for local RTC clock drift
+ LOG_DEBUG("Upgrading time to quality %s", RtcName(q));
+ } else if (q == RTCQualityGPS) {
shouldSet = true;
- LOG_DEBUG("Reapplying external time to correct clock drift %ld secs\n", tv->tv_sec);
+ LOG_DEBUG("Reapplying GPS time: %ld secs", printableEpoch);
+ } else if (q == RTCQualityNTP && !Throttle::isWithinTimespanMs(lastSetMsec, (12 * 60 * 60 * 1000UL))) {
+ // Every 12 hrs we will slam in a new NTP or Phone GPS / NTP time, to correct for local RTC clock drift
+ shouldSet = true;
+ LOG_DEBUG("Reapplying external time to correct clock drift %ld secs", printableEpoch);
} else {
shouldSet = false;
- LOG_DEBUG("Current RTC quality: %s. Ignoring time of RTC quality of %s\n", RtcName(currentQuality), RtcName(q));
+ LOG_DEBUG("Current RTC quality: %s. Ignoring time of RTC quality of %s", RtcName(currentQuality), RtcName(q));
}
if (shouldSet) {
@@ -133,29 +151,29 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
#ifdef RV3028_RTC
if (rtc_found.address == RV3028_RTC) {
Melopero_RV3028 rtc;
-#ifdef I2C_SDA1
+#if WIRE_INTERFACES_COUNT == 2
rtc.initI2C(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire);
#else
rtc.initI2C();
#endif
tm *t = gmtime(&tv->tv_sec);
rtc.setTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_wday, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
- LOG_DEBUG("RV3028_RTC setTime %02d-%02d-%02d %02d:%02d:%02d %ld\n", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
- t->tm_hour, t->tm_min, t->tm_sec, tv->tv_sec);
+ LOG_DEBUG("RV3028_RTC setTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
+ t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
}
#elif defined(PCF8563_RTC)
if (rtc_found.address == PCF8563_RTC) {
PCF8563_Class rtc;
-#ifdef I2C_SDA1
+#if WIRE_INTERFACES_COUNT == 2
rtc.begin(rtc_found.port == ScanI2C::I2CPort::WIRE1 ? Wire1 : Wire);
#else
rtc.begin();
#endif
tm *t = gmtime(&tv->tv_sec);
rtc.setDateTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
- LOG_DEBUG("PCF8563_RTC setDateTime %02d-%02d-%02d %02d:%02d:%02d %ld\n", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
- t->tm_hour, t->tm_min, t->tm_sec, tv->tv_sec);
+ LOG_DEBUG("PCF8563_RTC setDateTime %02d-%02d-%02d %02d:%02d:%02d (%ld)", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
+ t->tm_hour, t->tm_min, t->tm_sec, printableEpoch);
}
#elif defined(ARCH_ESP32)
settimeofday(tv, NULL);
@@ -210,9 +228,9 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t)
tv.tv_sec = res;
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
- // LOG_DEBUG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
+ // LOG_DEBUG("Got time from GPS month=%d, year=%d, unixtime=%ld", t.tm_mon, t.tm_year, tv.tv_sec);
if (t.tm_year < 0 || t.tm_year >= 300) {
- // LOG_DEBUG("Ignoring invalid GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec);
+ // LOG_DEBUG("Ignoring invalid GPS month=%d, year=%d, unixtime=%ld", t.tm_mon, t.tm_year, tv.tv_sec);
return false;
} else {
return perhapsSetRTC(q, &tv);
@@ -272,4 +290,4 @@ time_t gm_mktime(struct tm *tm)
#else
return mktime(tm);
#endif
-}
\ No newline at end of file
+}
diff --git a/src/gps/ubx.h b/src/gps/ubx.h
index 0852c331d..68cca00a3 100644
--- a/src/gps/ubx.h
+++ b/src/gps/ubx.h
@@ -1,3 +1,12 @@
+const char *failMessage = "Unable to %s";
+
+#define SEND_UBX_PACKET(TYPE, ID, DATA, ERRMSG, TIMEOUT) \
+ msglen = makeUBXPacket(TYPE, ID, sizeof(DATA), DATA); \
+ _serial_gps->write(UBXscratch, msglen); \
+ if (getACK(TYPE, ID, TIMEOUT) != GNSS_RESPONSE_OK) { \
+ LOG_WARN(failMessage, #ERRMSG); \
+ }
+
// Power Management
uint8_t GPS::_message_PMREQ[] PROGMEM = {
@@ -316,6 +325,13 @@ const uint8_t GPS::_message_SAVE[] = {
0x17 // deviceMask: BBR, Flash, EEPROM, and SPI Flash
};
+const uint8_t GPS::_message_SAVE_10[] = {
+ 0x00, 0x00, 0x00, 0x00, // clearMask: no sections cleared
+ 0xFF, 0xFF, 0x00, 0x00, // saveMask: save all sections
+ 0x00, 0x00, 0x00, 0x00, // loadMask: no sections loaded
+ 0x01 // deviceMask: only save to BBR
+};
+
// 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.
@@ -335,36 +351,36 @@ const uint8_t GPS::_message_SAVE[] = {
// has details on low-power modes
/*
-CFG-PM2 has been replaced by many CFG-PM commands
-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
+OPERATEMODE E1 2 (0 | 1 | 2)
+POSUPDATEPERIOD U4 5
+ACQPERIOD U4 10
+GRIDOFFSET U4 0
+ONTIME U2 1
+MINACQTIME U1 0
+MAXACQTIME U1 0
+DONOTENTEROFF L 1
+WAITTIMEFIX L 1
+UPDATEEPH L 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
// Ram layer config message:
-// 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
+// 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
// BBR layer config message:
-// 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
+// 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[] = {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};
+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};
/*
CFG-ITFM replaced by 5 valset messages which can be combined into one for RAM and one for BBR
diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp
index c4cb10f82..1d3b2f605 100644
--- a/src/graphics/EInkDisplay2.cpp
+++ b/src/graphics/EInkDisplay2.cpp
@@ -79,13 +79,13 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
}
// Trigger the refresh in GxEPD2
- LOG_DEBUG("Updating E-Paper... ");
+ LOG_DEBUG("Updating E-Paper");
adafruitDisplay->nextPage();
// End the update process
endUpdate();
- LOG_DEBUG("done\n");
+ LOG_DEBUG("done");
return true;
}
@@ -123,7 +123,7 @@ void EInkDisplay::setDetected(uint8_t detected)
// Connect to the display - variant specific
bool EInkDisplay::connect()
{
- LOG_INFO("Doing EInk init\n");
+ LOG_INFO("Doing EInk init");
#ifdef PIN_EINK_EN
// backlight power, HIGH is backlight on, LOW is off
diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp
index c31941a60..ac5755bc1 100644
--- a/src/graphics/EInkDynamicDisplay.cpp
+++ b/src/graphics/EInkDynamicDisplay.cpp
@@ -1,3 +1,4 @@
+#include "Throttle.h"
#include "configuration.h"
#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
@@ -118,7 +119,7 @@ void EInkDynamicDisplay::endOrDetach()
awaitRefresh();
else {
// Async begins
- LOG_DEBUG("Async full-refresh begins (dropping frames)\n");
+ LOG_DEBUG("Async full-refresh begins (dropping frames)");
notifyLater(intervalPollAsyncRefresh, DUE_POLL_ASYNCREFRESH, true); // Hand-off to NotifiedWorkerThread
}
}
@@ -132,7 +133,7 @@ void EInkDynamicDisplay::endOrDetach()
if (previousRefresh == FULL || previousRefresh == FAST) { // If refresh wasn't skipped (on unspecified..)
LOG_WARN(
"GxEPD2 version has not been modified to support async refresh; using fallback behavior. Please update lib_deps in "
- "variant's platformio.ini file\n");
+ "variant's platformio.ini file");
EInkDisplay::endUpdate();
}
#endif
@@ -169,7 +170,7 @@ bool EInkDynamicDisplay::determineMode()
checkFastRequested();
if (refresh == UNSPECIFIED)
- LOG_WARN("There was a flaw in the determineMode() logic.\n");
+ LOG_WARN("There was a flaw in the determineMode() logic.");
// -- Decision has been reached --
applyRefreshMode();
@@ -231,15 +232,13 @@ void EInkDynamicDisplay::checkForPromotion()
// Is it too soon for another frame of this type?
void EInkDynamicDisplay::checkRateLimiting()
{
- uint32_t now = millis();
-
// Sanity check: millis() overflow - just let the update run..
- if (previousRunMs > now)
+ if (previousRunMs > millis())
return;
// Skip update: too soon for BACKGROUND
if (frameFlags == BACKGROUND) {
- if (now - previousRunMs < EINK_LIMIT_RATE_BACKGROUND_SEC * 1000) {
+ if (Throttle::isWithinTimespanMs(previousRunMs, EINK_LIMIT_RATE_BACKGROUND_SEC * 1000)) {
refresh = SKIPPED;
reason = EXCEEDED_RATELIMIT_FULL;
return;
@@ -252,10 +251,10 @@ void EInkDynamicDisplay::checkRateLimiting()
// Skip update: too soon for RESPONSIVE
if (frameFlags & RESPONSIVE) {
- if (now - previousRunMs < EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000) {
+ if (Throttle::isWithinTimespanMs(previousRunMs, EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000)) {
refresh = SKIPPED;
reason = EXCEEDED_RATELIMIT_FAST;
- LOG_DEBUG("refresh=SKIPPED, reason=EXCEEDED_RATELIMIT_FAST, frameFlags=0x%x\n", frameFlags);
+ LOG_DEBUG("refresh=SKIPPED, reason=EXCEEDED_RATELIMIT_FAST, frameFlags=0x%x", frameFlags);
return;
}
}
@@ -272,7 +271,7 @@ void EInkDynamicDisplay::checkCosmetic()
if (frameFlags & COSMETIC) {
refresh = FULL;
reason = FLAGGED_COSMETIC;
- LOG_DEBUG("refresh=FULL, reason=FLAGGED_COSMETIC, frameFlags=0x%x\n", frameFlags);
+ LOG_DEBUG("refresh=FULL, reason=FLAGGED_COSMETIC, frameFlags=0x%x", frameFlags);
}
}
@@ -287,7 +286,7 @@ void EInkDynamicDisplay::checkDemandingFast()
if (frameFlags & DEMAND_FAST) {
refresh = FAST;
reason = FLAGGED_DEMAND_FAST;
- LOG_DEBUG("refresh=FAST, reason=FLAGGED_DEMAND_FAST, frameFlags=0x%x\n", frameFlags);
+ LOG_DEBUG("refresh=FAST, reason=FLAGGED_DEMAND_FAST, frameFlags=0x%x", frameFlags);
}
}
@@ -307,7 +306,7 @@ void EInkDynamicDisplay::checkFrameMatchesPrevious()
if (frameFlags == BACKGROUND && fastRefreshCount > 0) {
refresh = FULL;
reason = REDRAW_WITH_FULL;
- LOG_DEBUG("refresh=FULL, reason=REDRAW_WITH_FULL, frameFlags=0x%x\n", frameFlags);
+ LOG_DEBUG("refresh=FULL, reason=REDRAW_WITH_FULL, frameFlags=0x%x", frameFlags);
return;
}
#endif
@@ -315,7 +314,7 @@ void EInkDynamicDisplay::checkFrameMatchesPrevious()
// Not redrawn, not COSMETIC, not DEMAND_FAST
refresh = SKIPPED;
reason = FRAME_MATCHED_PREVIOUS;
- LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS, frameFlags=0x%x\n", frameFlags);
+ LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS, frameFlags=0x%x", frameFlags);
}
// Have too many fast-refreshes occured consecutively, since last full refresh?
@@ -329,7 +328,7 @@ void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) {
refresh = FULL;
reason = EXCEEDED_LIMIT_FASTREFRESH;
- LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH, frameFlags=0x%x\n", frameFlags);
+ LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH, frameFlags=0x%x", frameFlags);
}
}
@@ -344,13 +343,13 @@ void EInkDynamicDisplay::checkFastRequested()
// If we want BACKGROUND to use fast. (FULL only when a limit is hit)
refresh = FAST;
reason = BACKGROUND_USES_FAST;
- LOG_DEBUG("refresh=FAST, reason=BACKGROUND_USES_FAST, fastRefreshCount=%lu, frameFlags=0x%x\n", fastRefreshCount,
+ LOG_DEBUG("refresh=FAST, reason=BACKGROUND_USES_FAST, fastRefreshCount=%lu, frameFlags=0x%x", fastRefreshCount,
frameFlags);
#else
// If we do want to use FULL for BACKGROUND updates
refresh = FULL;
reason = FLAGGED_BACKGROUND;
- LOG_DEBUG("refresh=FULL, reason=FLAGGED_BACKGROUND\n");
+ LOG_DEBUG("refresh=FULL, reason=FLAGGED_BACKGROUND");
#endif
}
@@ -358,7 +357,7 @@ void EInkDynamicDisplay::checkFastRequested()
if (frameFlags & RESPONSIVE) {
refresh = FAST;
reason = NO_OBJECTIONS;
- LOG_DEBUG("refresh=FAST, reason=NO_OBJECTIONS, fastRefreshCount=%lu, frameFlags=0x%x\n", fastRefreshCount, frameFlags);
+ LOG_DEBUG("refresh=FAST, reason=NO_OBJECTIONS, fastRefreshCount=%lu, frameFlags=0x%x", fastRefreshCount, frameFlags);
}
}
@@ -439,7 +438,7 @@ void EInkDynamicDisplay::checkExcessiveGhosting()
if (ghostPixelCount > EINK_LIMIT_GHOSTING_PX) {
refresh = FULL;
reason = EXCEEDED_GHOSTINGLIMIT;
- LOG_DEBUG("refresh=FULL, reason=EXCEEDED_GHOSTINGLIMIT, frameFlags=0x%x\n", frameFlags);
+ LOG_DEBUG("refresh=FULL, reason=EXCEEDED_GHOSTINGLIMIT, frameFlags=0x%x", frameFlags);
}
}
@@ -470,7 +469,7 @@ void EInkDynamicDisplay::joinAsyncRefresh()
if (!asyncRefreshRunning)
return;
- LOG_DEBUG("Joining an async refresh in progress\n");
+ LOG_DEBUG("Joining an async refresh in progress");
// Continually poll the BUSY pin
while (adafruitDisplay->epd2.isBusy())
@@ -480,7 +479,7 @@ void EInkDynamicDisplay::joinAsyncRefresh()
adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
asyncRefreshRunning = false; // Unset the flag
- LOG_DEBUG("Refresh complete\n");
+ LOG_DEBUG("Refresh complete");
// Note: this code only works because of a modification to meshtastic/GxEPD2.
// It is only equipped to intercept calls to nextPage()
@@ -504,7 +503,7 @@ void EInkDynamicDisplay::pollAsyncRefresh()
adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
asyncRefreshRunning = false; // Unset the flag
- LOG_DEBUG("Async full-refresh complete\n");
+ LOG_DEBUG("Async full-refresh complete");
// Note: this code only works because of a modification to meshtastic/GxEPD2.
// It is only equipped to intercept calls to nextPage()
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index 04fe73e44..efa3ec78f 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -22,6 +22,7 @@ along with this program. If not, see .
#include "Screen.h"
#include "../userPrefs.h"
#include "PowerMon.h"
+#include "Throttle.h"
#include "configuration.h"
#if HAS_SCREEN
#include
@@ -47,6 +48,7 @@ along with this program. If not, see .
#include "modules/AdminModule.h"
#include "modules/ExternalNotificationModule.h"
#include "modules/TextMessageModule.h"
+#include "modules/WaypointModule.h"
#include "sleep.h"
#include "target_specific.h"
@@ -56,10 +58,11 @@ along with this program. If not, see .
#ifdef ARCH_ESP32
#include "esp_task_wdt.h"
-#include "modules/esp32/StoreForwardModule.h"
+#include "modules/StoreForwardModule.h"
#endif
#if ARCH_PORTDUINO
+#include "modules/StoreForwardModule.h"
#include "platform/portduino/PortduinoGlue.h"
#endif
@@ -117,6 +120,7 @@ static bool heartbeat = false;
#define SCREEN_HEIGHT display->getHeight()
#include "graphics/ScreenFonts.h"
+#include
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
@@ -140,7 +144,7 @@ static bool haveGlyphs(const char *str)
}
}
- LOG_DEBUG("haveGlyphs=%d\n", have);
+ LOG_DEBUG("haveGlyphs=%d", have);
return have;
}
@@ -159,8 +163,8 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl
display->setFont(FONT_MEDIUM);
display->setTextAlignment(TEXT_ALIGN_LEFT);
-#ifdef SPLASH_TITLE_USERPREFS
- const char *title = SPLASH_TITLE_USERPREFS;
+#ifdef USERPREFS_SPLASH_TITLE
+ const char *title = USERPREFS_SPLASH_TITLE;
#else
const char *title = "meshtastic.org";
#endif
@@ -182,56 +186,6 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl
display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
}
-static void drawOEMIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
-{
- // draw an xbm image.
- // Please note that everything that should be transitioned
- // needs to be drawn relative to x and y
-
- // draw centered icon left to right and centered above the one line of app text
- display->drawXbm(x + (SCREEN_WIDTH - oemStore.oem_icon_width) / 2,
- y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - oemStore.oem_icon_height) / 2 + 2, oemStore.oem_icon_width,
- oemStore.oem_icon_height, (const uint8_t *)oemStore.oem_icon_bits.bytes);
-
- switch (oemStore.oem_font) {
- case 0:
- display->setFont(FONT_SMALL);
- break;
- case 2:
- display->setFont(FONT_LARGE);
- break;
- default:
- display->setFont(FONT_MEDIUM);
- break;
- }
-
- display->setTextAlignment(TEXT_ALIGN_LEFT);
- const char *title = oemStore.oem_text;
- display->drawString(x + getStringCenteredX(title), y + SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM, title);
- display->setFont(FONT_SMALL);
-
- // Draw region in upper left
- if (upperMsg)
- display->drawString(x + 0, y + 0, upperMsg);
-
- // Draw version and shortname in upper right
- char buf[25];
- snprintf(buf, sizeof(buf), "%s\n%s", xstr(APP_VERSION_SHORT), haveGlyphs(owner.short_name) ? owner.short_name : "");
-
- display->setTextAlignment(TEXT_ALIGN_RIGHT);
- display->drawString(x + SCREEN_WIDTH, y + 0, buf);
- screen->forceDisplay();
-
- display->setTextAlignment(TEXT_ALIGN_LEFT); // Restore left align, just to be kind to any other unsuspecting code
-}
-
-static void drawOEMBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
-{
- // Draw region in upper left
- const char *region = myRegion ? myRegion->name : NULL;
- drawOEMIconScreen(region, display, state, x, y);
-}
-
void Screen::drawFrameText(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y, const char *message)
{
uint16_t x_offset = display->width() / 2;
@@ -288,7 +242,7 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i
// 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");
+ // LOG_DEBUG("Drawing function overlay");
if (functionSymbals.begin() != functionSymbals.end()) {
char buf[64];
display->setFont(FONT_SMALL);
@@ -306,7 +260,7 @@ static void drawDeepSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state,
EINK_ADD_FRAMEFLAG(display, COSMETIC);
EINK_ADD_FRAMEFLAG(display, BLOCKING);
- LOG_DEBUG("Drawing deep sleep screen\n");
+ LOG_DEBUG("Drawing deep sleep screen");
// Display displayStr on the screen
drawIconScreen("Sleeping", display, state, x, y);
@@ -315,7 +269,7 @@ static void drawDeepSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state,
/// Used on eink displays when screen updates are paused
static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
{
- LOG_DEBUG("Drawing screensaver overlay\n");
+ LOG_DEBUG("Drawing screensaver overlay");
EINK_ADD_FRAMEFLAG(display, COSMETIC); // Take the opportunity for a full-refresh
@@ -381,9 +335,9 @@ static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int
} else {
// otherwise, just display the module frame that's aligned with the current frame
module_frame = state->currentFrame;
- // LOG_DEBUG("Screen is not in transition. Frame: %d\n\n", module_frame);
+ // LOG_DEBUG("Screen is not in transition. Frame: %d", module_frame);
}
- // LOG_DEBUG("Drawing Module Frame %d\n\n", module_frame);
+ // LOG_DEBUG("Drawing Module Frame %d", module_frame);
MeshModule &pi = *moduleFrames.at(module_frame);
pi.drawFrame(display, state, x, y);
}
@@ -493,7 +447,7 @@ void Screen::drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *sta
display->drawString(x + 20, y + 2, batteryPercent);
}
- if (nimbleBluetooth->isConnected()) {
+ if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
drawBluetoothConnectedIcon(display, display->getWidth() - 18, y + 2);
}
@@ -725,7 +679,7 @@ void Screen::drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *stat
display->drawString(x + 20, y + 2, batteryPercent);
}
- if (nimbleBluetooth->isConnected()) {
+ if (nimbleBluetooth && nimbleBluetooth->isConnected()) {
drawBluetoothConnectedIcon(display, display->getWidth() - 18, y + 2);
}
@@ -958,7 +912,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
const meshtastic_MeshPacket &mp = devicestate.rx_text_message;
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
- // LOG_DEBUG("drawing text message from 0x%x: %s\n", mp.from,
+ // LOG_DEBUG("drawing text message from 0x%x: %s", mp.from,
// mp.decoded.variant.data.decoded.bytes);
// Demo for drawStringMaxWidth:
@@ -1004,55 +958,55 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
display->setColor(WHITE);
#ifndef EXCLUDE_EMOJI
- if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), u8"\U0001F44D") == 0) {
+ if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\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) {
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\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) {
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "❓") == 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) {
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "‼️") == 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) {
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\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) {
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\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) {
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\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) {
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\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) {
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\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) {
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\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) {
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "☁️") == 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) {
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "🌫️") == 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) {
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "\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) {
+ } else if (strcmp(reinterpret_cast(mp.decoded.payload.bytes), "♥️") == 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 {
@@ -1093,8 +1047,8 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStat
{
char usersString[20];
snprintf(usersString, sizeof(usersString), "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal());
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \
- defined(HX8357_CS)) && \
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
+ defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x, y + 3, 8, 8, imgUser);
#else
@@ -1285,8 +1239,8 @@ static int8_t prevFrame = -1;
// Draw the arrow pointing to a node's location
void Screen::drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t compassY, uint16_t compassDiam, float headingRadian)
{
- Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially
- float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f;
+ Point tip(0.0f, 0.5f), tail(0.0f, -0.35f); // pointing up initially
+ float arrowOffsetX = 0.14f, arrowOffsetY = 1.0f;
Point leftArrow(tip.x - arrowOffsetX, tip.y - arrowOffsetY), rightArrow(tip.x + arrowOffsetX, tip.y - arrowOffsetY);
Point *arrowPoints[] = {&tip, &tail, &leftArrow, &rightArrow};
@@ -1296,9 +1250,19 @@ void Screen::drawNodeHeading(OLEDDisplay *display, int16_t compassX, int16_t com
arrowPoints[i]->scale(compassDiam * 0.6);
arrowPoints[i]->translate(compassX, compassY);
}
+ /* Old arrow
display->drawLine(tip.x, tip.y, tail.x, tail.y);
display->drawLine(leftArrow.x, leftArrow.y, tip.x, tip.y);
display->drawLine(rightArrow.x, rightArrow.y, tip.x, tip.y);
+ display->drawLine(leftArrow.x, leftArrow.y, tail.x, tail.y);
+ display->drawLine(rightArrow.x, rightArrow.y, tail.x, tail.y);
+ */
+#ifdef USE_EINK
+ display->drawTriangle(tip.x, tip.y, rightArrow.x, rightArrow.y, tail.x, tail.y);
+#else
+ display->fillTriangle(tip.x, tip.y, rightArrow.x, rightArrow.y, tail.x, tail.y);
+#endif
+ display->drawTriangle(tip.x, tip.y, leftArrow.x, leftArrow.y, tail.x, tail.y);
}
// Get a string representation of the time passed since something happened
@@ -1336,22 +1300,27 @@ void Screen::drawCompassNorth(OLEDDisplay *display, int16_t compassX, int16_t co
// If north is supposed to be at the top of the compass we want rotation to be +0
if (config.display.compass_north_top)
myHeading = -0;
-
- Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f);
+ /* N sign points currently not deleted*/
+ Point N1(-0.04f, 0.65f), N2(0.04f, 0.65f); // N sign points (N1-N4)
Point N3(-0.04f, 0.55f), N4(0.04f, 0.55f);
- Point *rosePoints[] = {&N1, &N2, &N3, &N4};
+ Point NC1(0.00f, 0.50f); // north circle center point
+ Point *rosePoints[] = {&N1, &N2, &N3, &N4, &NC1};
uint16_t compassDiam = Screen::getCompassDiam(SCREEN_WIDTH, SCREEN_HEIGHT);
- for (int i = 0; i < 4; i++) {
+ for (int i = 0; i < 5; i++) {
// North on compass will be negative of heading
rosePoints[i]->rotate(-myHeading);
rosePoints[i]->scale(compassDiam);
rosePoints[i]->translate(compassX, compassY);
}
+
+ /* changed the N sign to a small circle on the compass circle.
display->drawLine(N1.x, N1.y, N3.x, N3.y);
display->drawLine(N2.x, N2.y, N4.x, N4.y);
display->drawLine(N1.x, N1.y, N4.x, N4.y);
+ */
+ display->drawCircle(NC1.x, NC1.y, 4); // North sign circle, 4px radius is sufficient for all displays.
}
uint16_t Screen::getCompassDiam(uint32_t displayWidth, uint32_t displayHeight)
@@ -1481,7 +1450,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
if (!hasNodeHeading) {
// direction to node is unknown so display question mark
// Debug info for gps lock errors
- // LOG_DEBUG("ourNode %d, ourPos %d, theirPos %d\n", !!ourNode, ourNode && hasValidPosition(ourNode),
+ // LOG_DEBUG("ourNode %d, ourPos %d, theirPos %d", !!ourNode, ourNode && hasValidPosition(ourNode),
// hasValidPosition(node));
display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?");
}
@@ -1515,7 +1484,8 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
#elif defined(USE_SSD1306)
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
-#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) || defined(HX8357_CS)
+#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7789_CS) || \
+ defined(RAK14014) || defined(HX8357_CS)
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
@@ -1529,7 +1499,7 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#elif ARCH_PORTDUINO
if (settingsMap[displayPanel] != no_screen) {
- LOG_DEBUG("Making TFTDisplay!\n");
+ LOG_DEBUG("Making TFTDisplay!");
dispdev = new TFTDisplay(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
} else {
@@ -1576,7 +1546,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
if (on != screenOn) {
if (on) {
- LOG_INFO("Turning on screen\n");
+ LOG_INFO("Turning on screen");
powerMon->setState(meshtastic_PowerMon_State_Screen_On);
#ifdef T_WATCH_S3
PMU->enablePowerOutput(XPOWERS_ALDO2);
@@ -1611,7 +1581,7 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
// eInkScreensaver parameter is usually NULL (default argument), default frame used instead
setScreensaverFrames(einkScreensaver);
#endif
- LOG_INFO("Turning off screen\n");
+ LOG_INFO("Turning off screen");
dispdev->displayOff();
#ifdef USE_ST7789
SPI1.end();
@@ -1654,6 +1624,11 @@ void Screen::setup()
static_cast(dispdev)->setSubtype(7);
#endif
+#if defined(USE_ST7789) && defined(TFT_MESH)
+ // Heltec T114 and T190: honor a custom text color, if defined in variant.h
+ static_cast(dispdev)->setRGB(TFT_MESH);
+#endif
+
// Initialising the UI will init the display too.
ui->init();
@@ -1674,9 +1649,6 @@ void Screen::setup()
// Set the utf8 conversion function
dispdev->setFontTableLookupFunction(customFontTableLookup);
- if (strlen(oemStore.oem_text) > 0)
- logo_timeout *= 2;
-
// Add frames.
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST);
alertFrames[0] = [this](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
@@ -1707,8 +1679,11 @@ void Screen::setup()
// Standard behaviour is to FLIP the screen (needed on T-Beam). If this config item is set, unflip it, and thereby logically
// flip it. If you have a headache now, you're welcome.
if (!config.display.flip_screen) {
-#if defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014) || defined(HX8357_CS)
+#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || \
+ defined(ST7789_CS) || defined(RAK14014) || defined(HX8357_CS)
static_cast(dispdev)->flipScreenVertically();
+#elif defined(USE_ST7789)
+ static_cast(dispdev)->flipScreenVertically();
#else
dispdev->flipScreenVertically();
#endif
@@ -1766,6 +1741,11 @@ void Screen::forceDisplay(bool forceUiUpdate)
#ifdef USE_EINK
// If requested, make sure queued commands are run, and UI has rendered a new frame
if (forceUiUpdate) {
+ // Force a display refresh, in addition to the UI update
+ // Changing the GPS status bar icon apparently doesn't register as a change in image
+ // (False negative of the image hashing algorithm used to skip identical frames)
+ EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST);
+
// No delay between UI frame rendering
setFastFramerate();
@@ -1809,28 +1789,11 @@ int32_t Screen::runOnce()
// serialSinceMsec adjusts for additional serial wait time during nRF52 bootup
static bool showingBootScreen = true;
if (showingBootScreen && (millis() > (logo_timeout + serialSinceMsec))) {
- LOG_INFO("Done with boot screen...\n");
+ LOG_INFO("Done with boot screen...");
stopBootScreen();
showingBootScreen = false;
}
- // If we have an OEM Boot screen, toggle after logo_timeout seconds
- if (strlen(oemStore.oem_text) > 0) {
- static bool showingOEMBootScreen = true;
- if (showingOEMBootScreen && (millis() > ((logo_timeout / 2) + serialSinceMsec))) {
- LOG_INFO("Switch to OEM screen...\n");
- // Change frames.
- static FrameCallback bootOEMFrames[] = {drawOEMBootScreen};
- static const int bootOEMFrameCount = sizeof(bootOEMFrames) / sizeof(bootOEMFrames[0]);
- ui->setFrames(bootOEMFrames, bootOEMFrameCount);
- ui->update();
-#ifndef USE_EINK
- ui->update();
-#endif
- showingOEMBootScreen = false;
- }
- }
-
#ifndef DISABLE_WELCOME_UNSET
if (showingNormalScreen && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
setWelcomeFrames();
@@ -1851,13 +1814,7 @@ int32_t Screen::runOnce()
handleSetOn(false);
break;
case Cmd::ON_PRESS:
- // If a nag notification is running, stop it
- if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) {
- externalNotificationModule->stopNow();
- } else {
- // Don't advance the screen if we just wanted to switch off the nag notification
- handleOnPress();
- }
+ handleOnPress();
break;
case Cmd::SHOW_PREV_FRAME:
handleShowPrevFrame();
@@ -1890,7 +1847,7 @@ int32_t Screen::runOnce()
free(cmd.print_text);
break;
default:
- LOG_ERROR("Invalid screen cmd\n");
+ LOG_ERROR("Invalid screen cmd");
}
}
@@ -1920,7 +1877,7 @@ int32_t Screen::runOnce()
if (showingNormalScreen) {
// standard screen loop handling here
if (config.display.auto_screen_carousel_secs > 0 &&
- (millis() - lastScreenTransition) > (config.display.auto_screen_carousel_secs * 1000)) {
+ !Throttle::isWithinTimespanMs(lastScreenTransition, config.display.auto_screen_carousel_secs * 1000)) {
// If an E-Ink display struggles with fast refresh, force carousel to use full refresh instead
// Carousel is potentially a major source of E-Ink display wear
@@ -1928,12 +1885,12 @@ int32_t Screen::runOnce()
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC);
#endif
- LOG_DEBUG("LastScreenTransition exceeded %ums transitioning to next frame\n", (millis() - lastScreenTransition));
+ LOG_DEBUG("LastScreenTransition exceeded %ums transitioning to next frame", (millis() - lastScreenTransition));
handleOnPress();
}
}
- // LOG_DEBUG("want fps %d, fixed=%d\n", targetFramerate,
+ // LOG_DEBUG("want fps %d, fixed=%d", targetFramerate,
// ui->getUiState()->frameState); If we are scrolling we need to be called
// soon, otherwise just 1 fps (to save CPU) We also ask to be called twice
// as fast as we really need so that any rounding errors still result with
@@ -1964,7 +1921,7 @@ void Screen::drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiStat
void Screen::setSSLFrames()
{
if (address_found.address) {
- // LOG_DEBUG("showing SSL frames\n");
+ // LOG_DEBUG("showing SSL frames");
static FrameCallback sslFrames[] = {drawSSLScreen};
ui->setFrames(sslFrames, 1);
ui->update();
@@ -1976,7 +1933,7 @@ void Screen::setSSLFrames()
void Screen::setWelcomeFrames()
{
if (address_found.address) {
- // LOG_DEBUG("showing Welcome frames\n");
+ // LOG_DEBUG("showing Welcome frames");
static FrameCallback frames[] = {drawWelcomeScreen};
setFrameImmediateDraw(frames);
}
@@ -2042,7 +1999,7 @@ void Screen::setFrames(FrameFocus focus)
uint8_t originalPosition = ui->getUiState()->currentFrame;
FramesetInfo fsi; // Location of specific frames, for applying focus parameter
- LOG_DEBUG("showing standard frames\n");
+ LOG_DEBUG("showing standard frames");
showingNormalScreen = true;
#ifdef USE_EINK
@@ -2055,10 +2012,10 @@ void Screen::setFrames(FrameFocus focus)
#endif
moduleFrames = MeshModule::GetMeshModulesWithUIFrames();
- LOG_DEBUG("Showing %d module frames\n", moduleFrames.size());
+ LOG_DEBUG("Showing %d module frames", moduleFrames.size());
#ifdef DEBUG_PORT
int totalFrameCount = MAX_NUM_NODES + NUM_EXTRA_FRAMES + moduleFrames.size();
- LOG_DEBUG("Total frame count: %d\n", totalFrameCount);
+ LOG_DEBUG("Total frame count: %d", totalFrameCount);
#endif
// We don't show the node info of our node (if we have it yet - we should)
@@ -2081,13 +2038,18 @@ void Screen::setFrames(FrameFocus focus)
// Check if the module being drawn has requested focus
// We will honor this request later, if setFrames was triggered by a UIFrameEvent
MeshModule *m = *i;
- if (m->isRequestingFocus())
+ if (m->isRequestingFocus()) {
fsi.positions.focusedModule = numframes;
+ }
+
+ // Identify the position of specific modules, if we need to know this later
+ if (m == waypointModule)
+ fsi.positions.waypoint = numframes;
numframes++;
}
- LOG_DEBUG("Added modules. numframes: %d\n", numframes);
+ LOG_DEBUG("Added modules. numframes: %d", numframes);
// If we have a critical fault, show it first
fsi.positions.fault = numframes;
@@ -2101,8 +2063,8 @@ void Screen::setFrames(FrameFocus focus)
#endif
// If we have a text message - show it next, unless it's a phone message and we aren't using any special modules
- fsi.positions.textMessage = numframes;
if (devicestate.has_rx_text_message && shouldDrawMessage(&devicestate.rx_text_message)) {
+ fsi.positions.textMessage = numframes;
normalFrames[numframes++] = drawTextMessageFrame;
}
@@ -2132,7 +2094,7 @@ void Screen::setFrames(FrameFocus focus)
#endif
fsi.frameCount = numframes; // Total framecount is used to apply FOCUS_PRESERVE
- LOG_DEBUG("Finished building frames. numframes: %d\n", numframes);
+ LOG_DEBUG("Finished building frames. numframes: %d", numframes);
ui->setFrames(normalFrames, numframes);
ui->enableAllIndicators();
@@ -2164,7 +2126,7 @@ void Screen::setFrames(FrameFocus focus)
case FOCUS_PRESERVE:
// If we can identify which type of frame "originalPosition" was, can move directly to it in the new frameset
- FramesetInfo &oldFsi = this->framesetInfo;
+ const FramesetInfo &oldFsi = this->framesetInfo;
if (originalPosition == oldFsi.positions.log)
ui->switchToFrame(fsi.positions.log);
else if (originalPosition == oldFsi.positions.settings)
@@ -2204,9 +2166,34 @@ void Screen::setFrameImmediateDraw(FrameCallback *drawFrames)
setFastFramerate();
}
+// Dismisses the currently displayed screen frame, if possible
+// Relevant for text message, waypoint, others in future?
+// Triggered with a CardKB keycombo
+void Screen::dismissCurrentFrame()
+{
+ uint8_t currentFrame = ui->getUiState()->currentFrame;
+ bool dismissed = false;
+
+ if (currentFrame == framesetInfo.positions.textMessage && devicestate.has_rx_text_message) {
+ LOG_INFO("Dismissing Text Message");
+ devicestate.has_rx_text_message = false;
+ dismissed = true;
+ }
+
+ else if (currentFrame == framesetInfo.positions.waypoint && devicestate.has_rx_waypoint) {
+ LOG_DEBUG("Dismissing Waypoint");
+ devicestate.has_rx_waypoint = false;
+ dismissed = true;
+ }
+
+ // If we did make changes to dismiss, we now need to regenerate the frameset
+ if (dismissed)
+ setFrames();
+}
+
void Screen::handleStartFirmwareUpdateScreen()
{
- LOG_DEBUG("showing firmware screen\n");
+ LOG_DEBUG("showing firmware screen");
showingNormalScreen = false;
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
@@ -2298,7 +2285,7 @@ void Screen::handlePrint(const char *text)
{
// the string passed into us probably has a newline, but that would confuse the logging system
// so strip it
- LOG_DEBUG("Screen: %.*s\n", strlen(text) - 1, text);
+ LOG_DEBUG("Screen: %.*s", strlen(text) - 1, text);
if (!useDisplay || !showingNormalScreen)
return;
@@ -2413,10 +2400,10 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// Draw our hardware ID to assist with bluetooth pairing. Either prefix with Info or S&F Logo
if (moduleConfig.store_forward.enabled) {
#ifdef ARCH_ESP32
- if (millis() - storeForwardModule->lastHeartbeat >
- (storeForwardModule->heartbeatInterval * 1200)) { // no heartbeat, overlap a bit
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \
- defined(HX8357_CS)) && \
+ if (!Throttle::isWithinTimespanMs(storeForwardModule->lastHeartbeat,
+ (storeForwardModule->heartbeatInterval * 1200))) { // no heartbeat, overlap a bit
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
+ defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgQuestionL1);
@@ -2427,8 +2414,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
imgQuestion);
#endif
} else {
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \
- defined(HX8357_CS)) && \
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
+ defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8,
imgSFL1);
@@ -2442,8 +2429,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
#endif
} else {
// TODO: Raspberry Pi supports more than just the one screen size
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \
- defined(HX8357_CS) || ARCH_PORTDUINO) && \
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
+ defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgInfoL1);
@@ -2651,7 +2638,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
int Screen::handleStatusUpdate(const meshtastic::Status *arg)
{
- // LOG_DEBUG("Screen got status update %d\n", arg->getStatusType());
+ // LOG_DEBUG("Screen got status update %d", arg->getStatusType());
switch (arg->getStatusType()) {
case STATUS_TYPE_NODE:
if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
@@ -2716,12 +2703,23 @@ int Screen::handleInputEvent(const InputEvent *event)
}
#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)) {
- showPrevFrame();
- } else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT)) {
- showNextFrame();
+ // Use left or right input from a keyboard to move between frames,
+ // so long as a mesh module isn't using these events for some other purpose
+ if (showingNormalScreen) {
+
+ // Ask any MeshModules if they're handling keyboard input right now
+ bool inputIntercepted = false;
+ for (MeshModule *module : moduleFrames) {
+ if (module->interceptingKeyboardInput())
+ inputIntercepted = true;
+ }
+
+ // If no modules are using the input, move between frames
+ if (!inputIntercepted) {
+ if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT))
+ showPrevFrame();
+ else if (event->inputEvent == static_cast(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT))
+ showNextFrame();
}
}
diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h
index 021b11bda..b2e6e905b 100644
--- a/src/graphics/Screen.h
+++ b/src/graphics/Screen.h
@@ -454,6 +454,9 @@ class Screen : public concurrency::OSThread
void setWelcomeFrames();
+ // Dismiss the currently focussed frame, if possible (e.g. text message, waypoint)
+ void dismissCurrentFrame();
+
#ifdef USE_EINK
/// Draw an image to remain on E-Ink display after screen off
void setScreensaverFrames(FrameCallback einkScreensaver = NULL);
@@ -503,11 +506,14 @@ class Screen : public concurrency::OSThread
void handleStartFirmwareUpdateScreen();
// Info collected by setFrames method.
- // Index location of specific frames. Used to apply the FrameFocus parameter of setFrames
+ // Index location of specific frames.
+ // - Used to apply the FrameFocus parameter of setFrames
+ // - Used to dismiss the currently shown frame (txt; waypoint) by CardKB combo
struct FramesetInfo {
struct FramePositions {
uint8_t fault = 0;
uint8_t textMessage = 0;
+ uint8_t waypoint = 0;
uint8_t focusedModule = 0;
uint8_t log = 0;
uint8_t settings = 0;
diff --git a/src/graphics/ScreenFonts.h b/src/graphics/ScreenFonts.h
index 267f6e0a8..c9ce961fa 100644
--- a/src/graphics/ScreenFonts.h
+++ b/src/graphics/ScreenFonts.h
@@ -12,8 +12,8 @@
#include "graphics/fonts/OLEDDisplayFontsUA.h"
#endif
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(USE_ST7789) || \
- defined(HX8357_CS)) && \
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
+ defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
// The screen is bigger so use bigger fonts
#define FONT_SMALL ArialMT_Plain_16 // Height: 19
@@ -41,4 +41,4 @@
#define FONT_HEIGHT_SMALL _fontHeight(FONT_SMALL)
#define FONT_HEIGHT_MEDIUM _fontHeight(FONT_MEDIUM)
-#define FONT_HEIGHT_LARGE _fontHeight(FONT_LARGE)
\ No newline at end of file
+#define FONT_HEIGHT_LARGE _fontHeight(FONT_LARGE)
diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp
index 2849dd9a9..3ba847c23 100644
--- a/src/graphics/TFTDisplay.cpp
+++ b/src/graphics/TFTDisplay.cpp
@@ -244,9 +244,9 @@ class LGFX : public lgfx::LGFX_Device
static LGFX *tft = nullptr;
-#elif defined(ILI9341_DRIVER)
+#elif defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER)
-#include // Graphics and font library for ILI9341 driver chip
+#include // Graphics and font library for ILI9341/ILI9342 driver chip
#if defined(ILI9341_BACKLIGHT_EN) && !defined(TFT_BL)
#define TFT_BL ILI9341_BACKLIGHT_EN
@@ -254,7 +254,11 @@ static LGFX *tft = nullptr;
class LGFX : public lgfx::LGFX_Device
{
+#if defined(ILI9341_DRIVER)
lgfx::Panel_ILI9341 _panel_instance;
+#elif defined(ILI9342_DRIVER)
+ lgfx::Panel_ILI9342 _panel_instance;
+#endif
lgfx::Bus_SPI _bus_instance;
lgfx::Light_PWM _light_instance;
@@ -265,7 +269,11 @@ class LGFX : public lgfx::LGFX_Device
auto cfg = _bus_instance.config();
// configure SPI
+#if defined(ILI9341_DRIVER)
cfg.spi_host = ILI9341_SPI_HOST; // ESP32-S2,S3,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
+#elif defined(ILI9342_DRIVER)
+ cfg.spi_host = ILI9342_SPI_HOST; // ESP32-S2,S3,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
+#endif
cfg.spi_mode = 0;
cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
// 80MHz by an integer)
@@ -336,7 +344,7 @@ class LGFX : public lgfx::LGFX_Device
static LGFX *tft = nullptr;
#elif defined(ST7735_CS)
-#include // Graphics and font library for ILI9341 driver chip
+#include // Graphics and font library for ILI9342 driver chip
static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
#elif ARCH_PORTDUINO && HAS_SCREEN != 0
@@ -360,6 +368,8 @@ class LGFX : public lgfx::LGFX_Device
_panel_instance = new lgfx::Panel_ST7735S;
else if (settingsMap[displayPanel] == ili9341)
_panel_instance = new lgfx::Panel_ILI9341;
+ else if (settingsMap[displayPanel] == ili9342)
+ _panel_instance = new lgfx::Panel_ILI9342;
auto buscfg = _bus_instance.config();
buscfg.spi_mode = 0;
buscfg.spi_host = settingsMap[displayspidev];
@@ -370,7 +380,7 @@ class LGFX : public lgfx::LGFX_Device
_panel_instance->setBus(&_bus_instance); // set the bus on the panel.
auto cfg = _panel_instance->config(); // Gets a structure for display panel settings.
- LOG_DEBUG("Height: %d, Width: %d \n", settingsMap[displayHeight], settingsMap[displayWidth]);
+ LOG_DEBUG("Height: %d, Width: %d ", settingsMap[displayHeight], settingsMap[displayWidth]);
cfg.pin_cs = settingsMap[displayCS]; // Pin number where CS is connected (-1 = disable)
cfg.pin_rst = settingsMap[displayReset];
cfg.panel_width = settingsMap[displayWidth]; // actual displayable width
@@ -500,10 +510,126 @@ class LGFX : public lgfx::LGFX_Device
static LGFX *tft = nullptr;
+#elif defined(ST7701_CS)
+#include // Graphics and font library for ST7701 driver chip
+#include
+#include
+
+class LGFX : public lgfx::LGFX_Device
+{
+ lgfx::Panel_ST7701 _panel_instance;
+ lgfx::Bus_RGB _bus_instance;
+ lgfx::Light_PWM _light_instance;
+ lgfx::Touch_FT5x06 _touch_instance;
+
+ public:
+ LGFX(void)
+ {
+ {
+ auto cfg = _panel_instance.config();
+ cfg.memory_width = 800;
+ cfg.memory_height = 480;
+ cfg.panel_width = TFT_WIDTH;
+ cfg.panel_height = TFT_HEIGHT;
+ cfg.offset_x = TFT_OFFSET_X;
+ cfg.offset_y = TFT_OFFSET_Y;
+ _panel_instance.config(cfg);
+ }
+
+ {
+ auto cfg = _panel_instance.config_detail();
+ cfg.pin_cs = ST7701_CS;
+ cfg.pin_sclk = ST7701_SCK;
+ cfg.pin_mosi = ST7701_SDA;
+ // cfg.use_psram = 1;
+ _panel_instance.config_detail(cfg);
+ }
+
+ {
+ auto cfg = _bus_instance.config();
+ cfg.panel = &_panel_instance;
+#ifdef SENSECAP_INDICATOR
+ cfg.pin_d0 = GPIO_NUM_15; // B0
+ cfg.pin_d1 = GPIO_NUM_14; // B1
+ cfg.pin_d2 = GPIO_NUM_13; // B2
+ cfg.pin_d3 = GPIO_NUM_12; // B3
+ cfg.pin_d4 = GPIO_NUM_11; // B4
+
+ cfg.pin_d5 = GPIO_NUM_10; // G0
+ cfg.pin_d6 = GPIO_NUM_9; // G1
+ cfg.pin_d7 = GPIO_NUM_8; // G2
+ cfg.pin_d8 = GPIO_NUM_7; // G3
+ cfg.pin_d9 = GPIO_NUM_6; // G4
+ cfg.pin_d10 = GPIO_NUM_5; // G5
+
+ cfg.pin_d11 = GPIO_NUM_4; // R0
+ cfg.pin_d12 = GPIO_NUM_3; // R1
+ cfg.pin_d13 = GPIO_NUM_2; // R2
+ cfg.pin_d14 = GPIO_NUM_1; // R3
+ cfg.pin_d15 = GPIO_NUM_0; // R4
+
+ cfg.pin_henable = GPIO_NUM_18;
+ cfg.pin_vsync = GPIO_NUM_17;
+ cfg.pin_hsync = GPIO_NUM_16;
+ cfg.pin_pclk = GPIO_NUM_21;
+ cfg.freq_write = 12000000;
+
+ cfg.hsync_polarity = 0;
+ cfg.hsync_front_porch = 10;
+ cfg.hsync_pulse_width = 8;
+ cfg.hsync_back_porch = 50;
+
+ cfg.vsync_polarity = 0;
+ cfg.vsync_front_porch = 10;
+ cfg.vsync_pulse_width = 8;
+ cfg.vsync_back_porch = 20;
+
+ cfg.pclk_active_neg = 0;
+ cfg.de_idle_high = 1;
+ cfg.pclk_idle_high = 0;
+#endif
+ _bus_instance.config(cfg);
+ }
+ _panel_instance.setBus(&_bus_instance);
+
+ {
+ auto cfg = _light_instance.config();
+ cfg.pin_bl = ST7701_BL;
+ _light_instance.config(cfg);
+ }
+ _panel_instance.light(&_light_instance);
+
+ {
+ auto cfg = _touch_instance.config();
+ cfg.pin_cs = -1;
+ cfg.x_min = 0;
+ cfg.x_max = 479;
+ cfg.y_min = 0;
+ cfg.y_max = 479;
+ cfg.pin_int = -1; // don't use SCREEN_TOUCH_INT;
+ cfg.pin_rst = SCREEN_TOUCH_RST;
+ cfg.bus_shared = true;
+ cfg.offset_rotation = TFT_OFFSET_ROTATION;
+
+ cfg.i2c_port = TOUCH_I2C_PORT;
+ cfg.i2c_addr = TOUCH_SLAVE_ADDRESS;
+ cfg.pin_sda = I2C_SDA;
+ cfg.pin_scl = I2C_SCL;
+ cfg.freq = 400000;
+ _touch_instance.config(cfg);
+ _panel_instance.setTouch(&_touch_instance);
+ }
+
+ setPanel(&_panel_instance);
+ }
+};
+
+static LGFX *tft = nullptr;
+
#endif
-#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || \
- (ARCH_PORTDUINO && HAS_SCREEN != 0)
+#if defined(ST7701_CS) || defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
+ defined(RAK14014) || defined(HX8357_CS) || (ARCH_PORTDUINO && HAS_SCREEN != 0)
#include "SPILock.h"
#include "TFTDisplay.h"
#include
@@ -517,7 +643,7 @@ GpioPin *TFTDisplay::backlightEnable = NULL;
TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
{
- LOG_DEBUG("TFTDisplay!\n");
+ LOG_DEBUG("TFTDisplay!");
#ifdef TFT_BL
GpioPin *p = new GpioHwPin(TFT_BL);
@@ -586,7 +712,7 @@ void TFTDisplay::sendCommand(uint8_t com)
// handle display on/off directly
switch (com) {
case DISPLAYON: {
- // LOG_DEBUG("Display on\n");
+ // LOG_DEBUG("Display on");
backlightEnable->set(true);
#if ARCH_PORTDUINO
display(true);
@@ -610,7 +736,7 @@ void TFTDisplay::sendCommand(uint8_t com)
break;
}
case DISPLAYOFF: {
- // LOG_DEBUG("Display off\n");
+ // LOG_DEBUG("Display off");
backlightEnable->set(false);
#if ARCH_PORTDUINO
tft->clear();
@@ -646,14 +772,14 @@ void TFTDisplay::setDisplayBrightness(uint8_t _brightness)
// todo
#else
tft->setBrightness(_brightness);
- LOG_DEBUG("Brightness is set to value: %i \n", _brightness);
+ LOG_DEBUG("Brightness is set to value: %i ", _brightness);
#endif
}
void TFTDisplay::flipScreenVertically()
{
#if defined(T_WATCH_S3)
- LOG_DEBUG("Flip TFT vertically\n"); // T-Watch S3 right-handed orientation
+ LOG_DEBUG("Flip TFT vertically"); // T-Watch S3 right-handed orientation
tft->setRotation(0);
#endif
}
@@ -697,7 +823,7 @@ void TFTDisplay::setDetected(uint8_t detected)
bool TFTDisplay::connect()
{
concurrency::LockGuard g(spiLock);
- LOG_INFO("Doing TFT init\n");
+ LOG_INFO("Doing TFT init");
#ifdef RAK14014
tft = new TFT_eSPI;
#else
@@ -705,11 +831,10 @@ bool TFTDisplay::connect()
#endif
backlightEnable->set(true);
- LOG_INFO("Power to TFT Backlight\n");
+ LOG_INFO("Power to TFT Backlight");
#ifdef UNPHONE
unphone.backlight(true); // using unPhone library
- LOG_INFO("Power to TFT Backlight\n");
#endif
tft->init();
@@ -725,7 +850,7 @@ bool TFTDisplay::connect()
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)
+#elif defined(T_WATCH_S3) || defined(SENSECAP_INDICATOR)
tft->setRotation(2); // T-Watch S3 left-handed orientation
#else
tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
@@ -735,4 +860,4 @@ bool TFTDisplay::connect()
return true;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/graphics/images.h b/src/graphics/images.h
index ab3767a89..2b0854a33 100644
--- a/src/graphics/images.h
+++ b/src/graphics/images.h
@@ -20,8 +20,8 @@ const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03
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(USE_ST7789) || \
- defined(HX8357_CS) || ARCH_PORTDUINO) && \
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7701_CS) || defined(ST7735_CS) || \
+ defined(ST7789_CS) || defined(USE_ST7789) || defined(HX8357_CS) || ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff};
const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f};
diff --git a/src/graphics/img/icon.xbm b/src/graphics/img/icon.xbm
index f90cf4946..4e78ae8a1 100644
--- a/src/graphics/img/icon.xbm
+++ b/src/graphics/img/icon.xbm
@@ -1,4 +1,4 @@
-#ifndef HAS_USERPREFS_SPLASH
+#ifndef USERPREFS_HAS_SPLASH
#define icon_width 50
#define icon_height 28
static uint8_t icon_bits[] = {
diff --git a/src/input/ExpressLRSFiveWay.cpp b/src/input/ExpressLRSFiveWay.cpp
index c444800ba..56413bd55 100644
--- a/src/input/ExpressLRSFiveWay.cpp
+++ b/src/input/ExpressLRSFiveWay.cpp
@@ -1,5 +1,5 @@
-
#include "ExpressLRSFiveWay.h"
+#include "Throttle.h"
#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE
@@ -76,11 +76,10 @@ void ExpressLRSFiveWay::update(int *keyValue, bool *keyLongPressed)
*keyValue = NO_PRESS;
int newKey = readKey();
- uint32_t now = millis();
if (keyInProcess == NO_PRESS) {
// New key down
if (newKey != NO_PRESS) {
- keyDownStart = now;
+ keyDownStart = millis();
// DBGLN("down=%u", newKey);
}
} else {
@@ -88,7 +87,7 @@ void ExpressLRSFiveWay::update(int *keyValue, bool *keyLongPressed)
if (newKey == NO_PRESS) {
// DBGLN("up=%u", keyInProcess);
if (!isLongPressed) {
- if ((now - keyDownStart) > KEY_DEBOUNCE_MS) {
+ if (!Throttle::isWithinTimespanMs(keyDownStart, KEY_DEBOUNCE_MS)) {
*keyValue = keyInProcess;
*keyLongPressed = false;
}
@@ -101,7 +100,7 @@ void ExpressLRSFiveWay::update(int *keyValue, bool *keyLongPressed)
}
// else still pressing, waiting for long if not already signaled
else if (!isLongPressed) {
- if ((now - keyDownStart) > KEY_LONG_PRESS_MS) {
+ if (!Throttle::isWithinTimespanMs(keyDownStart, KEY_LONG_PRESS_MS)) {
*keyValue = keyInProcess;
*keyLongPressed = true;
isLongPressed = true;
@@ -234,7 +233,7 @@ void ExpressLRSFiveWay::sendAdhocPing()
// Contained as one method for easier remapping of buttons by user
void ExpressLRSFiveWay::shutdown()
{
- LOG_INFO("Shutdown from long press\n");
+ LOG_INFO("Shutdown from long press");
powerFSM.trigger(EVENT_PRESS);
screen->startAlert("Shutting down...");
// Don't set alerting = true. We don't want to auto-dismiss this alert.
diff --git a/src/input/InputBroker.h b/src/input/InputBroker.h
index 57c25af4b..db7524bb0 100644
--- a/src/input/InputBroker.h
+++ b/src/input/InputBroker.h
@@ -4,6 +4,22 @@
#define ANYKEY 0xFF
#define MATRIXKEY 0xFE
+#define INPUT_BROKER_MSG_BRIGHTNESS_UP 0x11
+#define INPUT_BROKER_MSG_BRIGHTNESS_DOWN 0x12
+#define INPUT_BROKER_MSG_REBOOT 0x90
+#define INPUT_BROKER_MSG_SHUTDOWN 0x9b
+#define INPUT_BROKER_MSG_GPS_TOGGLE 0x9e
+#define INPUT_BROKER_MSG_MUTE_TOGGLE 0xac
+#define INPUT_BROKER_MSG_SEND_PING 0xaf
+#define INPUT_BROKER_MSG_DISMISS_FRAME 0x8b
+#define INPUT_BROKER_MSG_LEFT 0xb4
+#define INPUT_BROKER_MSG_UP 0xb5
+#define INPUT_BROKER_MSG_DOWN 0xb6
+#define INPUT_BROKER_MSG_RIGHT 0xb7
+#define INPUT_BROKER_MSG_FN_SYMBOL_ON 0xf1
+#define INPUT_BROKER_MSG_FN_SYMBOL_OFF 0xf2
+#define INPUT_BROKER_MSG_BLUETOOTH_TOGGLE 0xAA
+
typedef struct _InputEvent {
const char *source;
char inputEvent;
diff --git a/src/input/LinuxInput.cpp b/src/input/LinuxInput.cpp
index 6194195ed..57a87b0ef 100644
--- a/src/input/LinuxInput.cpp
+++ b/src/input/LinuxInput.cpp
@@ -147,11 +147,11 @@ int32_t LinuxInput::runOnce()
case KEY_LEFT: // Left
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT;
break;
- e.kbchar = 0xb4;
+ e.kbchar = INPUT_BROKER_MSG_LEFT;
case KEY_RIGHT: // Right
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT;
break;
- e.kbchar = 0xb7;
+ e.kbchar = INPUT_BROKER_MSG_RIGHT;
case KEY_ENTER: // Enter
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
break;
diff --git a/src/input/MPR121Keyboard.cpp b/src/input/MPR121Keyboard.cpp
new file mode 100644
index 000000000..e1b32aa54
--- /dev/null
+++ b/src/input/MPR121Keyboard.cpp
@@ -0,0 +1,430 @@
+// Based on the BBQ10 Keyboard
+
+#include "MPR121Keyboard.h"
+#include "configuration.h"
+#include
+
+#define _MPR121_REG_KEY 0x5a
+
+#define _MPR121_REG_TOUCH_STATUS 0x00
+#define _MPR121_REG_ELECTRODE_FILTERED_DATA
+#define _MPR121_REG_BASELINE_VALUE 0x1E
+
+// Baseline filters
+#define _MPR121_REG_MAX_HALF_DELTA_RISING 0x2B
+#define _MPR121_REG_NOISE_HALF_DELTA_RISING 0x2C
+#define _MPR121_REG_NOISE_COUNT_LIMIT_RISING 0x2D
+#define _MPR121_REG_FILTER_DELAY_COUNT_RISING 0x2E
+#define _MPR121_REG_MAX_HALF_DELTA_FALLING 0x2F
+#define _MPR121_REG_NOISE_HALF_DELTA_FALLING 0x30
+#define _MPR121_REG_NOISE_COUNT_LIMIT_FALLING 0x31
+#define _MPR121_REG_FILTER_DELAY_COUNT_FALLING 0x32
+#define _MPR121_REG_NOISE_HALF_DELTA_TOUCHED 0x33
+#define _MPR121_REG_NOISE_COUNT_LIMIT_TOUCHED 0x34
+#define _MPR121_REG_FILTER_DELAY_COUNT_TOUCHED 0x35
+
+#define _MPR121_REG_TOUCH_THRESHOLD 0x41 // First input, +2 for subsequent
+#define _MPR121_REG_RELEASE_THRESHOLD 0x42 // First input, +2 for subsequent
+#define _MPR121_REG_DEBOUNCE 0x5B
+#define _MPR121_REG_CONFIG1 0x5C
+#define _MPR121_REG_CONFIG2 0x5D
+#define _MPR121_REG_ELECTRODE_CONFIG 0x5E
+#define _MPR121_REG_SOFT_RESET 0x80
+
+#define _KEY_MASK 0x0FFF // Key mask for the first 12 bits
+#define _NUM_KEYS 12
+
+#define ECR_CALIBRATION_TRACK_FROM_ZERO (0 << 6)
+#define ECR_CALIBRATION_LOCK (1 << 6)
+#define ECR_CALIBRATION_TRACK_FROM_PARTIAL_FILTER (2 << 6) // Recommended Typical Mode
+#define ECR_CALIBRATION_TRACK_FROM_FULL_FILTER (3 << 6)
+#define ECR_PROXIMITY_DETECTION_OFF (0 << 0) // Not using proximity detection
+#define ECR_TOUCH_DETECTION_12CH (12 << 0) // Using all 12 channels
+
+#define MPR121_NONE 0x00
+#define MPR121_REBOOT 0x90
+#define MPR121_LEFT 0xb4
+#define MPR121_UP 0xb5
+#define MPR121_DOWN 0xb6
+#define MPR121_RIGHT 0xb7
+#define MPR121_ESC 0x1b
+#define MPR121_BSP 0x08
+#define MPR121_SELECT 0x0d
+
+#define MPR121_FN_ON 0xf1
+#define MPR121_FN_OFF 0xf2
+
+#define LONG_PRESS_THRESHOLD 2000
+#define MULTI_TAP_THRESHOLD 2000
+
+uint8_t TapMod[12] = {1, 2, 1, 13, 7, 7, 7, 7, 7, 9, 7, 9}; // Num chars per key, Modulus for rotating through characters
+
+unsigned char MPR121_TapMap[12][13] = {{MPR121_BSP},
+ {'0', ' '},
+ {MPR121_SELECT},
+ {'1', '.', ',', '?', '!', ':', ';', '-', '_', '\\', '/', '(', ')'},
+ {'2', 'a', 'b', 'c', 'A', 'B', 'C'},
+ {'3', 'd', 'e', 'f', 'D', 'E', 'F'},
+ {'4', 'g', 'h', 'i', 'G', 'H', 'I'},
+ {'5', 'j', 'k', 'l', 'J', 'K', 'L'},
+ {'6', 'm', 'n', 'o', 'M', 'N', 'O'},
+ {'7', 'p', 'q', 'r', 's', 'P', 'Q', 'R', 'S'},
+ {'8', 't', 'u', 'v', 'T', 'U', 'V'},
+ {'9', 'w', 'x', 'y', 'z', 'W', 'X', 'Y', 'Z'}};
+
+unsigned char MPR121_LongPressMap[12] = {MPR121_ESC, ' ', MPR121_NONE, MPR121_NONE, MPR121_UP, MPR121_NONE,
+ MPR121_LEFT, MPR121_NONE, MPR121_RIGHT, MPR121_NONE, MPR121_DOWN, MPR121_NONE};
+
+// Translation map from left to right, top to bottom layout to a more convenient layout to manufacture, matching the
+// https://www.amazon.com.au/Capacitive-Sensitive-Sensitivity-Replacement-Traditional/dp/B0CTJD5KW9/ref=pd_ci_mcx_mh_mcx_views_0_title?th=1
+/*uint8_t MPR121_KeyMap[12] = {
+ 9, 6, 3, 0,
+ 10, 7, 4, 1,
+ 11, 8, 5, 2
+};*/
+// Rotated Layout
+uint8_t MPR121_KeyMap[12] = {2, 5, 8, 11, 1, 4, 7, 10, 0, 3, 6, 9};
+
+MPR121Keyboard::MPR121Keyboard() : m_wire(nullptr), m_addr(0), readCallback(nullptr), writeCallback(nullptr)
+{
+ // LOG_DEBUG("MPR121 @ %02x\n", m_addr);
+ state = Init;
+ last_key = -1;
+ last_tap = 0L;
+ char_idx = 0;
+ queue = "";
+}
+
+void MPR121Keyboard::begin(uint8_t addr, TwoWire *wire)
+{
+ m_addr = addr;
+ m_wire = wire;
+
+ m_wire->begin();
+
+ reset();
+}
+
+void MPR121Keyboard::begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr)
+{
+ m_addr = addr;
+ m_wire = nullptr;
+ writeCallback = w;
+ readCallback = r;
+ reset();
+}
+
+void MPR121Keyboard::reset()
+{
+ LOG_DEBUG("MPR121 Resetting...");
+ // Trigger a MPR121 Soft Reset
+ if (m_wire) {
+ m_wire->beginTransmission(m_addr);
+ m_wire->write(_MPR121_REG_SOFT_RESET);
+ m_wire->endTransmission();
+ }
+ if (writeCallback) {
+ uint8_t data = 0;
+ writeCallback(m_addr, _MPR121_REG_SOFT_RESET, &data, 0);
+ }
+ delay(100);
+ // Reset Electrode Configuration to 0x00, Stop Mode
+ writeRegister(_MPR121_REG_ELECTRODE_CONFIG, 0x00);
+ delay(100);
+
+ LOG_DEBUG("MPR121 Configuring");
+ // Set touch release thresholds
+ for (uint8_t i = 0; i < 12; i++) {
+ // Set touch threshold
+ writeRegister(_MPR121_REG_TOUCH_THRESHOLD + (i * 2), 15);
+ delay(20);
+ // Set release threshold
+ writeRegister(_MPR121_REG_RELEASE_THRESHOLD + (i * 2), 7);
+ delay(20);
+ }
+ // Configure filtering and baseline registers
+ writeRegister(_MPR121_REG_MAX_HALF_DELTA_RISING, 0x01);
+ delay(20);
+ writeRegister(_MPR121_REG_MAX_HALF_DELTA_FALLING, 0x01);
+ delay(20);
+ writeRegister(_MPR121_REG_NOISE_HALF_DELTA_RISING, 0x01);
+ delay(20);
+ writeRegister(_MPR121_REG_NOISE_HALF_DELTA_FALLING, 0x05);
+ delay(20);
+ writeRegister(_MPR121_REG_NOISE_HALF_DELTA_TOUCHED, 0x00);
+ delay(20);
+ writeRegister(_MPR121_REG_NOISE_COUNT_LIMIT_RISING, 0x0e);
+ delay(20);
+ writeRegister(_MPR121_REG_NOISE_COUNT_LIMIT_FALLING, 0x01);
+ delay(20);
+ writeRegister(_MPR121_REG_NOISE_COUNT_LIMIT_TOUCHED, 0x00);
+ delay(20);
+ writeRegister(_MPR121_REG_FILTER_DELAY_COUNT_RISING, 0x00);
+ delay(20);
+ writeRegister(_MPR121_REG_FILTER_DELAY_COUNT_FALLING, 0x00);
+ delay(20);
+ writeRegister(_MPR121_REG_FILTER_DELAY_COUNT_TOUCHED, 0x00);
+ delay(20);
+ // Set Debounce to 0x02
+ writeRegister(_MPR121_REG_DEBOUNCE, 0x00);
+ delay(20);
+ // Set Filter1 itterations and discharge current 6x and 16uA respectively (0x10)
+ writeRegister(_MPR121_REG_CONFIG1, 0x10);
+ delay(20);
+ // Set CDT to 0.5us, Filter2 itterations to 4x, and Sample interval = 0 (0x20)
+ writeRegister(_MPR121_REG_CONFIG2, 0x20);
+ delay(20);
+ // Enter run mode by Seting partial filter calibration tracking, disable proximity detection, enable 12 channels
+ writeRegister(_MPR121_REG_ELECTRODE_CONFIG,
+ ECR_CALIBRATION_TRACK_FROM_PARTIAL_FILTER | ECR_PROXIMITY_DETECTION_OFF | ECR_TOUCH_DETECTION_12CH);
+ delay(100);
+ LOG_DEBUG("MPR121 Running");
+ state = Idle;
+}
+
+void MPR121Keyboard::attachInterrupt(uint8_t pin, void (*func)(void)) const
+{
+ pinMode(pin, INPUT_PULLUP);
+ ::attachInterrupt(digitalPinToInterrupt(pin), func, RISING);
+}
+
+void MPR121Keyboard::detachInterrupt(uint8_t pin) const
+{
+ ::detachInterrupt(pin);
+}
+
+uint8_t MPR121Keyboard::status() const
+{
+ return readRegister16(_MPR121_REG_KEY);
+}
+
+uint8_t MPR121Keyboard::keyCount() const
+{
+ // Read the key register
+ uint16_t keyRegister = readRegister16(_MPR121_REG_KEY);
+ return keyCount(keyRegister);
+}
+
+uint8_t MPR121Keyboard::keyCount(uint16_t value) const
+{
+ // Mask the first 12 bits
+ uint16_t buttonState = value & _KEY_MASK;
+
+ // Count how many bits are set to 1 (i.e., how many buttons are pressed)
+ uint8_t numButtonsPressed = 0;
+ for (uint8_t i = 0; i < 12; ++i) {
+ if (buttonState & (1 << i)) {
+ numButtonsPressed++;
+ }
+ }
+
+ return numButtonsPressed;
+}
+
+bool MPR121Keyboard::hasEvent()
+{
+ return queue.length() > 0;
+}
+
+void MPR121Keyboard::queueEvent(char next)
+{
+ if (next == MPR121_NONE) {
+ return;
+ }
+ queue.concat(next);
+}
+
+char MPR121Keyboard::dequeueEvent()
+{
+ if (queue.length() < 1) {
+ return MPR121_NONE;
+ }
+ char next = queue.charAt(0);
+ queue.remove(0, 1);
+ return next;
+}
+
+void MPR121Keyboard::trigger()
+{
+ // Intended to fire in response to an interrupt from the MPR121 or a longpress callback
+ // Only functional if not in Init state
+ if (state != Init) {
+ // Read the key register
+ uint16_t keyRegister = readRegister16(_MPR121_REG_KEY);
+ uint8_t keysPressed = keyCount(keyRegister);
+ if (keysPressed == 0) {
+ // No buttons pressed
+ if (state == Held)
+ released();
+ state = Idle;
+ return;
+ }
+ if (keysPressed == 1) {
+ // No buttons pressed
+ if (state == Held || state == HeldLong)
+ held(keyRegister);
+ if (state == Idle)
+ pressed(keyRegister);
+ return;
+ }
+ if (keysPressed > 1) {
+ // Multipress
+ state = Busy;
+ return;
+ }
+ } else {
+ reset();
+ }
+}
+
+void MPR121Keyboard::pressed(uint16_t keyRegister)
+{
+ if (state == Init || state == Busy) {
+ return;
+ }
+ if (keyCount(keyRegister) != 1) {
+ LOG_DEBUG("Multipress");
+ return;
+ } else {
+ LOG_DEBUG("Pressed");
+ }
+ uint16_t buttonState = keyRegister & _KEY_MASK;
+ uint8_t next_pin = 0;
+ for (uint8_t i = 0; i < 12; ++i) {
+ if (buttonState & (1 << i)) {
+ next_pin = i;
+ }
+ }
+ uint8_t next_key = MPR121_KeyMap[next_pin];
+ LOG_DEBUG("MPR121 Pin: %i Key: %i", next_pin, next_key);
+ uint32_t now = millis();
+ int32_t tap_interval = now - last_tap;
+ if (tap_interval < 0) {
+ // long running, millis has overflowed.
+ last_tap = 0;
+ state = Busy;
+ return;
+ }
+ if (next_key != last_key || tap_interval > MULTI_TAP_THRESHOLD) {
+ char_idx = 0;
+ } else {
+ char_idx += 1;
+ }
+ last_key = next_key;
+ last_tap = now;
+ state = Held;
+ return;
+}
+
+void MPR121Keyboard::held(uint16_t keyRegister)
+{
+ if (state == Init || state == Busy) {
+ return;
+ }
+ if (keyCount(keyRegister) != 1) {
+ return;
+ }
+ LOG_DEBUG("Held");
+ uint16_t buttonState = keyRegister & _KEY_MASK;
+ uint8_t next_key = 0;
+ for (uint8_t i = 0; i < 12; ++i) {
+ if (buttonState & (1 << i)) {
+ next_key = MPR121_KeyMap[i];
+ }
+ }
+ uint32_t now = millis();
+ int32_t held_interval = now - last_tap;
+ if (held_interval < 0 || next_key != last_key) {
+ // long running, millis has overflowed, or a key has been switched quickly...
+ last_tap = 0;
+ state = Busy;
+ return;
+ }
+ if (held_interval > LONG_PRESS_THRESHOLD) {
+ // Set state to heldlong, send a longpress, and reset the timer...
+ state = HeldLong; // heldlong will allow this function to still fire, but prevent a "release"
+ queueEvent(MPR121_LongPressMap[last_key]);
+ last_tap = now;
+ LOG_DEBUG("Long Press Key: %i Map: %i", last_key, MPR121_LongPressMap[last_key]);
+ }
+ return;
+}
+
+void MPR121Keyboard::released()
+{
+ if (state != Held) {
+ return;
+ }
+ // would clear longpress callback... later.
+ if (last_key < 0 || last_key > _NUM_KEYS) { // reset to idle if last_key out of bounds
+ last_key = -1;
+ state = Idle;
+ return;
+ }
+ LOG_DEBUG("Released");
+ if (char_idx > 0 && TapMod[last_key] > 1) {
+ queueEvent(MPR121_BSP);
+ LOG_DEBUG("Multi Press, Backspace");
+ }
+ queueEvent(MPR121_TapMap[last_key][(char_idx % TapMod[last_key])]);
+ LOG_DEBUG("Key Press: %i Index:%i if %i Map: %i", last_key, char_idx, TapMod[last_key],
+ MPR121_TapMap[last_key][(char_idx % TapMod[last_key])]);
+}
+
+uint8_t MPR121Keyboard::readRegister8(uint8_t reg) const
+{
+ if (m_wire) {
+ m_wire->beginTransmission(m_addr);
+ m_wire->write(reg);
+ m_wire->endTransmission();
+
+ m_wire->requestFrom(m_addr, (uint8_t)1);
+ if (m_wire->available() < 1)
+ return 0;
+
+ return m_wire->read();
+ }
+ if (readCallback) {
+ uint8_t data;
+ readCallback(m_addr, reg, &data, 1);
+ return data;
+ }
+ return 0;
+}
+
+uint16_t MPR121Keyboard::readRegister16(uint8_t reg) const
+{
+ uint8_t data[2] = {0};
+ // uint8_t low = 0, high = 0;
+ if (m_wire) {
+ m_wire->beginTransmission(m_addr);
+ m_wire->write(reg);
+ m_wire->endTransmission();
+
+ m_wire->requestFrom(m_addr, (uint8_t)2);
+ if (m_wire->available() < 2)
+ return 0;
+ data[0] = m_wire->read();
+ data[1] = m_wire->read();
+ }
+ if (readCallback) {
+ readCallback(m_addr, reg, data, 2);
+ }
+ return (data[1] << 8) | data[0];
+}
+
+void MPR121Keyboard::writeRegister(uint8_t reg, uint8_t value)
+{
+ uint8_t data[2];
+ data[0] = reg;
+ data[1] = value;
+
+ if (m_wire) {
+ m_wire->beginTransmission(m_addr);
+ m_wire->write(data, sizeof(uint8_t) * 2);
+ m_wire->endTransmission();
+ }
+ if (writeCallback) {
+ writeCallback(m_addr, data[0], &(data[1]), 1);
+ }
+}
diff --git a/src/input/MPR121Keyboard.h b/src/input/MPR121Keyboard.h
new file mode 100644
index 000000000..6349750ce
--- /dev/null
+++ b/src/input/MPR121Keyboard.h
@@ -0,0 +1,56 @@
+// Based on the BBQ10 Keyboard
+
+#include "concurrency/NotifiedWorkerThread.h"
+#include "configuration.h"
+#include
+#include
+
+class MPR121Keyboard
+{
+ public:
+ typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len);
+
+ enum MPR121States { Init = 0, Idle, Held, HeldLong, Busy };
+
+ MPR121States state;
+
+ int8_t last_key;
+ uint32_t last_tap;
+ uint8_t char_idx;
+
+ String queue;
+
+ MPR121Keyboard();
+
+ void begin(uint8_t addr = MPR121_KB_ADDR, TwoWire *wire = &Wire);
+
+ void begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr = MPR121_KB_ADDR);
+
+ void reset(void);
+
+ void attachInterrupt(uint8_t pin, void (*func)(void)) const;
+ void detachInterrupt(uint8_t pin) const;
+
+ void trigger(void);
+ void pressed(uint16_t value);
+ void held(uint16_t value);
+ void released(void);
+
+ uint8_t status(void) const;
+ uint8_t keyCount(void) const;
+ uint8_t keyCount(uint16_t value) const;
+
+ bool hasEvent(void);
+ char dequeueEvent(void);
+ void queueEvent(char);
+
+ uint8_t readRegister8(uint8_t reg) const;
+ uint16_t readRegister16(uint8_t reg) const;
+ void writeRegister(uint8_t reg, uint8_t value);
+
+ private:
+ TwoWire *m_wire;
+ uint8_t m_addr;
+ i2c_com_fptr_t readCallback;
+ i2c_com_fptr_t writeCallback;
+};
\ No newline at end of file
diff --git a/src/input/RotaryEncoderInterruptBase.cpp b/src/input/RotaryEncoderInterruptBase.cpp
index 0b8e8325d..785d98ebe 100644
--- a/src/input/RotaryEncoderInterruptBase.cpp
+++ b/src/input/RotaryEncoderInterruptBase.cpp
@@ -28,7 +28,7 @@ void RotaryEncoderInterruptBase::init(
this->rotaryLevelA = digitalRead(this->_pinA);
this->rotaryLevelB = digitalRead(this->_pinB);
- LOG_INFO("Rotary initialized (%d, %d, %d)\n", this->_pinA, this->_pinB, pinPress);
+ LOG_INFO("Rotary initialized (%d, %d, %d)", this->_pinA, this->_pinB, pinPress);
}
int32_t RotaryEncoderInterruptBase::runOnce()
@@ -38,13 +38,13 @@ int32_t RotaryEncoderInterruptBase::runOnce()
e.source = this->_originName;
if (this->action == ROTARY_ACTION_PRESSED) {
- LOG_DEBUG("Rotary event Press\n");
+ LOG_DEBUG("Rotary event Press");
e.inputEvent = this->_eventPressed;
} else if (this->action == ROTARY_ACTION_CW) {
- LOG_DEBUG("Rotary event CW\n");
+ LOG_DEBUG("Rotary event CW");
e.inputEvent = this->_eventCw;
} else if (this->action == ROTARY_ACTION_CCW) {
- LOG_DEBUG("Rotary event CCW\n");
+ LOG_DEBUG("Rotary event CCW");
e.inputEvent = this->_eventCcw;
}
diff --git a/src/input/ScanAndSelect.cpp b/src/input/ScanAndSelect.cpp
index d693d768c..e1b39edf5 100644
--- a/src/input/ScanAndSelect.cpp
+++ b/src/input/ScanAndSelect.cpp
@@ -6,6 +6,7 @@
#include "ScanAndSelect.h"
#include "modules/CannedMessageModule.h"
+#include
// Config
static const char name[] = "scanAndSelect"; // should match "allow input source" string
@@ -46,7 +47,7 @@ bool ScanAndSelectInput::init()
// Connect our class to the canned message module
inputBroker->registerSource(this);
- LOG_INFO("Initialized 'Scan and Select' input for Canned Messages, using pin %d\n", pin);
+ LOG_INFO("Initialized 'Scan and Select' input for Canned Messages, using pin %d", pin);
return true; // Init succeded
}
@@ -75,7 +76,7 @@ int32_t ScanAndSelectInput::runOnce()
else {
// Duration enough for long press
// Long press not yet fired (prevent repeat firing while held)
- if (!longPressFired && now - downSinceMs > durationLongMs) {
+ if (!longPressFired && Throttle::isWithinTimespanMs(downSinceMs, durationLongMs)) {
longPressFired = true;
longPress();
}
@@ -91,7 +92,7 @@ int32_t ScanAndSelectInput::runOnce()
// Long press event didn't already fire
if (held && !longPressFired) {
// Duration enough for short press
- if (now - downSinceMs > durationShortMs) {
+ if (!Throttle::isWithinTimespanMs(downSinceMs, durationShortMs)) {
shortPress();
}
}
diff --git a/src/input/SerialKeyboard.cpp b/src/input/SerialKeyboard.cpp
index fa3eb2528..8d0730418 100644
--- a/src/input/SerialKeyboard.cpp
+++ b/src/input/SerialKeyboard.cpp
@@ -1,5 +1,6 @@
#include "SerialKeyboard.h"
#include "configuration.h"
+#include
#ifdef INPUTBROKER_SERIAL_TYPE
#define CANNED_MESSAGE_MODULE_ENABLE 1 // in case it's not set in the variant file
@@ -51,7 +52,7 @@ int32_t SerialKeyboard::runOnce()
digitalWrite(KB_LOAD, HIGH);
digitalWrite(KB_CLK, LOW);
prevKeys = 0b1111111111111111;
- LOG_DEBUG("Serial Keyboard setup\n");
+ LOG_DEBUG("Serial Keyboard setup");
}
if (INPUTBROKER_SERIAL_TYPE == 1) { // Chatter V1.0 & V2.0 keypads
@@ -73,7 +74,7 @@ int32_t SerialKeyboard::runOnce()
// Serial.print ("X");
// Serial.println (shiftRegister2, BIN);
- if (millis() - lastPressTime > 500) {
+ if (!Throttle::isWithinTimespanMs(lastPressTime, 500)) {
quickPress = 0;
}
@@ -87,7 +88,7 @@ int32_t SerialKeyboard::runOnce()
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP;
} else if (!(shiftRegister2 & (1 << 2))) {
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT;
- e.kbchar = 0xb7;
+ e.kbchar = INPUT_BROKER_MSG_RIGHT;
} else if (!(shiftRegister2 & (1 << 1))) {
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
} else if (!(shiftRegister2 & (1 << 0))) {
diff --git a/src/input/TouchScreenBase.cpp b/src/input/TouchScreenBase.cpp
index 2f361ac4c..03618b338 100644
--- a/src/input/TouchScreenBase.cpp
+++ b/src/input/TouchScreenBase.cpp
@@ -23,7 +23,7 @@ TouchScreenBase::TouchScreenBase(const char *name, uint16_t width, uint16_t heig
void TouchScreenBase::init(bool hasTouch)
{
if (hasTouch) {
- LOG_INFO("TouchScreen initialized %d %d\n", TOUCH_THRESHOLD_X, TOUCH_THRESHOLD_Y);
+ LOG_INFO("TouchScreen initialized %d %d", TOUCH_THRESHOLD_X, TOUCH_THRESHOLD_Y);
this->setInterval(100);
} else {
disable();
@@ -68,20 +68,20 @@ int32_t TouchScreenBase::runOnce()
if (adx > ady && adx > TOUCH_THRESHOLD_X) {
if (0 > dx) { // swipe right to left
e.touchEvent = static_cast(TOUCH_ACTION_LEFT);
- LOG_DEBUG("action SWIPE: right to left\n");
+ LOG_DEBUG("action SWIPE: right to left");
} else { // swipe left to right
e.touchEvent = static_cast(TOUCH_ACTION_RIGHT);
- LOG_DEBUG("action SWIPE: left to right\n");
+ LOG_DEBUG("action SWIPE: left to right");
}
}
// swipe vertical
else if (ady > adx && ady > TOUCH_THRESHOLD_Y) {
if (0 > dy) { // swipe bottom to top
e.touchEvent = static_cast(TOUCH_ACTION_UP);
- LOG_DEBUG("action SWIPE: bottom to top\n");
+ LOG_DEBUG("action SWIPE: bottom to top");
} else { // swipe top to bottom
e.touchEvent = static_cast(TOUCH_ACTION_DOWN);
- LOG_DEBUG("action SWIPE: top to bottom\n");
+ LOG_DEBUG("action SWIPE: top to bottom");
}
}
// tap
@@ -90,7 +90,7 @@ int32_t TouchScreenBase::runOnce()
if (_tapped) {
_tapped = false;
e.touchEvent = static_cast(TOUCH_ACTION_DOUBLE_TAP);
- LOG_DEBUG("action DOUBLE TAP(%d/%d)\n", x, y);
+ LOG_DEBUG("action DOUBLE TAP(%d/%d)", x, y);
} else {
_tapped = true;
}
@@ -106,7 +106,7 @@ int32_t TouchScreenBase::runOnce()
if (_tapped && (time_t(millis()) - _start) > TIME_LONG_PRESS - 50) {
_tapped = false;
e.touchEvent = static_cast(TOUCH_ACTION_TAP);
- LOG_DEBUG("action TAP(%d/%d)\n", _last_x, _last_y);
+ LOG_DEBUG("action TAP(%d/%d)", _last_x, _last_y);
}
// fire LONG_PRESS event without the need for release
@@ -114,7 +114,7 @@ int32_t TouchScreenBase::runOnce()
// tricky: prevent reoccurring events and another touch event when releasing
_start = millis() + 30000;
e.touchEvent = static_cast(TOUCH_ACTION_LONG_PRESS);
- LOG_DEBUG("action LONG PRESS(%d/%d)\n", _last_x, _last_y);
+ LOG_DEBUG("action LONG PRESS(%d/%d)", _last_x, _last_y);
}
if (e.touchEvent != TOUCH_ACTION_NONE) {
diff --git a/src/input/TrackballInterruptBase.cpp b/src/input/TrackballInterruptBase.cpp
index 71cd130cc..e35da3622 100644
--- a/src/input/TrackballInterruptBase.cpp
+++ b/src/input/TrackballInterruptBase.cpp
@@ -30,7 +30,7 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
attachInterrupt(this->_pinLeft, onIntLeft, RISING);
attachInterrupt(this->_pinRight, onIntRight, RISING);
- LOG_DEBUG("Trackball GPIO initialized (%d, %d, %d, %d, %d)\n", this->_pinUp, this->_pinDown, this->_pinLeft, this->_pinRight,
+ LOG_DEBUG("Trackball GPIO initialized (%d, %d, %d, %d, %d)", this->_pinUp, this->_pinDown, this->_pinLeft, this->_pinRight,
pinPress);
this->setInterval(100);
@@ -42,19 +42,19 @@ int32_t TrackballInterruptBase::runOnce()
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
if (this->action == TB_ACTION_PRESSED) {
- // LOG_DEBUG("Trackball event Press\n");
+ // LOG_DEBUG("Trackball event Press");
e.inputEvent = this->_eventPressed;
} else if (this->action == TB_ACTION_UP) {
- // LOG_DEBUG("Trackball event UP\n");
+ // LOG_DEBUG("Trackball event UP");
e.inputEvent = this->_eventUp;
} else if (this->action == TB_ACTION_DOWN) {
- // LOG_DEBUG("Trackball event DOWN\n");
+ // LOG_DEBUG("Trackball event DOWN");
e.inputEvent = this->_eventDown;
} else if (this->action == TB_ACTION_LEFT) {
- // LOG_DEBUG("Trackball event LEFT\n");
+ // LOG_DEBUG("Trackball event LEFT");
e.inputEvent = this->_eventLeft;
} else if (this->action == TB_ACTION_RIGHT) {
- // LOG_DEBUG("Trackball event RIGHT\n");
+ // LOG_DEBUG("Trackball event RIGHT");
e.inputEvent = this->_eventRight;
}
diff --git a/src/input/UpDownInterruptBase.cpp b/src/input/UpDownInterruptBase.cpp
index b1f83c56b..979489c57 100644
--- a/src/input/UpDownInterruptBase.cpp
+++ b/src/input/UpDownInterruptBase.cpp
@@ -23,7 +23,7 @@ void UpDownInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinPress,
attachInterrupt(this->_pinDown, onIntDown, RISING);
attachInterrupt(this->_pinUp, onIntUp, RISING);
- LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)\n", this->_pinUp, this->_pinDown, pinPress);
+ LOG_DEBUG("Up/down/press GPIO initialized (%d, %d, %d)", this->_pinUp, this->_pinDown, pinPress);
this->setInterval(100);
}
@@ -34,13 +34,13 @@ int32_t UpDownInterruptBase::runOnce()
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
if (this->action == UPDOWN_ACTION_PRESSED) {
- LOG_DEBUG("GPIO event Press\n");
+ LOG_DEBUG("GPIO event Press");
e.inputEvent = this->_eventPressed;
} else if (this->action == UPDOWN_ACTION_UP) {
- LOG_DEBUG("GPIO event Up\n");
+ LOG_DEBUG("GPIO event Up");
e.inputEvent = this->_eventUp;
} else if (this->action == UPDOWN_ACTION_DOWN) {
- LOG_DEBUG("GPIO event Down\n");
+ LOG_DEBUG("GPIO event Down");
e.inputEvent = this->_eventDown;
}
diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp
index 1bff49475..c1f35ba3c 100644
--- a/src/input/cardKbI2cImpl.cpp
+++ b/src/input/cardKbI2cImpl.cpp
@@ -9,14 +9,14 @@ CardKbI2cImpl::CardKbI2cImpl() : KbI2cBase("cardKB") {}
void CardKbI2cImpl::init()
{
-#ifndef ARCH_PORTDUINO
+#if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(I2C_NO_RESCAN)
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;
+ LOG_DEBUG("Rescanning for I2C keyboard");
+ uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR};
+ uint8_t i2caddr_asize = 4;
auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire());
-#if defined(I2C_SDA1)
+#if WIRE_INTERFACES_COUNT == 2
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize);
#endif
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize);
@@ -39,12 +39,17 @@ void CardKbI2cImpl::init()
// assign an arbitrary value to distinguish from other models
kb_model = 0x11;
break;
+ case ScanI2C::DeviceType::MPR121KB:
+ // assign an arbitrary value to distinguish from other models
+ kb_model = 0x37;
+ 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);
+ LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00", kb_info.type);
kb_model = 0x00;
}
}
+ LOG_DEBUG("Keyboard Type: 0x%02x Model: 0x%02x Address: 0x%02x", kb_info.type, kb_model, cardkb_found.address);
if (cardkb_found.address == 0x00) {
disable();
return;
@@ -57,4 +62,4 @@ void CardKbI2cImpl::init()
}
#endif
inputBroker->registerSource(this);
-}
+}
\ No newline at end of file
diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp
index 024b16b9e..d0f36c386 100644
--- a/src/input/kbI2cBase.cpp
+++ b/src/input/kbI2cBase.cpp
@@ -33,22 +33,28 @@ int32_t KbI2cBase::runOnce()
if (!i2cBus) {
switch (cardkb_found.port) {
case ScanI2C::WIRE1:
-#ifdef I2C_SDA1
- LOG_DEBUG("Using I2C Bus 1 (the second one)\n");
+#if WIRE_INTERFACES_COUNT == 2
+ LOG_DEBUG("Using I2C Bus 1 (the second one)");
i2cBus = &Wire1;
if (cardkb_found.address == BBQ10_KB_ADDR) {
Q10keyboard.begin(BBQ10_KB_ADDR, &Wire1);
Q10keyboard.setBacklight(0);
}
+ if (cardkb_found.address == MPR121_KB_ADDR) {
+ MPRkeyboard.begin(MPR121_KB_ADDR, &Wire1);
+ }
break;
#endif
case ScanI2C::WIRE:
- LOG_DEBUG("Using I2C Bus 0 (the first one)\n");
+ LOG_DEBUG("Using I2C Bus 0 (the first one)");
i2cBus = &Wire;
if (cardkb_found.address == BBQ10_KB_ADDR) {
Q10keyboard.begin(BBQ10_KB_ADDR, &Wire);
Q10keyboard.setBacklight(0);
}
+ if (cardkb_found.address == MPR121_KB_ADDR) {
+ MPRkeyboard.begin(MPR121_KB_ADDR, &Wire);
+ }
break;
case ScanI2C::NO_I2C:
default:
@@ -94,7 +100,7 @@ int32_t KbI2cBase::runOnce()
case 'e': // sym e
if (is_sym) {
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP;
- e.kbchar = 0xb5;
+ e.kbchar = INPUT_BROKER_MSG_UP;
is_sym = false; // reset sym state after second keypress
} else {
e.inputEvent = ANYKEY;
@@ -104,7 +110,7 @@ int32_t KbI2cBase::runOnce()
case 'x': // sym x
if (is_sym) {
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN;
- e.kbchar = 0xb6;
+ e.kbchar = INPUT_BROKER_MSG_DOWN;
is_sym = false; // reset sym state after second keypress
} else {
e.inputEvent = ANYKEY;
@@ -134,8 +140,8 @@ int32_t KbI2cBase::runOnce()
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
+ e.kbchar = is_sym ? INPUT_BROKER_MSG_FN_SYMBOL_ON // send 0xf1 to tell CannedMessages to display that
+ : INPUT_BROKER_MSG_FN_SYMBOL_OFF; // 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;
@@ -157,6 +163,69 @@ int32_t KbI2cBase::runOnce()
}
break;
}
+ case 0x37: { // MPR121
+ MPRkeyboard.trigger();
+ InputEvent e;
+
+ while (MPRkeyboard.hasEvent()) {
+ char nextEvent = MPRkeyboard.dequeueEvent();
+ e.inputEvent = ANYKEY;
+ e.kbchar = 0x00;
+ e.source = this->_originName;
+ switch (nextEvent) {
+ case 0x00: // MPR121_NONE
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
+ e.kbchar = 0x00;
+ break;
+ case 0x90: // MPR121_REBOOT
+ e.inputEvent = ANYKEY;
+ e.kbchar = INPUT_BROKER_MSG_REBOOT;
+ break;
+ case 0xb4: // MPR121_LEFT
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT;
+ e.kbchar = 0x00;
+ break;
+ case 0xb5: // MPR121_UP
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP;
+ e.kbchar = 0x00;
+ break;
+ case 0xb6: // MPR121_DOWN
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN;
+ e.kbchar = 0x00;
+ break;
+ case 0xb7: // MPR121_RIGHT
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT;
+ e.kbchar = 0x00;
+ break;
+ case 0x1b: // MPR121_ESC
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL;
+ e.kbchar = 0x1b;
+ break;
+ case 0x08: // MPR121_BSP
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK;
+ e.kbchar = 0x08;
+ break;
+ case 0x0d: // MPR121_SELECT
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
+ e.kbchar = 0x0d;
+ break;
+ default:
+ if (nextEvent > 127) {
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
+ e.kbchar = 0x00;
+ break;
+ }
+ e.inputEvent = ANYKEY;
+ e.kbchar = nextEvent;
+ break;
+ }
+ if (e.inputEvent != meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE) {
+ LOG_DEBUG("MP121 Notifying: %i Char: %i", e.inputEvent, e.kbchar);
+ this->notifyObservers(&e);
+ }
+ }
+ break;
+ }
case 0x02: {
// RAK14004
uint8_t rDataBuf[8] = {0};
@@ -171,7 +240,7 @@ int32_t KbI2cBase::runOnce()
}
}
if (PrintDataBuf != 0) {
- LOG_DEBUG("RAK14004 key 0x%x pressed\n", PrintDataBuf);
+ LOG_DEBUG("RAK14004 key 0x%x pressed", PrintDataBuf);
InputEvent e;
e.inputEvent = MATRIXKEY;
e.source = this->_originName;
@@ -214,7 +283,7 @@ int32_t KbI2cBase::runOnce()
if (is_sym) {
is_sym = false;
e.inputEvent = ANYKEY;
- e.kbchar = 0xac; // mute notifications
+ e.kbchar = INPUT_BROKER_MSG_MUTE_TOGGLE; // mute notifications
} else {
e.inputEvent = ANYKEY;
e.kbchar = c;
@@ -224,7 +293,7 @@ int32_t KbI2cBase::runOnce()
if (is_sym) {
is_sym = false;
e.inputEvent = ANYKEY;
- e.kbchar = 0x11; // Increase Brightness code
+ e.kbchar = INPUT_BROKER_MSG_BRIGHTNESS_UP; // Increase Brightness code
} else {
e.inputEvent = ANYKEY;
e.kbchar = c;
@@ -234,7 +303,7 @@ int32_t KbI2cBase::runOnce()
if (is_sym) {
is_sym = false;
e.inputEvent = ANYKEY;
- e.kbchar = 0x12; // Decrease Brightness code
+ e.kbchar = INPUT_BROKER_MSG_BRIGHTNESS_DOWN; // Decrease Brightness code
} else {
e.inputEvent = ANYKEY;
e.kbchar = c;
@@ -244,7 +313,7 @@ int32_t KbI2cBase::runOnce()
if (is_sym) {
is_sym = false;
e.inputEvent = ANYKEY;
- e.kbchar = 0xaf; // (fn + space)
+ e.kbchar = INPUT_BROKER_MSG_SEND_PING; // (fn + space)
} else {
e.inputEvent = ANYKEY;
e.kbchar = c;
@@ -254,7 +323,7 @@ int32_t KbI2cBase::runOnce()
if (is_sym) {
is_sym = false;
e.inputEvent = ANYKEY;
- e.kbchar = 0x9e;
+ e.kbchar = INPUT_BROKER_MSG_GPS_TOGGLE;
} else {
e.inputEvent = ANYKEY;
e.kbchar = c;
@@ -269,32 +338,35 @@ int32_t KbI2cBase::runOnce()
break;
case 0xb5: // Up
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP;
- e.kbchar = 0xb5;
+ e.kbchar = INPUT_BROKER_MSG_UP;
break;
case 0xb6: // Down
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN;
- e.kbchar = 0xb6;
+ e.kbchar = INPUT_BROKER_MSG_DOWN;
break;
case 0xb4: // Left
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT;
- e.kbchar = 0xb4;
+ e.kbchar = INPUT_BROKER_MSG_LEFT;
break;
case 0xb7: // Right
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT;
- e.kbchar = 0xb7;
+ e.kbchar = INPUT_BROKER_MSG_RIGHT;
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
+ e.kbchar = is_sym ? INPUT_BROKER_MSG_FN_SYMBOL_ON // send 0xf1 to tell CannedMessages to display that the
+ : INPUT_BROKER_MSG_FN_SYMBOL_OFF; // modifier key is active
break;
- case 0x90: // fn+r
+ case 0x90: // fn+r INPUT_BROKER_MSG_REBOOT
case 0x91: // fn+t
- case 0x9b: // fn+s
- case 0xac: // fn+m
- case 0x9e: // fn+g
- case 0xaf: // fn+space
+ case 0x9b: // fn+s INPUT_BROKER_MSG_SHUTDOWN
+ case 0xac: // fn+m INPUT_BROKER_MSG_MUTE_TOGGLE
+ case 0x9e: // fn+g INPUT_BROKER_MSG_GPS_TOGGLE
+ case 0xaf: // fn+space INPUT_BROKER_MSG_SEND_PING
+ case 0x8b: // fn+del INPUT_BROKEN_MSG_DISMISS_FRAME
+ case 0xAA: // fn+b INPUT_BROKER_MSG_BLUETOOTH_TOGGLE
// just pass those unmodified
e.inputEvent = ANYKEY;
e.kbchar = c;
@@ -323,7 +395,7 @@ int32_t KbI2cBase::runOnce()
break;
}
default:
- LOG_WARN("Unknown kb_model 0x%02x\n", kb_model);
+ LOG_WARN("Unknown kb_model 0x%02x", kb_model);
}
return 300;
}
\ No newline at end of file
diff --git a/src/input/kbI2cBase.h b/src/input/kbI2cBase.h
index 35b9b0901..dc2414fc0 100644
--- a/src/input/kbI2cBase.h
+++ b/src/input/kbI2cBase.h
@@ -2,6 +2,7 @@
#include "BBQ10Keyboard.h"
#include "InputBroker.h"
+#include "MPR121Keyboard.h"
#include "Wire.h"
#include "concurrency/OSThread.h"
@@ -19,5 +20,6 @@ class KbI2cBase : public Observable, public concurrency::OST
TwoWire *i2cBus = 0;
BBQ10Keyboard Q10keyboard;
+ MPR121Keyboard MPRkeyboard;
bool is_sym = false;
-};
+};
\ No newline at end of file
diff --git a/src/input/kbMatrixBase.cpp b/src/input/kbMatrixBase.cpp
index 823bfb629..51815b525 100644
--- a/src/input/kbMatrixBase.cpp
+++ b/src/input/kbMatrixBase.cpp
@@ -70,7 +70,7 @@ int32_t KbMatrixBase::runOnce()
// debounce
if (key != prevkey) {
if (key != 0) {
- LOG_DEBUG("Key 0x%x pressed\n", key);
+ LOG_DEBUG("Key 0x%x pressed", key);
// reset shift now that we have a keypress
InputEvent e;
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
@@ -122,7 +122,7 @@ int32_t KbMatrixBase::runOnce()
}
} else {
- LOG_WARN("Unknown kb_model 0x%02x\n", INPUTBROKER_MATRIX_TYPE);
+ LOG_WARN("Unknown kb_model 0x%02x", INPUTBROKER_MATRIX_TYPE);
return disable();
}
return 50; // Keyscan every 50msec to avoid key bounce
diff --git a/src/main.cpp b/src/main.cpp
index 46b5bea1f..41c32ea71 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,3 +1,4 @@
+#include "../userPrefs.h"
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_GPS
#include "GPS.h"
@@ -11,34 +12,33 @@
#include "airtime.h"
#include "buzz.h"
-#include "error.h"
-#include "power.h"
-// #include "debug.h"
#include "FSCommon.h"
#include "Led.h"
#include "RTC.h"
#include "SPILock.h"
+#include "Throttle.h"
#include "concurrency/OSThread.h"
#include "concurrency/Periodic.h"
#include "detect/ScanI2C.h"
+#include "error.h"
+#include "power.h"
#if !MESHTASTIC_EXCLUDE_I2C
#include "detect/ScanI2CTwoWire.h"
#include
#endif
-#include "detect/axpDebug.h"
#include "detect/einkScan.h"
#include "graphics/RAKled.h"
#include "graphics/Screen.h"
#include "main.h"
#include "mesh/generated/meshtastic/config.pb.h"
+#include "meshUtils.h"
#include "modules/Modules.h"
#include "shutdown.h"
#include "sleep.h"
#include "target_specific.h"
#include
#include
-// #include
#ifdef ARCH_ESP32
#if !MESHTASTIC_EXCLUDE_WEBSERVER
@@ -72,6 +72,7 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr;
#include "LLCC68Interface.h"
#include "LR1110Interface.h"
#include "LR1120Interface.h"
+#include "LR1121Interface.h"
#include "RF95Interface.h"
#include "SX1262Interface.h"
#include "SX1268Interface.h"
@@ -102,8 +103,8 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr;
#include "AmbientLightingThread.h"
#include "PowerFSMThread.h"
-#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
-#include "AccelerometerThread.h"
+#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C
+#include "motion/AccelerometerThread.h"
AccelerometerThread *accelerometerThread = nullptr;
#endif
@@ -118,6 +119,8 @@ float tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if TCXO is optional, put this h
using namespace concurrency;
+volatile static const char slipstreamTZString[] = USERPREFS_TZ_STRING;
+
// We always create a screen object, but we only init it if we find the hardware
graphics::Screen *screen = nullptr;
@@ -234,7 +237,7 @@ void lateInitVariant() {}
*/
void printInfo()
{
- LOG_INFO("S:B:%d,%s\n", HW_VENDOR, optstr(APP_VERSION));
+ LOG_INFO("S:B:%d,%s", HW_VENDOR, optstr(APP_VERSION));
}
#ifndef PIO_UNIT_TESTING
void setup()
@@ -263,17 +266,22 @@ void setup()
#ifdef DEBUG_PORT
consoleInit(); // Set serial baud rate and init our mesh console
#endif
+
+#ifdef UNPHONE
+ unphone.printStore();
+#endif
+
#if ARCH_PORTDUINO
struct timeval tv;
tv.tv_sec = time(NULL);
tv.tv_usec = 0;
perhapsSetRTC(RTCQualityNTP, &tv);
#endif
- powerMonInit();
+ powerMonInit();
serialSinceMsec = millis();
- LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n\n");
+ LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n");
initDeepSleep();
@@ -294,6 +302,11 @@ void setup()
digitalWrite(VEXT_ENABLE, VEXT_ON_VALUE); // turn on the display power
#endif
+#if defined(BIAS_T_ENABLE)
+ pinMode(BIAS_T_ENABLE, OUTPUT);
+ digitalWrite(BIAS_T_ENABLE, BIAS_T_VALUE); // turn on 5V for GPS Antenna
+#endif
+
#if defined(VTFT_CTRL)
pinMode(VTFT_CTRL, OUTPUT);
digitalWrite(VTFT_CTRL, LOW);
@@ -315,22 +328,26 @@ void setup()
#ifdef PERIPHERAL_WARMUP_MS
// Some peripherals may require additional time to stabilize after power is connected
// e.g. I2C on Heltec Vision Master
- LOG_INFO("Waiting for peripherals to stabilize\n");
+ LOG_INFO("Waiting for peripherals to stabilize");
delay(PERIPHERAL_WARMUP_MS);
#endif
#ifdef BUTTON_PIN
#ifdef ARCH_ESP32
- // If the button is connected to GPIO 12, don't enable the ability to use
- // meshtasticAdmin on the device.
- pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT);
-
+#if ESP_ARDUINO_VERSION_MAJOR >= 3
+#ifdef BUTTON_NEED_PULLUP
+ pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP);
+#else
+ pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN
+#endif
+#else
+ pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT); // default to BUTTON_PIN
#ifdef BUTTON_NEED_PULLUP
gpio_pullup_en((gpio_num_t)(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN));
delay(10);
#endif
-
+#endif
#endif
#endif
@@ -360,6 +377,8 @@ void setup()
Wire1.begin();
#elif defined(I2C_SDA1) && !defined(ARCH_RP2040)
Wire1.begin(I2C_SDA1, I2C_SCL1);
+#elif WIRE_INTERFACES_COUNT == 2
+ Wire1.begin();
#endif
#if defined(I2C_SDA) && defined(ARCH_RP2040)
@@ -370,10 +389,10 @@ 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].c_str());
+ LOG_INFO("Using %s as I2C device.", settingsStrings[i2cdev].c_str());
Wire.begin(settingsStrings[i2cdev].c_str());
} else {
- LOG_INFO("No I2C device configured, skipping.\n");
+ LOG_INFO("No I2C device configured, skipping.");
}
#elif HAS_WIRE
Wire.begin();
@@ -390,7 +409,7 @@ void setup()
#endif
#ifdef AQ_SET_PIN
- // RAK-12039 set pin for Air quality sensor
+ // RAK-12039 set pin for Air quality sensor. Detectable on I2C after ~3 seconds, so we need to rescan later
pinMode(AQ_SET_PIN, OUTPUT);
digitalWrite(AQ_SET_PIN, HIGH);
#endif
@@ -416,7 +435,7 @@ void setup()
// accessories
auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire());
#if HAS_WIRE
- LOG_INFO("Scanning for i2c devices...\n");
+ LOG_INFO("Scanning for i2c devices...");
#endif
#if defined(I2C_SDA1) && defined(ARCH_RP2040)
@@ -427,6 +446,8 @@ void setup()
#elif defined(I2C_SDA1) && !defined(ARCH_RP2040)
Wire1.begin(I2C_SDA1, I2C_SCL1);
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1);
+#elif defined(NRF52840_XXAA) && (WIRE_INTERFACES_COUNT == 2)
+ i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1);
#endif
#if defined(I2C_SDA) && defined(ARCH_RP2040)
@@ -439,7 +460,7 @@ void setup()
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE);
#elif defined(ARCH_PORTDUINO)
if (settingsStrings[i2cdev] != "") {
- LOG_INFO("Scanning for i2c devices...\n");
+ LOG_INFO("Scanning for i2c devices...");
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE);
}
#elif HAS_WIRE
@@ -448,9 +469,9 @@ void setup()
auto i2cCount = i2cScanner->countDevices();
if (i2cCount == 0) {
- LOG_INFO("No I2C devices found\n");
+ LOG_INFO("No I2C devices found");
} else {
- LOG_INFO("%i I2C devices found\n", i2cCount);
+ LOG_INFO("%i I2C devices found", i2cCount);
#ifdef SENSOR_GPS_CONFLICT
sensor_detected = true;
#endif
@@ -506,9 +527,13 @@ void setup()
// assign an arbitrary value to distinguish from other models
kb_model = 0x11;
break;
+ case ScanI2C::DeviceType::MPR121KB:
+ // assign an arbitrary value to distinguish from other models
+ kb_model = 0x37;
+ 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);
+ LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00", kb_info.type);
kb_model = 0x00;
}
}
@@ -526,10 +551,25 @@ void setup()
rgb_found = i2cScanner->find(ScanI2C::DeviceType::NCP5623);
#endif
+#ifdef HAS_TPS65233
+ // TPS65233 is a power management IC for satellite modems, used in the Dreamcatcher
+ // We are switching it off here since we don't use an LNB.
+ if (i2cScanner->exists(ScanI2C::DeviceType::TPS65233)) {
+ Wire.beginTransmission(TPS65233_ADDR);
+ Wire.write(0); // Register 0
+ Wire.write(128); // Turn off the LNB power, keep I2C Control enabled
+ Wire.endTransmission();
+ Wire.beginTransmission(TPS65233_ADDR);
+ Wire.write(1); // Register 1
+ Wire.write(0); // Turn off Tone Generator 22kHz
+ Wire.endTransmission();
+ }
+#endif
+
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
auto acc_info = i2cScanner->firstAccelerometer();
accelerometer_found = acc_info.type != ScanI2C::DeviceType::NONE ? acc_info.address : accelerometer_found;
- LOG_DEBUG("acc_info = %i\n", acc_info.type);
+ LOG_DEBUG("acc_info = %i", acc_info.type);
#endif
#define STRING(S) #S
@@ -540,7 +580,7 @@ void setup()
if (found.type != ScanI2C::DeviceType::NONE) { \
nodeTelemetrySensorsMap[PB_T].first = found.address.address; \
nodeTelemetrySensorsMap[PB_T].second = i2cScanner->fetchI2CBus(found.address); \
- LOG_DEBUG("found i2c sensor %s\n", STRING(PB_T)); \
+ LOG_DEBUG("found i2c sensor %s", STRING(PB_T)); \
} \
}
@@ -552,6 +592,7 @@ void setup()
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA219, meshtastic_TelemetrySensorType_INA219)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::INA3221, meshtastic_TelemetrySensorType_INA3221)
+ SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MAX17048, meshtastic_TelemetrySensorType_MAX17048)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MCP9808, meshtastic_TelemetrySensorType_MCP9808)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MCP9808, meshtastic_TelemetrySensorType_MCP9808)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT31, meshtastic_TelemetrySensorType_SHT31)
@@ -560,15 +601,19 @@ void setup()
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMC6310, meshtastic_TelemetrySensorType_QMC6310)
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::HMC5883L, 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::MLX90614, meshtastic_TelemetrySensorType_MLX90614)
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)
+ SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948)
+ SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102)
i2cScanner.reset();
#endif
@@ -586,6 +631,9 @@ void setup()
// Hello
printInfo();
+#ifdef BUILD_EPOCH
+ LOG_INFO("Build timestamp: %ld", BUILD_EPOCH);
+#endif
#ifdef ARCH_ESP32
esp32Setup();
@@ -599,6 +647,8 @@ void setup()
rp2040Setup();
#endif
+ initSPI(); // needed here before reading from littleFS
+
// We do this as early as possible because this loads preferences from flash
// but we need to do this after main cpu init (esp32setup), because we need the random seed set
nodeDB = new NodeDB;
@@ -617,7 +667,13 @@ void setup()
buttonThread = new ButtonThread();
#endif
- playStartMelody();
+ // only play start melody when role is not tracker or sensor
+ if (config.power.is_power_saving == true &&
+ IS_ONE_OF(config.device.role, meshtastic_Config_DeviceConfig_Role_TRACKER,
+ meshtastic_Config_DeviceConfig_Role_TAK_TRACKER, meshtastic_Config_DeviceConfig_Role_SENSOR))
+ LOG_DEBUG("Tracker/Sensor: Skipping start melody");
+ else
+ playStartMelody();
// fixed screen override?
if (config.display.oled != meshtastic_Config_DisplayConfig_OledType_OLED_AUTO)
@@ -625,7 +681,7 @@ void setup()
#if defined(USE_SH1107)
screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // set dimension of 128x128
- display_geometry = GEOMETRY_128_128;
+ screen_geometry = GEOMETRY_128_128;
#endif
#if defined(USE_SH1107_128_64)
@@ -633,10 +689,8 @@ void setup()
#endif
#if !MESHTASTIC_EXCLUDE_I2C
-#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
+#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
if (acc_info.type != ScanI2C::DeviceType::NONE) {
- config.display.wake_on_tap_or_motion = true;
- moduleConfig.external_notification.enabled = true;
accelerometerThread = new AccelerometerThread(acc_info.type);
}
#endif
@@ -658,7 +712,6 @@ void setup()
#endif
// Init our SPI controller (must be before screen and lora)
- initSPI();
#ifdef ARCH_RP2040
#ifdef HW_SPI1_DEVICE
SPI1.setSCK(LORA_SCK);
@@ -678,7 +731,7 @@ void setup()
#else
// ESP32
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
- LOG_DEBUG("SPI.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)\n", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
+ LOG_DEBUG("SPI.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
SPI.setFrequency(4000000);
#endif
@@ -687,13 +740,20 @@ void setup()
// setup TZ prior to time actions.
#if !MESHTASTIC_EXCLUDE_TZ
- if (*config.device.tzdef) {
+ LOG_DEBUG("Using compiled/slipstreamed %s", slipstreamTZString); // important, removing this clobbers our magic string
+ if (*config.device.tzdef && config.device.tzdef[0] != 0) {
+ LOG_DEBUG("Saved TZ: %s ", config.device.tzdef);
setenv("TZ", config.device.tzdef, 1);
} else {
- setenv("TZ", "GMT0", 1);
+ if (strncmp((const char *)slipstreamTZString, "tzpl", 4) == 0) {
+ setenv("TZ", "GMT0", 1);
+ } else {
+ setenv("TZ", (const char *)slipstreamTZString, 1);
+ strcpy(config.device.tzdef, (const char *)slipstreamTZString);
+ }
}
tzset();
- LOG_DEBUG("Set Timezone to %s\n", getenv("TZ"));
+ LOG_DEBUG("Set Timezone to %s", getenv("TZ"));
#endif
readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time)
@@ -710,7 +770,7 @@ void setup()
if (gps) {
gpsStatus->observe(&gps->newStatus);
} else {
- LOG_DEBUG("Running without GPS.\n");
+ LOG_DEBUG("Running without GPS.");
}
}
}
@@ -723,7 +783,7 @@ void setup()
nodeStatus->observe(&nodeDB->newStatus);
#ifdef HAS_I2S
- LOG_DEBUG("Starting audio thread\n");
+ LOG_DEBUG("Starting audio thread");
audioThread = new AudioThread();
#endif
@@ -747,8 +807,8 @@ void setup()
#if !MESHTASTIC_EXCLUDE_I2C
// Don't call screen setup until after nodedb is setup (because we need
// the current region name)
-#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(HX8357_CS) || \
- defined(USE_ST7789)
+#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
+ defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789)
screen->setup();
#elif defined(ARCH_PORTDUINO)
if (screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) {
@@ -762,12 +822,6 @@ void setup()
screen->print("Started...\n");
-#ifdef SX126X_ANT_SW
- // make analog PA vs not PA switch on SX126x eval board work properly
- pinMode(SX126X_ANT_SW, OUTPUT);
- digitalWrite(SX126X_ANT_SW, 1);
-#endif
-
#ifdef PIN_PWR_DELAY_MS
// This may be required to give the peripherals time to power up.
delay(PIN_PWR_DELAY_MS);
@@ -776,63 +830,63 @@ void setup()
#ifdef ARCH_PORTDUINO
if (settingsMap[use_sx1262]) {
if (!rIf) {
- LOG_DEBUG("Attempting to activate sx1262 radio on SPI port %s\n", settingsStrings[spidev].c_str());
+ LOG_DEBUG("Attempting to activate sx1262 radio on SPI port %s", settingsStrings[spidev].c_str());
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()) {
- LOG_ERROR("Failed to find SX1262 radio\n");
+ LOG_ERROR("Failed to find SX1262 radio");
delete rIf;
exit(EXIT_FAILURE);
} else {
- LOG_INFO("SX1262 Radio init succeeded, using SX1262 radio\n");
+ LOG_INFO("SX1262 Radio init succeeded, using SX1262 radio");
}
}
} else if (settingsMap[use_rf95]) {
if (!rIf) {
- LOG_DEBUG("Attempting to activate rf95 radio on SPI port %s\n", settingsStrings[spidev].c_str());
+ LOG_DEBUG("Attempting to activate rf95 radio on SPI port %s", settingsStrings[spidev].c_str());
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()) {
- LOG_ERROR("Failed to find RF95 radio\n");
+ LOG_ERROR("Failed to find RF95 radio");
delete rIf;
rIf = NULL;
exit(EXIT_FAILURE);
} else {
- LOG_INFO("RF95 Radio init succeeded, using RF95 radio\n");
+ LOG_INFO("RF95 Radio init succeeded, using RF95 radio");
}
}
} else if (settingsMap[use_sx1280]) {
if (!rIf) {
- LOG_DEBUG("Attempting to activate sx1280 radio on SPI port %s\n", settingsStrings[spidev].c_str());
+ LOG_DEBUG("Attempting to activate sx1280 radio on SPI port %s", settingsStrings[spidev].c_str());
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
rIf = new SX1280Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
- LOG_ERROR("Failed to find SX1280 radio\n");
+ LOG_ERROR("Failed to find SX1280 radio");
delete rIf;
rIf = NULL;
exit(EXIT_FAILURE);
} else {
- LOG_INFO("SX1280 Radio init succeeded, using SX1280 radio\n");
+ LOG_INFO("SX1280 Radio init succeeded, using SX1280 radio");
}
}
} else if (settingsMap[use_sx1268]) {
if (!rIf) {
- LOG_DEBUG("Attempting to activate sx1268 radio on SPI port %s\n", settingsStrings[spidev].c_str());
+ LOG_DEBUG("Attempting to activate sx1268 radio on SPI port %s", 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");
+ LOG_ERROR("Failed to find SX1268 radio");
delete rIf;
rIf = NULL;
exit(EXIT_FAILURE);
} else {
- LOG_INFO("SX1268 Radio init succeeded, using SX1268 radio\n");
+ LOG_INFO("SX1268 Radio init succeeded, using SX1268 radio");
}
}
}
@@ -848,11 +902,11 @@ void setup()
if (!rIf) {
rIf = new STM32WLE5JCInterface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
- LOG_WARN("Failed to find STM32WL radio\n");
+ LOG_WARN("Failed to find STM32WL radio");
delete rIf;
rIf = NULL;
} else {
- LOG_INFO("STM32WL Radio init succeeded, using STM32WL radio\n");
+ LOG_INFO("STM32WL Radio init succeeded, using STM32WL radio");
radioType = STM32WLx_RADIO;
}
}
@@ -862,154 +916,165 @@ void setup()
if (!rIf) {
rIf = new SimRadio;
if (!rIf->init()) {
- LOG_WARN("Failed to find simulated radio\n");
+ LOG_WARN("Failed to find simulated radio");
delete rIf;
rIf = NULL;
} else {
- LOG_INFO("Using SIMULATED radio!\n");
+ LOG_INFO("Using SIMULATED radio!");
radioType = SIM_RADIO;
}
}
#endif
-#if defined(RF95_IRQ)
- if (!rIf) {
+#if defined(RF95_IRQ) && RADIOLIB_EXCLUDE_SX127X != 1
+ if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
rIf = new RF95Interface(RadioLibHAL, LORA_CS, RF95_IRQ, RF95_RESET, RF95_DIO1);
if (!rIf->init()) {
- LOG_WARN("Failed to find RF95 radio\n");
+ LOG_WARN("Failed to find RF95 radio");
delete rIf;
rIf = NULL;
} else {
- LOG_INFO("RF95 Radio init succeeded, using RF95 radio\n");
+ LOG_INFO("RF95 Radio init succeeded, using RF95 radio");
radioType = RF95_RADIO;
}
}
#endif
-#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL)
- if (!rIf) {
+#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && !defined(TCXO_OPTIONAL) && RADIOLIB_EXCLUDE_SX126X != 1
+ if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
- LOG_WARN("Failed to find SX1262 radio\n");
+ LOG_WARN("Failed to find SX1262 radio");
delete rIf;
rIf = NULL;
} else {
- LOG_INFO("SX1262 Radio init succeeded, using SX1262 radio\n");
+ LOG_INFO("SX1262 Radio init succeeded, using SX1262 radio");
radioType = SX1262_RADIO;
}
}
#endif
#if defined(USE_SX1262) && !defined(ARCH_PORTDUINO) && defined(TCXO_OPTIONAL)
- if (!rIf) {
+ if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
// Try using the specified TCXO voltage
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
- LOG_WARN("Failed to find SX1262 radio with TCXO using DIO3 reference voltage at %f V\n", tcxoVoltage);
+ LOG_WARN("Failed to find SX1262 radio with TCXO, Vref %f V", tcxoVoltage);
delete rIf;
rIf = NULL;
tcxoVoltage = 0; // if it fails, set the TCXO voltage to zero for the next attempt
} else {
- LOG_INFO("SX1262 Radio init succeeded, using ");
- LOG_WARN("SX1262 Radio with TCXO");
- LOG_INFO(", reference voltage at %f V\n", tcxoVoltage);
+ LOG_WARN("SX1262 Radio init succeeded, TCXO, Vref %f V", tcxoVoltage);
radioType = SX1262_RADIO;
}
}
- if (!rIf) {
+ if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
// If specified TCXO voltage fails, attempt to use DIO3 as a reference instea
rIf = new SX1262Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
- LOG_WARN("Failed to find SX1262 radio with XTAL using DIO3 reference voltage at %f V\n", tcxoVoltage);
+ LOG_WARN("Failed to find SX1262 radio with XTAL, Vref %f V", tcxoVoltage);
delete rIf;
rIf = NULL;
tcxoVoltage = SX126X_DIO3_TCXO_VOLTAGE; // if it fails, set the TCXO voltage back for the next radio search
} else {
- LOG_INFO("SX1262 Radio init succeeded, using ");
- LOG_WARN("SX1262 Radio with XTAL");
- LOG_INFO(", reference voltage at %f V\n", tcxoVoltage);
+ LOG_INFO("SX1262 Radio init succeeded, XTAL, Vref %f V", tcxoVoltage);
radioType = SX1262_RADIO;
}
}
#endif
#if defined(USE_SX1268)
- if (!rIf) {
+ if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
rIf = new SX1268Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
- LOG_WARN("Failed to find SX1268 radio\n");
+ LOG_WARN("Failed to find SX1268 radio");
delete rIf;
rIf = NULL;
} else {
- LOG_INFO("SX1268 Radio init succeeded, using SX1268 radio\n");
+ LOG_INFO("SX1268 Radio init succeeded, using SX1268 radio");
radioType = SX1268_RADIO;
}
}
#endif
#if defined(USE_LLCC68)
- if (!rIf) {
+ if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
rIf = new LLCC68Interface(RadioLibHAL, SX126X_CS, SX126X_DIO1, SX126X_RESET, SX126X_BUSY);
if (!rIf->init()) {
- LOG_WARN("Failed to find LLCC68 radio\n");
+ LOG_WARN("Failed to find LLCC68 radio");
delete rIf;
rIf = NULL;
} else {
- LOG_INFO("LLCC68 Radio init succeeded, using LLCC68 radio\n");
+ LOG_INFO("LLCC68 Radio init succeeded, using LLCC68 radio");
radioType = LLCC68_RADIO;
}
}
#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 defined(USE_LR1110) && RADIOLIB_EXCLUDE_LR11X0 != 1
+ if ((!rIf) && (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) {
+ rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESET_PIN, LR1110_BUSY_PIN);
if (!rIf->init()) {
- LOG_WARN("Failed to find LR1110 radio\n");
+ LOG_WARN("Failed to find LR1110 radio");
delete rIf;
rIf = NULL;
} else {
- LOG_INFO("LR1110 Radio init succeeded, using LR1110 radio\n");
+ LOG_INFO("LR1110 Radio init succeeded, using LR1110 radio");
+ radioType = LR1110_RADIO;
}
}
#endif
-#if defined(USE_LR1120)
+#if defined(USE_LR1120) && RADIOLIB_EXCLUDE_LR11X0 != 1
if (!rIf) {
- rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESER_PIN, LR1120_BUSY_PIN);
+ rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESET_PIN, LR1120_BUSY_PIN);
if (!rIf->init()) {
- LOG_WARN("Failed to find LR1120 radio\n");
+ LOG_WARN("Failed to find LR1120 radio");
delete rIf;
rIf = NULL;
} else {
- LOG_INFO("LR1120 Radio init succeeded, using LR1120 radio\n");
+ LOG_INFO("LR1120 Radio init succeeded, using LR1120 radio");
+ radioType = LR1120_RADIO;
}
}
#endif
-#if defined(USE_SX1280)
+#if defined(USE_LR1121) && RADIOLIB_EXCLUDE_LR11X0 != 1
+ if (!rIf) {
+ rIf = new LR1121Interface(RadioLibHAL, LR1121_SPI_NSS_PIN, LR1121_IRQ_PIN, LR1121_NRESET_PIN, LR1121_BUSY_PIN);
+ if (!rIf->init()) {
+ LOG_WARN("Failed to find LR1121 radio");
+ delete rIf;
+ rIf = NULL;
+ } else {
+ LOG_INFO("LR1121 Radio init succeeded, using LR1121 radio");
+ radioType = LR1121_RADIO;
+ }
+ }
+#endif
+
+#if defined(USE_SX1280) && RADIOLIB_EXCLUDE_SX128X != 1
if (!rIf) {
rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY);
if (!rIf->init()) {
- LOG_WARN("Failed to find SX1280 radio\n");
+ LOG_WARN("Failed to find SX1280 radio");
delete rIf;
rIf = NULL;
} else {
- LOG_INFO("SX1280 Radio init succeeded, using SX1280 radio\n");
+ LOG_INFO("SX1280 Radio init succeeded, using SX1280 radio");
radioType = SX1280_RADIO;
}
}
#endif
// check if the radio chip matches the selected region
-
if ((config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (!rIf->wideLora())) {
- LOG_WARN("Radio chip does not support 2.4GHz LoRa. Reverting to unset.\n");
+ LOG_WARN("Radio chip does not support 2.4GHz LoRa. Reverting to unset.");
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
nodeDB->saveToDisk(SEGMENT_CONFIG);
if (!rIf->reconfigure()) {
- LOG_WARN("Reconfigure failed, rebooting\n");
+ LOG_WARN("Reconfigure failed, rebooting");
screen->startAlert("Rebooting...");
rebootAtMsec = millis() + 5000;
}
@@ -1064,9 +1129,9 @@ void setup()
router->addInterface(rIf);
// Log bit rate to debug output
- LOG_DEBUG("LoRA bitrate = %f bytes / sec\n", (float(meshtastic_Constants_DATA_PAYLOAD_LEN) /
- (float(rIf->getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN)))) *
- 1000);
+ LOG_DEBUG("LoRA bitrate = %f bytes / sec", (float(meshtastic_Constants_DATA_PAYLOAD_LEN) /
+ (float(rIf->getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN)))) *
+ 1000);
}
// This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values
@@ -1095,6 +1160,9 @@ extern meshtastic_DeviceMetadata getDeviceMetadata()
deviceMetadata.position_flags = config.position.position_flags;
deviceMetadata.hw_model = HW_VENDOR;
deviceMetadata.hasRemoteHardware = moduleConfig.remote_hardware.enabled;
+#if !(MESHTASTIC_EXCLUDE_PKI)
+ deviceMetadata.hasPKC = true;
+#endif
return deviceMetadata;
}
#ifndef PIO_UNIT_TESTING
@@ -1102,10 +1170,6 @@ void loop()
{
runASAP = false;
- // axpDebugOutput.loop();
-
- // heap_caps_check_integrity_all(true); // FIXME - disable this expensive check
-
#ifdef ARCH_ESP32
esp32Loop();
#endif
@@ -1114,33 +1178,21 @@ void loop()
#endif
powerCommandsCheck();
- // For debugging
- // if (rIf) ((RadioLibInterface *)rIf)->isActivelyReceiving();
-
#ifdef DEBUG_STACK
static uint32_t lastPrint = 0;
- if (millis() - lastPrint > 10 * 1000L) {
+ if (!Throttle::isWithinTimespanMs(lastPrint, 10 * 1000L)) {
lastPrint = millis();
meshtastic::printThreadInfo("main");
}
#endif
- // TODO: This should go into a thread handled by FreeRTOS.
- // handleWebResponse();
-
service->loop();
long delayMsec = mainController.runOrDelay();
- /* if (mainController.nextThread && delayMsec)
- LOG_DEBUG("Next %s in %ld\n", mainController.nextThread->ThreadName.c_str(),
- mainController.nextThread->tillRun(millis())); */
-
// We want to sleep as long as possible here - because it saves power
if (!runASAP && loopCanSleep()) {
- // if(delayMsec > 100) LOG_DEBUG("sleeping %ld\n", delayMsec);
mainDelay.delay(delayMsec);
}
- // if (didWake) LOG_DEBUG("wake!\n");
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/main.h b/src/main.h
index 52a3fff1f..5722f7cf0 100644
--- a/src/main.h
+++ b/src/main.h
@@ -56,8 +56,8 @@ extern AudioThread *audioThread;
// Global Screen singleton.
extern graphics::Screen *screen;
-#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
-#include "AccelerometerThread.h"
+#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C
+#include "motion/AccelerometerThread.h"
extern AccelerometerThread *accelerometerThread;
#endif
@@ -86,4 +86,4 @@ void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop(), rp2040Setup(), clearB
meshtastic_DeviceMetadata getDeviceMetadata();
// We default to 4MHz SPI, SPI mode 0
-extern SPISettings spiSettings;
+extern SPISettings spiSettings;
\ No newline at end of file
diff --git a/src/memGet.cpp b/src/memGet.cpp
index e982ef7ee..ef1102f1e 100644
--- a/src/memGet.cpp
+++ b/src/memGet.cpp
@@ -57,6 +57,8 @@ uint32_t MemGet::getFreePsram()
{
#ifdef ARCH_ESP32
return ESP.getFreePsram();
+#elif defined(ARCH_PORTDUINO)
+ return 4194252;
#else
return 0;
#endif
@@ -71,6 +73,8 @@ uint32_t MemGet::getPsramSize()
{
#ifdef ARCH_ESP32
return ESP.getPsramSize();
+#elif defined(ARCH_PORTDUINO)
+ return 4194252;
#else
return 0;
#endif
diff --git a/src/mesh/Channels.cpp b/src/mesh/Channels.cpp
index bba7571d2..b9fe95678 100644
--- a/src/mesh/Channels.cpp
+++ b/src/mesh/Channels.cpp
@@ -13,10 +13,6 @@
#include "mqtt/MQTT.h"
#endif
-/// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128)
-static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
- 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01};
-
Channels channels;
const char *Channels::adminChannel = "admin";
@@ -80,6 +76,23 @@ meshtastic_Channel &Channels::fixupChannel(ChannelIndex chIndex)
return ch;
}
+void Channels::initDefaultLoraConfig()
+{
+ meshtastic_Config_LoRaConfig &loraConfig = config.lora;
+
+ loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; // Default to Long Range & Fast
+ loraConfig.use_preset = true;
+ loraConfig.tx_power = 0; // default
+ loraConfig.channel_num = 0;
+
+#ifdef USERPREFS_LORACONFIG_MODEM_PRESET
+ loraConfig.modem_preset = USERPREFS_LORACONFIG_MODEM_PRESET;
+#endif
+#ifdef USERPREFS_LORACONFIG_CHANNEL_NUM
+ loraConfig.channel_num = USERPREFS_LORACONFIG_CHANNEL_NUM;
+#endif
+}
+
/**
* Write a default channel to the specified channel index
*/
@@ -87,12 +100,7 @@ void Channels::initDefaultChannel(ChannelIndex chIndex)
{
meshtastic_Channel &ch = getByIndex(chIndex);
meshtastic_ChannelSettings &channelSettings = ch.settings;
- meshtastic_Config_LoRaConfig &loraConfig = config.lora;
- loraConfig.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; // Default to Long Range & Fast
- loraConfig.use_preset = true;
- loraConfig.tx_power = 0; // default
- loraConfig.channel_num = 0;
uint8_t defaultpskIndex = 1;
channelSettings.psk.bytes[0] = defaultpskIndex;
channelSettings.psk.size = 1;
@@ -101,29 +109,68 @@ void Channels::initDefaultChannel(ChannelIndex chIndex)
channelSettings.has_module_settings = true;
ch.has_settings = true;
- ch.role = meshtastic_Channel_Role_PRIMARY;
+ ch.role = chIndex == 0 ? meshtastic_Channel_Role_PRIMARY : meshtastic_Channel_Role_SECONDARY;
-#ifdef LORACONFIG_MODEM_PRESET_USERPREFS
- loraConfig.modem_preset = LORACONFIG_MODEM_PRESET_USERPREFS;
+ switch (chIndex) {
+ case 0:
+#ifdef USERPREFS_CHANNEL_0_PSK
+ static const uint8_t defaultpsk0[] = USERPREFS_CHANNEL_0_PSK;
+ memcpy(channelSettings.psk.bytes, defaultpsk0, sizeof(defaultpsk0));
+ channelSettings.psk.size = sizeof(defaultpsk0);
#endif
-#ifdef LORACONFIG_CHANNEL_NUM_USERPREFS
- loraConfig.channel_num = LORACONFIG_CHANNEL_NUM_USERPREFS;
+#ifdef USERPREFS_CHANNEL_0_NAME
+ strcpy(channelSettings.name, USERPREFS_CHANNEL_0_NAME);
#endif
-
- // Install custom defaults. Will eventually support setting multiple default channels
- if (chIndex == 0) {
-#ifdef CHANNEL_0_PSK_USERPREFS
- static const uint8_t defaultpsk[] = CHANNEL_0_PSK_USERPREFS;
- memcpy(channelSettings.psk.bytes, defaultpsk, sizeof(defaultpsk));
- channelSettings.psk.size = sizeof(defaultpsk);
-
+#ifdef USERPREFS_CHANNEL_0_PRECISION
+ channelSettings.module_settings.position_precision = USERPREFS_CHANNEL_0_PRECISION;
#endif
-#ifdef CHANNEL_0_NAME_USERPREFS
- strcpy(channelSettings.name, CHANNEL_0_NAME_USERPREFS);
+#ifdef USERPREFS_CHANNEL_0_UPLINK_ENABLED
+ channelSettings.uplink_enabled = USERPREFS_CHANNEL_0_UPLINK_ENABLED;
#endif
-#ifdef CHANNEL_0_PRECISION_USERPREFS
- channelSettings.module_settings.position_precision = CHANNEL_0_PRECISION_USERPREFS;
+#ifdef USERPREFS_CHANNEL_0_DOWNLINK_ENABLED
+ channelSettings.downlink_enabled = USERPREFS_CHANNEL_0_DOWNLINK_ENABLED;
#endif
+ break;
+ case 1:
+#ifdef USERPREFS_CHANNEL_1_PSK
+ static const uint8_t defaultpsk1[] = USERPREFS_CHANNEL_1_PSK;
+ memcpy(channelSettings.psk.bytes, defaultpsk1, sizeof(defaultpsk1));
+ channelSettings.psk.size = sizeof(defaultpsk1);
+#endif
+#ifdef USERPREFS_CHANNEL_1_NAME
+ strcpy(channelSettings.name, USERPREFS_CHANNEL_1_NAME);
+#endif
+#ifdef USERPREFS_CHANNEL_1_PRECISION
+ channelSettings.module_settings.position_precision = USERPREFS_CHANNEL_1_PRECISION;
+#endif
+#ifdef USERPREFS_CHANNEL_1_UPLINK_ENABLED
+ channelSettings.uplink_enabled = USERPREFS_CHANNEL_1_UPLINK_ENABLED;
+#endif
+#ifdef USERPREFS_CHANNEL_1_DOWNLINK_ENABLED
+ channelSettings.downlink_enabled = USERPREFS_CHANNEL_1_DOWNLINK_ENABLED;
+#endif
+ break;
+ case 2:
+#ifdef USERPREFS_CHANNEL_2_PSK
+ static const uint8_t defaultpsk2[] = USERPREFS_CHANNEL_2_PSK;
+ memcpy(channelSettings.psk.bytes, defaultpsk2, sizeof(defaultpsk2));
+ channelSettings.psk.size = sizeof(defaultpsk2);
+#endif
+#ifdef USERPREFS_CHANNEL_2_NAME
+ strcpy(channelSettings.name, USERPREFS_CHANNEL_2_NAME);
+#endif
+#ifdef USERPREFS_CHANNEL_2_PRECISION
+ channelSettings.module_settings.position_precision = USERPREFS_CHANNEL_2_PRECISION;
+#endif
+#ifdef USERPREFS_CHANNEL_2_UPLINK_ENABLED
+ channelSettings.uplink_enabled = USERPREFS_CHANNEL_2_UPLINK_ENABLED;
+#endif
+#ifdef USERPREFS_CHANNEL_2_DOWNLINK_ENABLED
+ channelSettings.downlink_enabled = USERPREFS_CHANNEL_2_DOWNLINK_ENABLED;
+#endif
+ break;
+ default:
+ break;
}
}
@@ -143,34 +190,19 @@ CryptoKey Channels::getKey(ChannelIndex chIndex)
k.length = channelSettings.psk.size;
if (k.length == 0) {
if (ch.role == meshtastic_Channel_Role_SECONDARY) {
- LOG_DEBUG("Unset PSK for secondary channel %s. using primary key\n", ch.settings.name);
+ LOG_DEBUG("Unset PSK for secondary channel %s. using primary key", ch.settings.name);
k = getKey(primaryIndex);
} else {
- LOG_WARN("User disabled encryption\n");
+ LOG_WARN("User disabled encryption");
}
} else if (k.length == 1) {
// Convert the short single byte variants of psk into variant that can be used more generally
uint8_t pskIndex = k.bytes[0];
- LOG_DEBUG("Expanding short PSK #%d\n", pskIndex);
+ LOG_DEBUG("Expanding short PSK #%d", pskIndex);
if (pskIndex == 0)
k.length = 0; // Turn off encryption
- else if (oemStore.oem_aes_key.size > 1) {
- // Use the OEM key
- LOG_DEBUG("Using OEM Key with %d bytes\n", oemStore.oem_aes_key.size);
- memcpy(k.bytes, oemStore.oem_aes_key.bytes, oemStore.oem_aes_key.size);
- k.length = oemStore.oem_aes_key.size;
- // Bump up the last byte of PSK as needed
- uint8_t *last = k.bytes + oemStore.oem_aes_key.size - 1;
- *last = *last + pskIndex - 1; // index of 1 means no change vs defaultPSK
- if (k.length < 16) {
- LOG_WARN("OEM provided a too short AES128 key - padding\n");
- k.length = 16;
- } else if (k.length < 32 && k.length != 16) {
- LOG_WARN("OEM provided a too short AES256 key - padding\n");
- k.length = 32;
- }
- } else {
+ else {
memcpy(k.bytes, defaultpsk, sizeof(defaultpsk));
k.length = sizeof(defaultpsk);
// Bump up the last byte of PSK as needed
@@ -180,12 +212,12 @@ CryptoKey Channels::getKey(ChannelIndex chIndex)
} else if (k.length < 16) {
// Error! The user specified only the first few bits of an AES128 key. So by convention we just pad the rest of the
// key with zeros
- LOG_WARN("User provided a too short AES128 key - padding\n");
+ LOG_WARN("User provided a too short AES128 key - padding");
k.length = 16;
} else if (k.length < 32 && k.length != 16) {
// Error! The user specified only the first few bits of an AES256 key. So by convention we just pad the rest of the
// key with zeros
- LOG_WARN("User provided a too short AES256 key - padding\n");
+ LOG_WARN("User provided a too short AES256 key - padding");
k.length = 32;
}
}
@@ -213,7 +245,15 @@ void Channels::initDefaults()
channelFile.channels_count = MAX_NUM_CHANNELS;
for (int i = 0; i < channelFile.channels_count; i++)
fixupChannel(i);
+ initDefaultLoraConfig();
+
+#ifdef USERPREFS_CHANNELS_TO_WRITE
+ for (int i = 0; i < USERPREFS_CHANNELS_TO_WRITE; i++) {
+ initDefaultChannel(i);
+ }
+#else
initDefaultChannel(0);
+#endif
}
void Channels::onConfigChanged()
@@ -227,7 +267,7 @@ void Channels::onConfigChanged()
}
#if !MESHTASTIC_EXCLUDE_MQTT
if (channels.anyMqttEnabled() && mqtt && !mqtt->isEnabled()) {
- LOG_DEBUG("MQTT is enabled on at least one channel, so set MQTT thread to run immediately\n");
+ LOG_DEBUG("MQTT is enabled on at least one channel, so set MQTT thread to run immediately");
mqtt->start();
}
#endif
@@ -240,7 +280,7 @@ meshtastic_Channel &Channels::getByIndex(ChannelIndex chIndex)
meshtastic_Channel *ch = channelFile.channels + chIndex;
return *ch;
} else {
- LOG_ERROR("Invalid channel index %d > %d, malformed packet received?\n", chIndex, channelFile.channels_count);
+ LOG_ERROR("Invalid channel index %d > %d, malformed packet received?", chIndex, channelFile.channels_count);
static meshtastic_Channel *ch = (meshtastic_Channel *)malloc(sizeof(meshtastic_Channel));
memset(ch, 0, sizeof(meshtastic_Channel));
@@ -277,7 +317,7 @@ void Channels::setChannel(const meshtastic_Channel &c)
bool Channels::anyMqttEnabled()
{
-#if EVENT_MODE
+#if USERPREFS_EVENT_MODE
// Don't publish messages on the public MQTT broker if we are in event mode
if (strcmp(moduleConfig.mqtt.address, default_mqtt_address) == 0) {
return false;
@@ -344,11 +384,11 @@ bool Channels::hasDefaultChannel()
bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash)
{
if (chIndex > getNumChannels() || getHash(chIndex) != channelHash) {
- // LOG_DEBUG("Skipping channel %d (hash %x) due to invalid hash/index, want=%x\n", chIndex, getHash(chIndex),
+ // LOG_DEBUG("Skipping channel %d (hash %x) due to invalid hash/index, want=%x", chIndex, getHash(chIndex),
// channelHash);
return false;
} else {
- LOG_DEBUG("Using channel %d (hash 0x%x)\n", chIndex, channelHash);
+ LOG_DEBUG("Using channel %d (hash 0x%x)", chIndex, channelHash);
setCrypto(chIndex);
return true;
}
@@ -363,4 +403,4 @@ bool Channels::decryptForHash(ChannelIndex chIndex, ChannelHash channelHash)
int16_t Channels::setActiveByIndex(ChannelIndex channelIndex)
{
return setCrypto(channelIndex);
-}
+}
\ No newline at end of file
diff --git a/src/mesh/Channels.h b/src/mesh/Channels.h
index 4f87cb309..b0c9b3d07 100644
--- a/src/mesh/Channels.h
+++ b/src/mesh/Channels.h
@@ -117,7 +117,12 @@ class Channels
meshtastic_Channel &fixupChannel(ChannelIndex chIndex);
/**
- * Write a default channel to the specified channel index
+ * Writes the default lora config
+ */
+ void initDefaultLoraConfig();
+
+ /**
+ * Write default channels defined in UserPrefs
*/
void initDefaultChannel(ChannelIndex chIndex);
@@ -129,4 +134,12 @@ class Channels
};
/// Singleton channel table
-extern Channels channels;
\ No newline at end of file
+extern Channels channels;
+
+/// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128)
+static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
+ 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0x01};
+
+static const uint8_t eventpsk[] = {0x38, 0x4b, 0xbc, 0xc0, 0x1d, 0xc0, 0x22, 0xd1, 0x81, 0xbf, 0x36,
+ 0xb8, 0x61, 0x21, 0xe1, 0xfb, 0x96, 0xb7, 0x2e, 0x55, 0xbf, 0x74,
+ 0x22, 0x7e, 0x9d, 0x6a, 0xfb, 0x48, 0xd6, 0x4c, 0xb1, 0xa1};
\ No newline at end of file
diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp
index a5322a65a..282013ea0 100644
--- a/src/mesh/CryptoEngine.cpp
+++ b/src/mesh/CryptoEngine.cpp
@@ -1,8 +1,6 @@
#include "CryptoEngine.h"
-#include "NodeDB.h"
-#include "RadioInterface.h"
+// #include "NodeDB.h"
#include "architecture.h"
-#include "configuration.h"
#if !(MESHTASTIC_EXCLUDE_PKI)
#include "aes-ccm.h"
@@ -20,7 +18,7 @@
*/
void CryptoEngine::generateKeyPair(uint8_t *pubKey, uint8_t *privKey)
{
- LOG_DEBUG("Generating Curve25519 key pair...\n");
+ LOG_DEBUG("Generating Curve25519 key pair...");
Curve25519::dh1(public_key, private_key);
memcpy(pubKey, public_key, sizeof(public_key));
memcpy(privKey, private_key, sizeof(private_key));
@@ -37,14 +35,14 @@ bool CryptoEngine::regeneratePublicKey(uint8_t *pubKey, uint8_t *privKey)
if (!memfll(privKey, 0, sizeof(private_key))) {
Curve25519::eval(pubKey, privKey, 0);
if (Curve25519::isWeakPoint(pubKey)) {
- LOG_ERROR("PKI key generation failed. Specified private key results in a weak\n");
+ LOG_ERROR("PKI key generation failed. Specified private key results in a weak");
memset(pubKey, 0, 32);
return false;
}
memcpy(private_key, privKey, sizeof(private_key));
memcpy(public_key, pubKey, sizeof(public_key));
} else {
- LOG_WARN("X25519 key generation failed due to blank private key\n");
+ LOG_WARN("X25519 key generation failed due to blank private key");
return false;
}
return true;
@@ -62,32 +60,32 @@ void CryptoEngine::clearKeys()
*
* @param bytes is updated in place
*/
-bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes,
- uint8_t *bytesOut)
+bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic,
+ uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut)
{
uint8_t *auth;
- uint32_t *extraNonce;
long extraNonceTmp = random();
auth = bytesOut + numBytes;
- extraNonce = (uint32_t *)(auth + 8);
- *extraNonce = extraNonceTmp;
- LOG_INFO("Random nonce value: %d\n", *extraNonce);
- meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(toNode);
- if (node->num < 1 || node->user.public_key.size == 0) {
- LOG_DEBUG("Node %d or their public_key not found\n", toNode);
+ memcpy((uint8_t *)(auth + 8), &extraNonceTmp,
+ sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : *extraNonce = extraNonceTmp;
+ LOG_INFO("Random nonce value: %d", extraNonceTmp);
+ if (remotePublic.size == 0) {
+ LOG_DEBUG("Node %d or their public_key not found", toNode);
return false;
}
- if (!crypto->setDHKey(toNode)) {
+ if (!crypto->setDHPublicKey(remotePublic.bytes)) {
return false;
}
- initNonce(fromNode, packetNum, *extraNonce);
+ crypto->hash(shared_key, 32);
+ initNonce(fromNode, packetNum, extraNonceTmp);
// Calculate the shared secret with the destination node and encrypt
printBytes("Attempting encrypt using nonce: ", nonce, 13);
- printBytes("Attempting encrypt using shared_key: ", shared_key, 32);
+ printBytes("Attempting encrypt using shared_key starting with: ", shared_key, 8);
aes_ccm_ae(shared_key, 32, nonce, 8, bytes, numBytes, nullptr, 0, bytesOut,
auth); // this can write up to 15 bytes longer than numbytes past bytesOut
- *extraNonce = extraNonceTmp;
+ memcpy((uint8_t *)(auth + 8), &extraNonceTmp,
+ sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : *extraNonce = extraNonceTmp;
return true;
}
@@ -97,27 +95,30 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_
*
* @param bytes is updated in place
*/
-bool CryptoEngine::decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut)
+bool CryptoEngine::decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, uint64_t packetNum,
+ size_t numBytes, uint8_t *bytes, uint8_t *bytesOut)
{
- uint8_t *auth; // set to last 8 bytes of text?
- uint32_t *extraNonce;
+ uint8_t *auth; // set to last 8 bytes of text?
+ uint32_t extraNonce; // pointer was not really used
auth = bytes + numBytes - 12;
- extraNonce = (uint32_t *)(auth + 8);
- LOG_INFO("Random nonce value: %d\n", *extraNonce);
- meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(fromNode);
+ memcpy(&extraNonce, auth + 8,
+ sizeof(uint32_t)); // do not use dereference on potential non aligned pointers : (uint32_t *)(auth + 8);
+ LOG_INFO("Random nonce value: %d", extraNonce);
- if (node == nullptr || node->num < 1 || node->user.public_key.size == 0) {
- LOG_DEBUG("Node or its public key not found in database\n");
+ if (remotePublic.size == 0) {
+ LOG_DEBUG("Node or its public key not found in database");
return false;
}
// Calculate the shared secret with the sending node and decrypt
- if (!crypto->setDHKey(fromNode)) {
+ if (!crypto->setDHPublicKey(remotePublic.bytes)) {
return false;
}
- initNonce(fromNode, packetNum, *extraNonce);
+ crypto->hash(shared_key, 32);
+
+ initNonce(fromNode, packetNum, extraNonce);
printBytes("Attempting decrypt using nonce: ", nonce, 13);
- printBytes("Attempting decrypt using shared_key: ", shared_key, 32);
+ printBytes("Attempting decrypt using shared_key starting with: ", shared_key, 8);
return aes_ccm_ad(shared_key, 32, nonce, 8, bytes, numBytes - 12, nullptr, 0, auth, bytesOut);
}
@@ -125,37 +126,6 @@ void CryptoEngine::setDHPrivateKey(uint8_t *_private_key)
{
memcpy(private_key, _private_key, 32);
}
-/**
- * Set the PKI key used for encrypt, decrypt.
- *
- * @param nodeNum the node number of the node who's public key we want to use
- */
-bool CryptoEngine::setDHKey(uint32_t nodeNum)
-{
- meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeNum);
- if (node->num < 1 || node->user.public_key.size == 0) {
- LOG_DEBUG("Node %d or their public_key not found\n", nodeNum);
- return false;
- }
-
- if (!setDHPublicKey(node->user.public_key.bytes))
- return false;
-
- printBytes("DH Output: ", shared_key, 32);
-
- /**
- * D.J. Bernstein reccomends hashing the shared key. We want to do this because there are
- * at least 128 bits of entropy in the 256-bit output of the DH key exchange, but we don't
- * really know where. If you extract, for instance, the first 128 bits with basic truncation,
- * then you don't know if you got all of your 128 entropy bits, or less, possibly much less.
- *
- * No exploitable bias is really known at that point, but we know enough to be wary.
- * Hashing the DH output is a simple and safe way to gather all the entropy and spread
- * it around as needed.
- */
- crypto->hash(shared_key, 32);
- return true;
-}
/**
* Hash arbitrary data using SHA256.
@@ -166,12 +136,12 @@ bool CryptoEngine::setDHKey(uint32_t nodeNum)
void CryptoEngine::hash(uint8_t *bytes, size_t numBytes)
{
SHA256 hash;
- size_t posn, len;
+ size_t posn;
uint8_t size = numBytes;
uint8_t inc = 16;
hash.reset();
for (posn = 0; posn < size; posn += inc) {
- len = size - posn;
+ size_t len = size - posn;
if (len > inc)
len = inc;
hash.update(bytes + posn, len);
@@ -204,7 +174,7 @@ bool CryptoEngine::setDHPublicKey(uint8_t *pubKey)
// Calculate the shared secret with the specified node's public key and our private key
// This includes an internal weak key check, which among other things looks for an all 0 public key and shared key.
if (!Curve25519::dh2(shared_key, local_priv)) {
- LOG_WARN("Curve25519DH step 2 failed!\n");
+ LOG_WARN("Curve25519DH step 2 failed!");
return false;
}
return true;
@@ -215,7 +185,7 @@ concurrency::Lock *cryptLock;
void CryptoEngine::setKey(const CryptoKey &k)
{
- LOG_DEBUG("Using AES%d key!\n", k.length * 8);
+ LOG_DEBUG("Using AES%d key!", k.length * 8);
key = k;
}
@@ -231,7 +201,7 @@ void CryptoEngine::encryptPacket(uint32_t fromNode, uint64_t packetId, size_t nu
if (numBytes <= MAX_BLOCKSIZE) {
encryptAESCtr(key, nonce, numBytes, bytes);
} else {
- LOG_ERROR("Packet too large for crypto engine: %d. noop encryption!\n", numBytes);
+ LOG_ERROR("Packet too large for crypto engine: %d. noop encryption!", numBytes);
}
}
}
diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h
index 4c2fc19d9..32862d95c 100644
--- a/src/mesh/CryptoEngine.h
+++ b/src/mesh/CryptoEngine.h
@@ -39,10 +39,10 @@ class CryptoEngine
#endif
void clearKeys();
void setDHPrivateKey(uint8_t *_private_key);
- virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes,
- uint8_t *bytesOut);
- virtual bool decryptCurve25519(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut);
- bool setDHKey(uint32_t nodeNum);
+ virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic,
+ uint64_t packetNum, size_t numBytes, uint8_t *bytes, uint8_t *bytesOut);
+ virtual bool decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, uint64_t packetNum,
+ size_t numBytes, uint8_t *bytes, uint8_t *bytesOut);
virtual bool setDHPublicKey(uint8_t *publicKey);
virtual void hash(uint8_t *bytes, size_t numBytes);
diff --git a/src/mesh/Default.cpp b/src/mesh/Default.cpp
index ac7441394..653528b60 100644
--- a/src/mesh/Default.cpp
+++ b/src/mesh/Default.cpp
@@ -43,9 +43,18 @@ uint32_t Default::getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t d
return getConfiguredOrDefaultMs(configured, defaultValue) * congestionScalingCoefficient(numOnlineNodes);
}
+uint32_t Default::getConfiguredOrMinimumValue(uint32_t configured, uint32_t minValue)
+{
+ // If zero, intervals should be coalesced later by getConfiguredOrDefault... methods
+ if (configured == 0)
+ return configured;
+
+ return configured < minValue ? minValue : configured;
+}
+
uint8_t Default::getConfiguredOrDefaultHopLimit(uint8_t configured)
{
-#if EVENT_MODE
+#if USERPREFS_EVENT_MODE
return (configured > HOP_RELIABLE) ? HOP_RELIABLE : config.lora.hop_limit;
#else
return (configured >= HOP_MAX) ? HOP_MAX : config.lora.hop_limit;
diff --git a/src/mesh/Default.h b/src/mesh/Default.h
index bafa60898..2406dafc5 100644
--- a/src/mesh/Default.h
+++ b/src/mesh/Default.h
@@ -3,9 +3,12 @@
#include
#define ONE_DAY 24 * 60 * 60
#define ONE_MINUTE_MS 60 * 1000
+#define THIRTY_SECONDS_MS 30 * 1000
+#define FIVE_SECONDS_MS 5 * 1000
+#define min_default_telemetry_interval_secs 30 * 60
#define default_gps_update_interval IF_ROUTER(ONE_DAY, 2 * 60)
-#define default_telemetry_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 30 * 60)
+#define default_telemetry_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 60 * 60)
#define default_broadcast_interval_secs IF_ROUTER(ONE_DAY / 2, 15 * 60)
#define default_wait_bluetooth_secs IF_ROUTER(1, 60)
#define default_sds_secs IF_ROUTER(ONE_DAY, UINT32_MAX) // Default to forever super deep sleep
@@ -33,6 +36,7 @@ class Default
static uint32_t getConfiguredOrDefault(uint32_t configured, uint32_t defaultValue);
static uint32_t getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes);
static uint8_t getConfiguredOrDefaultHopLimit(uint8_t configured);
+ static uint32_t getConfiguredOrMinimumValue(uint32_t configured, uint32_t minValue);
private:
static float congestionScalingCoefficient(int numOnlineNodes)
diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index da4c6f969..fa35e48ef 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -22,11 +22,13 @@ ErrorCode FloodingRouter::send(meshtastic_MeshPacket *p)
bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
{
if (wasSeenRecently(p)) { // Note: this will also add a recent packet record
- printPacket("Ignoring incoming msg we've already seen", p);
+ printPacket("Ignoring dupe incoming msg", p);
+ rxDupe++;
if (config.device.role != meshtastic_Config_DeviceConfig_Role_ROUTER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_REPEATER) {
// cancel rebroadcast of this message *if* there was already one, unless we're a router/repeater!
- Router::cancelSending(p->from, p->id);
+ if (Router::cancelSending(p->from, p->id))
+ txRelayCanceled++;
}
return true;
}
@@ -34,15 +36,27 @@ bool FloodingRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
return Router::shouldFilterReceived(p);
}
+bool FloodingRouter::isRebroadcaster()
+{
+ return config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE &&
+ config.device.rebroadcast_mode != meshtastic_Config_DeviceConfig_RebroadcastMode_NONE;
+}
+
void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
{
- if ((p->to != getNodeNum()) && (p->hop_limit > 0) && (getFrom(p) != getNodeNum())) {
+ bool isAckorReply = (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) && (p->decoded.request_id != 0);
+ if (isAckorReply && !isToUs(p) && !isBroadcast(p->to)) {
+ // do not flood direct message that is ACKed or replied to
+ LOG_DEBUG("Rxd an ACK/reply not for me, cancel rebroadcast.");
+ Router::cancelSending(p->to, p->decoded.request_id); // cancel rebroadcast for this DM
+ }
+ if (!isToUs(p) && (p->hop_limit > 0) && !isFromUs(p)) {
if (p->id != 0) {
- if (config.device.role != meshtastic_Config_DeviceConfig_Role_CLIENT_MUTE) {
+ if (isRebroadcaster()) {
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it
tosend->hop_limit--; // bump down the hop count
-#if EVENT_MODE
+#if USERPREFS_EVENT_MODE
if (tosend->hop_limit > 2) {
// if we are "correcting" the hop_limit, "correct" the hop_start by the same amount to preserve hops away.
tosend->hop_start -= (tosend->hop_limit - 2);
@@ -50,15 +64,15 @@ void FloodingRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
}
#endif
- LOG_INFO("Rebroadcasting received floodmsg to neighbors\n");
+ LOG_INFO("Rebroadcasting received floodmsg");
// Note: we are careful to resend using the original senders node id
// We are careful not to call our hooked version of send() - because we don't want to check this again
Router::send(tosend);
} else {
- LOG_DEBUG("Not rebroadcasting. Role = Role_ClientMute\n");
+ LOG_DEBUG("Not rebroadcasting: Role = CLIENT_MUTE or Rebroadcast Mode = NONE");
}
} else {
- LOG_DEBUG("Ignoring a simple (0 id) broadcast\n");
+ LOG_DEBUG("Ignoring 0 id broadcast");
}
}
// handle the packet as normal
diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h
index a3adfe70c..0ed2b5582 100644
--- a/src/mesh/FloodingRouter.h
+++ b/src/mesh/FloodingRouter.h
@@ -29,6 +29,8 @@
class FloodingRouter : public Router, protected PacketHistory
{
private:
+ bool isRebroadcaster();
+
public:
/**
* Constructor
diff --git a/src/mesh/InterfacesTemplates.cpp b/src/mesh/InterfacesTemplates.cpp
index f2cac8028..2720e8525 100644
--- a/src/mesh/InterfacesTemplates.cpp
+++ b/src/mesh/InterfacesTemplates.cpp
@@ -8,12 +8,19 @@
#include "api/ServerAPI.h"
// We need this declaration for proper linking in derived classes
+#if RADIOLIB_EXCLUDE_SX126X != 1
template class SX126xInterface;
template class SX126xInterface;
template class SX126xInterface;
+#endif
+#if RADIOLIB_EXCLUDE_SX128X != 1
template class SX128xInterface;
+#endif
+#if RADIOLIB_EXCLUDE_LR11X0 != 1
template class LR11x0Interface;
template class LR11x0Interface;
+template class LR11x0Interface;
+#endif
#ifdef ARCH_STM32WL
template class SX126xInterface;
#endif
diff --git a/src/mesh/LLCC68Interface.cpp b/src/mesh/LLCC68Interface.cpp
index 8109765a6..d92ea5450 100644
--- a/src/mesh/LLCC68Interface.cpp
+++ b/src/mesh/LLCC68Interface.cpp
@@ -1,3 +1,4 @@
+#if RADIOLIB_EXCLUDE_SX126X != 1
#include "LLCC68Interface.h"
#include "configuration.h"
#include "error.h"
@@ -6,4 +7,5 @@ LLCC68Interface::LLCC68Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, R
RADIOLIB_PIN_TYPE busy)
: SX126xInterface(hal, cs, irq, rst, busy)
{
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/src/mesh/LLCC68Interface.h b/src/mesh/LLCC68Interface.h
index 7e0fa1439..1cd23e92b 100644
--- a/src/mesh/LLCC68Interface.h
+++ b/src/mesh/LLCC68Interface.h
@@ -1,5 +1,5 @@
#pragma once
-
+#if RADIOLIB_EXCLUDE_SX126X != 1
#include "SX126xInterface.h"
/**
@@ -15,4 +15,5 @@ class LLCC68Interface : public SX126xInterface
public:
LLCC68Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
RADIOLIB_PIN_TYPE busy);
-};
\ No newline at end of file
+};
+#endif
\ No newline at end of file
diff --git a/src/mesh/LR1110Interface.cpp b/src/mesh/LR1110Interface.cpp
index c000bd838..5dbd3ff38 100644
--- a/src/mesh/LR1110Interface.cpp
+++ b/src/mesh/LR1110Interface.cpp
@@ -1,3 +1,5 @@
+#if RADIOLIB_EXCLUDE_LR11X0 != 1
+
#include "LR1110Interface.h"
#include "configuration.h"
#include "error.h"
@@ -6,4 +8,5 @@ LR1110Interface::LR1110Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, R
RADIOLIB_PIN_TYPE busy)
: LR11x0Interface(hal, cs, irq, rst, busy)
{
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/src/mesh/LR1110Interface.h b/src/mesh/LR1110Interface.h
index 79e7c36ca..2a2e6e861 100644
--- a/src/mesh/LR1110Interface.h
+++ b/src/mesh/LR1110Interface.h
@@ -1,5 +1,5 @@
#pragma once
-
+#if RADIOLIB_EXCLUDE_LR11X0 != 1
#include "LR11x0Interface.h"
/**
@@ -10,4 +10,5 @@ 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
+};
+#endif
\ No newline at end of file
diff --git a/src/mesh/LR1120Interface.cpp b/src/mesh/LR1120Interface.cpp
index 94f3568f7..a17ac87ef 100644
--- a/src/mesh/LR1120Interface.cpp
+++ b/src/mesh/LR1120Interface.cpp
@@ -1,3 +1,5 @@
+#if RADIOLIB_EXCLUDE_LR11X0 != 1
+
#include "LR1120Interface.h"
#include "configuration.h"
#include "error.h"
@@ -6,4 +8,10 @@ LR1120Interface::LR1120Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, R
RADIOLIB_PIN_TYPE busy)
: LR11x0Interface(hal, cs, irq, rst, busy)
{
-}
\ No newline at end of file
+}
+
+bool LR1120Interface::wideLora()
+{
+ return true;
+}
+#endif
\ No newline at end of file
diff --git a/src/mesh/LR1120Interface.h b/src/mesh/LR1120Interface.h
index fc59293ec..d81a480a9 100644
--- a/src/mesh/LR1120Interface.h
+++ b/src/mesh/LR1120Interface.h
@@ -1,7 +1,7 @@
#pragma once
+#if RADIOLIB_EXCLUDE_LR11X0 != 1
#include "LR11x0Interface.h"
-
/**
* Our adapter for LR1120 wideband radios
*/
@@ -10,4 +10,6 @@ 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
+ bool wideLora() override;
+};
+#endif
\ No newline at end of file
diff --git a/src/mesh/LR1121Interface.cpp b/src/mesh/LR1121Interface.cpp
new file mode 100644
index 000000000..29bd07d08
--- /dev/null
+++ b/src/mesh/LR1121Interface.cpp
@@ -0,0 +1,16 @@
+#if RADIOLIB_EXCLUDE_LR11X0 != 1
+#include "LR1121Interface.h"
+#include "configuration.h"
+#include "error.h"
+
+LR1121Interface::LR1121Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
+ RADIOLIB_PIN_TYPE busy)
+ : LR11x0Interface(hal, cs, irq, rst, busy)
+{
+}
+
+bool LR1121Interface::wideLora()
+{
+ return true;
+}
+#endif
\ No newline at end of file
diff --git a/src/mesh/LR1121Interface.h b/src/mesh/LR1121Interface.h
new file mode 100644
index 000000000..ebc5b59a9
--- /dev/null
+++ b/src/mesh/LR1121Interface.h
@@ -0,0 +1,16 @@
+#pragma once
+#if RADIOLIB_EXCLUDE_LR11X0 != 1
+
+#include "LR11x0Interface.h"
+
+/**
+ * Our adapter for LR1121 wideband radios
+ */
+class LR1121Interface : public LR11x0Interface
+{
+ public:
+ LR1121Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
+ RADIOLIB_PIN_TYPE busy);
+ bool wideLora() override;
+};
+#endif
\ No newline at end of file
diff --git a/src/mesh/LR11x0Interface.cpp b/src/mesh/LR11x0Interface.cpp
index 8f0dc062e..d0c1a1fbc 100644
--- a/src/mesh/LR11x0Interface.cpp
+++ b/src/mesh/LR11x0Interface.cpp
@@ -1,15 +1,33 @@
+#if RADIOLIB_EXCLUDE_LR11X0 != 1
#include "LR11x0Interface.h"
+#include "Throttle.h"
#include "configuration.h"
#include "error.h"
#include "mesh/NodeDB.h"
+#ifdef LR11X0_DIO_AS_RF_SWITCH
+#include "rfswitch.h"
+#else
+static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
+static const Module::RfSwitchMode_t rfswitch_table[] = {
+ {LR11x0::MODE_STBY, {}}, {LR11x0::MODE_RX, {}}, {LR11x0::MODE_TX, {}}, {LR11x0::MODE_TX_HP, {}},
+ {LR11x0::MODE_TX_HF, {}}, {LR11x0::MODE_GNSS, {}}, {LR11x0::MODE_WIFI, {}}, END_OF_MODE_TABLE,
+};
+#endif
+
#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
+#ifndef LR1110_MAX_POWER
+#define LR1110_MAX_POWER 22
+#endif
+
+// the 2.4G part maxes at 13dBm
+
+#ifndef LR1120_MAX_POWER
+#define LR1120_MAX_POWER 13
#endif
template
@@ -17,7 +35,7 @@ LR11x0Interface::LR11x0Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs
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);
+ LOG_WARN("LR11x0Interface(cs=%d, irq=%d, rst=%d, busy=%d)", cs, irq, rst, busy);
}
/// Initialise the Driver transport hardware and software.
@@ -36,77 +54,53 @@ template bool LR11x0Interface::init()
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");
+ LOG_DEBUG("LR11X0_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage");
#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);
+ LOG_DEBUG("LR11X0_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", 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;
+ if (power > LR1110_MAX_POWER) // Clamp power to maximum defined level
+ power = LR1110_MAX_POWER;
+
+ if ((power > LR1120_MAX_POWER) &&
+ (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) { // clamp again if wide freq range
+ power = LR1120_MAX_POWER;
+ preambleLength = 12; // 12 is the default for operation above 2GHz
+ }
limitPower();
-#ifdef TRACKER_T1000_E // Tracker T1000E uses DIO5, DIO6, DIO7, DIO8 for RF switching
-
- static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_LR11X0_DIO7,
- RADIOLIB_LR11X0_DIO8, RADIOLIB_NC};
-
- static const Module::RfSwitchMode_t rfswitch_table[] = {
- // mode DIO5 DIO6 DIO7 DIO8
- {LR11x0::MODE_STBY, {LOW, LOW, LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW, LOW, HIGH}},
- {LR11x0::MODE_TX, {HIGH, HIGH, LOW, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH, LOW, HIGH}},
- {LR11x0::MODE_TX_HF, {LOW, LOW, LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW, HIGH, LOW}},
- {LR11x0::MODE_WIFI, {LOW, LOW, LOW, LOW}}, END_OF_MODE_TABLE,
- };
-
-#else
-
- // set RF switch configuration for Wio WM1110
- // Wio WM1110 uses DIO5 and DIO6 for RF switching
-
- 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,
- };
-
+#ifdef LR11X0_RF_SWITCH_SUBGHZ
+ pinMode(LR11X0_RF_SWITCH_SUBGHZ, OUTPUT);
+ digitalWrite(LR11X0_RF_SWITCH_SUBGHZ, getFreq() < 1e9 ? HIGH : LOW);
+ LOG_DEBUG("Setting RF0 switch to %s", getFreq() < 1e9 ? "SubGHz" : "2.4GHz");
#endif
-// 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;
+#ifdef LR11X0_RF_SWITCH_2_4GHZ
+ pinMode(LR11X0_RF_SWITCH_2_4GHZ, OUTPUT);
+ digitalWrite(LR11X0_RF_SWITCH_2_4GHZ, getFreq() < 1e9 ? LOW : HIGH);
+ LOG_DEBUG("Setting RF1 switch to %s", getFreq() < 1e9 ? "SubGHz" : "2.4GHz");
#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);
+ LOG_INFO("LR11x0 init result %d", 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);
+ LR11x0VersionInfo_t version;
+ res = lora.getVersionInfo(&version);
+ if (res == RADIOLIB_ERR_NONE)
+ LOG_DEBUG("LR11x0 Device %d, HW %d, FW %d.%d, WiFi %d.%d, GNSS %d.%d", version.device, version.hardware, version.fwMajor,
+ version.fwMinor, version.fwMajorWiFi, version.fwMinorWiFi, version.fwGNSS, version.almanacGNSS);
+
+ LOG_INFO("Frequency set to %f", getFreq());
+ LOG_INFO("Bandwidth set to %f", bw);
+ LOG_INFO("Power output set to %d", power);
if (res == RADIOLIB_ERR_NONE)
res = lora.setCRC(2);
@@ -114,13 +108,30 @@ template bool LR11x0Interface::init()
// 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();
+
+#ifdef LR11X0_DIO_AS_RF_SWITCH
+ bool dioAsRfSwitch = true;
+#elif defined(ARCH_PORTDUINO)
+ bool dioAsRfSwitch = false;
+ if (settingsMap[dio2_as_rf_switch]) {
+ dioAsRfSwitch = true;
+ }
+#else
+ bool dioAsRfSwitch = false;
+#endif
+
+ if (dioAsRfSwitch) {
+ lora.setRfSwitchTable(rfswitch_dio_pins, rfswitch_table);
+ LOG_DEBUG("Setting DIO RF switch", res);
+ }
+
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);
+ LOG_INFO("Set RX gain to boosted mode; result: %d", res);
} else {
res = lora.setRxBoostedGainMode(false);
- LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d\n", res);
+ LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d", res);
}
}
@@ -165,8 +176,10 @@ template bool LR11x0Interface::reconfigure()
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;
+ if (power > LR1110_MAX_POWER) // This chip has lower power limits than some
+ power = LR1110_MAX_POWER;
+ if ((power > LR1120_MAX_POWER) && (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_LORA_24)) // 2.4G power limit
+ power = LR1120_MAX_POWER;
err = lora.setOutputPower(power);
assert(err == RADIOLIB_ERR_NONE);
@@ -188,7 +201,7 @@ template void LR11x0Interface::setStandby()
int err = lora.standby();
if (err != RADIOLIB_ERR_NONE) {
- LOG_DEBUG("LR11x0 standby failed with error %d\n", err);
+ LOG_DEBUG("LR11x0 standby failed with error %d", err);
}
assert(err == RADIOLIB_ERR_NONE);
@@ -205,7 +218,7 @@ template void LR11x0Interface::setStandby()
*/
template void LR11x0Interface::addReceiveMetadata(meshtastic_MeshPacket *mp)
{
- // LOG_DEBUG("PacketStatus %x\n", lora.getPacketStatus());
+ // LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
mp->rx_snr = lora.getSNR();
mp->rx_rssi = lround(lora.getRSSI());
}
@@ -263,35 +276,14 @@ 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;
+ return receiveDetected(lora.getIrqStatus(), RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID,
+ RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED);
}
template bool LR11x0Interface::sleep()
{
// \todo Display actual typename of the adapter, not just `LR11x0`
- LOG_DEBUG("LR11x0 entering sleep mode\n");
+ LOG_DEBUG("LR11x0 entering sleep mode");
setStandby(); // Stop any pending operations
// turn off TCXO if it was powered
@@ -306,4 +298,5 @@ template bool LR11x0Interface::sleep()
#endif
return true;
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/src/mesh/LR11x0Interface.h b/src/mesh/LR11x0Interface.h
index 9272f43f0..4829ddc1d 100644
--- a/src/mesh/LR11x0Interface.h
+++ b/src/mesh/LR11x0Interface.h
@@ -1,5 +1,5 @@
#pragma once
-
+#if RADIOLIB_EXCLUDE_LR11X0 != 1
#include "RadioLibInterface.h"
/**
@@ -65,7 +65,5 @@ template class LR11x0Interface : public RadioLibInterface
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
virtual void setStandby() override;
-
- private:
- uint32_t activeReceiveStart = 0;
};
+#endif
\ No newline at end of file
diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp
index 3b137d4bd..a8de540eb 100644
--- a/src/mesh/MeshModule.cpp
+++ b/src/mesh/MeshModule.cpp
@@ -55,7 +55,7 @@ meshtastic_MeshPacket *MeshModule::allocAckNak(meshtastic_Routing_Error err, Nod
p->decoded.request_id = idFrom;
p->channel = chIndex;
if (err != meshtastic_Routing_Error_NONE)
- LOG_WARN("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x\n", err, to, idFrom, p->id);
+ LOG_WARN("Alloc an err=%d,to=0x%x,idFrom=0x%x,id=0x%x", err, to, idFrom, p->id);
return p;
}
@@ -74,7 +74,7 @@ meshtastic_MeshPacket *MeshModule::allocErrorResponse(meshtastic_Routing_Error e
void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
{
- // LOG_DEBUG("In call modules\n");
+ // LOG_DEBUG("In call modules");
bool moduleFound = false;
// We now allow **encrypted** packets to pass through the modules
@@ -86,7 +86,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
// Was this message directed to us specifically? Will be false if we are sniffing someone elses packets
auto ourNodeNum = nodeDB->getNodeNum();
- bool toUs = mp.to == NODENUM_BROADCAST || mp.to == ourNodeNum;
+ bool toUs = isBroadcast(mp.to) || isToUs(&mp);
for (auto i = modules->begin(); i != modules->end(); ++i) {
auto &pi = **i;
@@ -104,7 +104,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
assert(!pi.myReply); // If it is !null it means we have a bug, because it should have been sent the previous time
if (wantsPacket) {
- LOG_DEBUG("Module '%s' wantsPacket=%d\n", pi.name, wantsPacket);
+ LOG_DEBUG("Module '%s' wantsPacket=%d", pi.name, wantsPacket);
moduleFound = true;
@@ -141,24 +141,23 @@ 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 (isDecoded && mp.decoded.want_response && toUs && (getFrom(&mp) != ourNodeNum || mp.to == ourNodeNum) &&
- !currentReply) {
+ if (isDecoded && mp.decoded.want_response && toUs && (!isFromUs(&mp) || isToUs(&mp)) && !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);
+ LOG_INFO("Asked module '%s' to send a response", pi.name);
} else {
- LOG_DEBUG("Module '%s' considered\n", pi.name);
+ LOG_DEBUG("Module '%s' considered", pi.name);
}
// If the requester didn't ask for a response we might need to discard unused replies to prevent memory leaks
if (pi.myReply) {
- LOG_DEBUG("Discarding an unneeded response\n");
+ LOG_DEBUG("Discarding an unneeded response");
packetPool.release(pi.myReply);
pi.myReply = NULL;
}
if (handled == ProcessMessage::STOP) {
- LOG_DEBUG("Module '%s' handled and skipped other processing\n", pi.name);
+ LOG_DEBUG("Module '%s' handled and skipped other processing", pi.name);
break;
}
}
@@ -177,7 +176,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
// no response reply
// No one wanted to reply to this request, tell the requster that happened
- LOG_DEBUG("No one responded, send a nak\n");
+ LOG_DEBUG("No one responded, send a nak");
// SECURITY NOTE! I considered sending back a different error code if we didn't find the psk (i.e. !isDecoded)
// but opted NOT TO. Because it is not a good idea to let remote nodes 'probe' to find out which PSKs were "good" vs
@@ -188,8 +187,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
}
if (!moduleFound && isDecoded) {
- LOG_DEBUG("No modules interested in portnum=%d, src=%s\n", mp.decoded.portnum,
- (src == RX_SRC_LOCAL) ? "LOCAL" : "REMOTE");
+ LOG_DEBUG("No modules interested in portnum=%d, src=%s", mp.decoded.portnum, (src == RX_SRC_LOCAL) ? "LOCAL" : "REMOTE");
}
}
@@ -212,7 +210,7 @@ void MeshModule::sendResponse(const meshtastic_MeshPacket &req)
currentReply = r;
} else {
// Ignore - this is now expected behavior for routing module (because it ignores some replies)
- // LOG_WARN("Client requested response but this module did not provide\n");
+ // LOG_WARN("Client requested response but this module did not provide");
}
}
@@ -241,7 +239,7 @@ std::vector MeshModule::GetMeshModulesWithUIFrames()
for (auto i = modules->begin(); i != modules->end(); ++i) {
auto &pi = **i;
if (pi.wantUIFrame()) {
- LOG_DEBUG("Module wants a UI Frame\n");
+ LOG_DEBUG("%s wants a UI Frame", pi.name);
modulesWithUIFrames.push_back(&pi);
}
}
@@ -256,7 +254,7 @@ void MeshModule::observeUIEvents(Observer *observer)
auto &pi = **i;
Observable *observable = pi.getUIFrameObservable();
if (observable != NULL) {
- LOG_DEBUG("Module wants a UI Frame\n");
+ LOG_DEBUG("%s wants a UI Frame", pi.name);
observer->observe(observable);
}
}
@@ -274,7 +272,7 @@ AdminMessageHandleResult MeshModule::handleAdminMessageForAllModules(const mesht
AdminMessageHandleResult h = pi.handleAdminMessageForModule(mp, request, response);
if (h == AdminMessageHandleResult::HANDLED_WITH_RESPONSE) {
// In case we have a response it always has priority.
- LOG_DEBUG("Reply prepared by module '%s' of variant: %d\n", pi.name, response->which_payload_variant);
+ LOG_DEBUG("Reply prepared by module '%s' of variant: %d", pi.name, response->which_payload_variant);
handled = h;
} else if ((handled != AdminMessageHandleResult::HANDLED_WITH_RESPONSE) && (h == AdminMessageHandleResult::HANDLED)) {
// In case the message is handled it should be populated, but will not overwrite
@@ -297,4 +295,4 @@ bool MeshModule::isRequestingFocus()
} else
return false;
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/mesh/MeshModule.h b/src/mesh/MeshModule.h
index c341b301a..7929ba972 100644
--- a/src/mesh/MeshModule.h
+++ b/src/mesh/MeshModule.h
@@ -79,7 +79,8 @@ class MeshModule
meshtastic_AdminMessage *response);
#if HAS_SCREEN
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { return; }
- virtual bool isRequestingFocus(); // Checked by screen, when regenerating frameset
+ virtual bool isRequestingFocus(); // Checked by screen, when regenerating frameset
+ virtual bool interceptingKeyboardInput() { return false; } // Can screen use keyboard for nav, or is module handling input?
#endif
protected:
const char *name;
diff --git a/src/mesh/MeshPacketQueue.cpp b/src/mesh/MeshPacketQueue.cpp
index 6581b1ce4..99ef41c1e 100644
--- a/src/mesh/MeshPacketQueue.cpp
+++ b/src/mesh/MeshPacketQueue.cpp
@@ -1,4 +1,5 @@
#include "MeshPacketQueue.h"
+#include "NodeDB.h"
#include "configuration.h"
#include
@@ -16,13 +17,9 @@ bool CompareMeshPacketFunc(const meshtastic_MeshPacket *p1, const meshtastic_Mes
{
assert(p1 && p2);
auto p1p = getPriority(p1), p2p = getPriority(p2);
-
// If priorities differ, use that
- // for equal priorities, order by id (older packets have higher priority - this will briefly be wrong when IDs roll over but
- // no big deal)
- return (p1p != p2p)
- ? (p1p < p2p) // prefer bigger priorities
- : ((p1->id & ID_COUNTER_MASK) >= (p2->id & ID_COUNTER_MASK)); // Mask to counter portion, prefer smaller packet ids
+ // for equal priorities, prefer packets already on mesh.
+ return (p1p != p2p) ? (p1p > p2p) : (!isFromUs(p1) && isFromUs(p2));
}
MeshPacketQueue::MeshPacketQueue(size_t _maxLen) : maxLen(_maxLen) {}
@@ -69,8 +66,9 @@ bool MeshPacketQueue::enqueue(meshtastic_MeshPacket *p)
return replaceLowerPriorityPacket(p);
}
- queue.push_back(p);
- std::push_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc);
+ // Find the correct position using upper_bound to maintain a stable order
+ auto it = std::upper_bound(queue.begin(), queue.end(), p, CompareMeshPacketFunc);
+ queue.insert(it, p); // Insert packet at the found position
return true;
}
@@ -81,9 +79,7 @@ meshtastic_MeshPacket *MeshPacketQueue::dequeue()
}
auto *p = queue.front();
- std::pop_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc);
- queue.pop_back();
-
+ queue.erase(queue.begin()); // Remove the highest-priority packet
return p;
}
@@ -104,7 +100,6 @@ meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id)
auto p = (*it);
if (getFrom(p) == from && p->id == id) {
queue.erase(it);
- std::make_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc);
return p;
}
}
@@ -115,28 +110,21 @@ meshtastic_MeshPacket *MeshPacketQueue::remove(NodeNum from, PacketId id)
/** Attempt to find and remove a packet from this queue. Returns the packet which was removed from the queue */
bool MeshPacketQueue::replaceLowerPriorityPacket(meshtastic_MeshPacket *p)
{
- std::sort_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc); // sort ascending based on priority (0 -> 127)
- // find first packet which does not compare less (in priority) than parameter packet
- auto low = std::lower_bound(queue.begin(), queue.end(), p, &CompareMeshPacketFunc);
-
- if (low == queue.begin()) { // if already at start, there are no packets with lower priority
- return false;
+ if (queue.empty()) {
+ return false; // No packets to replace
+ }
+ // Check if the packet at the back has a lower priority than the new packet
+ auto &backPacket = queue.back();
+ if (backPacket->priority < p->priority) {
+ // Remove the back packet
+ packetPool.release(backPacket);
+ queue.pop_back();
+ // Insert the new packet in the correct order
+ enqueue(p);
+ return true;
}
- if (low == queue.end()) {
- // all priorities in the vector are smaller than the incoming packet. Replace the lowest priority (first) element
- low = queue.begin();
- } else {
- // 'low' iterator points to first packet which does not compare less than parameter
- --low; // iterate to lower priority packet
- }
-
- if (getPriority(p) > getPriority(*low)) {
- packetPool.release(*low); // deallocate and drop the packet we're replacing
- *low = p; // replace low-pri packet at this position with incoming packet with higher priority
- }
-
- std::make_heap(queue.begin(), queue.end(), &CompareMeshPacketFunc);
- return true;
+ // If the back packet's priority is not lower, no replacement occurs
+ return false;
}
\ No newline at end of file
diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp
index c833c0dec..5d36c5b1b 100644
--- a/src/mesh/MeshService.cpp
+++ b/src/mesh/MeshService.cpp
@@ -80,13 +80,16 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp)
nodeDB->updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) {
- LOG_DEBUG(
- "Received telemetry response. Skip sending our NodeInfo because this potentially a Repeater which will ignore our "
- "request for its NodeInfo.\n");
+ LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo."); // because this potentially a Repeater which will
+ // ignore our request for its NodeInfo
} else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user &&
nodeInfoModule) {
- // LOG_INFO("Heard a node on channel %d we don't know, sending NodeInfo and asking for a response.\n", mp->channel);
- // nodeInfoModule->sendOurNodeInfo(mp->from, true, mp->channel);
+ LOG_INFO("Heard new node on channel %d, sending NodeInfo and asking for a response.", mp->channel);
+ if (airTime->isTxAllowedChannelUtil(true)) {
+ nodeInfoModule->sendOurNodeInfo(mp->from, true, mp->channel);
+ } else {
+ LOG_DEBUG("Skip sending NodeInfo due to > 25 percent channel util.");
+ }
}
printPacket("Forwarding to phone", mp);
@@ -127,7 +130,7 @@ bool MeshService::reloadConfig(int saveWhat)
/// The owner User record just got updated, update our node DB and broadcast the info into the mesh
void MeshService::reloadOwner(bool shouldSave)
{
- // LOG_DEBUG("reloadOwner()\n");
+ // LOG_DEBUG("reloadOwner()");
// update our local data directly
nodeDB->updateUser(nodeDB->getNodeNum(), owner);
assert(nodeInfoModule);
@@ -177,7 +180,7 @@ void MeshService::handleToRadio(meshtastic_MeshPacket &p)
// Switch the port from PortNum_SIMULATOR_APP back to the original PortNum
p.decoded.portnum = decoded->portnum;
} else
- LOG_ERROR("Error decoding protobuf for simulator message!\n");
+ LOG_ERROR("Error decoding protobuf for simulator message!");
}
// Let SimRadio receive as if it did via its LoRa chip
SimRadio::instance->startReceive(&p);
@@ -219,7 +222,7 @@ ErrorCode MeshService::sendQueueStatusToPhone(const meshtastic_QueueStatus &qs,
copied->mesh_packet_id = mesh_packet_id;
if (toPhoneQueueStatusQueue.numFree() == 0) {
- LOG_DEBUG("NOTE: tophone queue status queue is full, discarding oldest\n");
+ LOG_INFO("tophone queue status queue is full, discarding oldest");
meshtastic_QueueStatus *d = toPhoneQueueStatusQueue.dequeuePtr(0);
if (d)
releaseQueueStatusToPool(d);
@@ -263,14 +266,14 @@ bool MeshService::trySendPosition(NodeNum dest, bool wantReplies)
if (hasValidPosition(node)) {
#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS
if (positionModule) {
- LOG_INFO("Sending position ping to 0x%x, wantReplies=%d, channel=%d\n", dest, wantReplies, node->channel);
+ LOG_INFO("Sending position ping to 0x%x, wantReplies=%d, channel=%d", dest, wantReplies, node->channel);
positionModule->sendOurPosition(dest, wantReplies, node->channel);
return true;
}
} else {
#endif
if (nodeInfoModule) {
- LOG_INFO("Sending nodeinfo ping to 0x%x, wantReplies=%d, channel=%d\n", dest, wantReplies, node->channel);
+ LOG_INFO("Sending nodeinfo ping to 0x%x, wantReplies=%d, channel=%d", dest, wantReplies, node->channel);
nodeInfoModule->sendOurNodeInfo(dest, wantReplies, node->channel);
}
}
@@ -295,12 +298,12 @@ void MeshService::sendToPhone(meshtastic_MeshPacket *p)
if (toPhoneQueue.numFree() == 0) {
if (p->decoded.portnum == meshtastic_PortNum_TEXT_MESSAGE_APP ||
p->decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP) {
- LOG_WARN("ToPhone queue is full, discarding oldest\n");
+ LOG_WARN("ToPhone queue is full, discarding oldest");
meshtastic_MeshPacket *d = toPhoneQueue.dequeuePtr(0);
if (d)
releaseToPool(d);
} else {
- LOG_WARN("ToPhone queue is full, dropping packet.\n");
+ LOG_WARN("ToPhone queue is full, dropping packet.");
releaseToPool(p);
fromNum++; // Make sure to notify observers in case they are reconnected so they can get the packets
return;
@@ -313,9 +316,9 @@ void MeshService::sendToPhone(meshtastic_MeshPacket *p)
void MeshService::sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m)
{
- LOG_DEBUG("Sending mqtt message on topic '%s' to client for proxying to server\n", m->topic);
+ LOG_DEBUG("Sending mqtt message on topic '%s' to client for proxy", m->topic);
if (toPhoneMqttProxyQueue.numFree() == 0) {
- LOG_WARN("MqttClientProxyMessagePool queue is full, discarding oldest\n");
+ LOG_WARN("MqttClientProxyMessagePool queue is full, discarding oldest");
meshtastic_MqttClientProxyMessage *d = toPhoneMqttProxyQueue.dequeuePtr(0);
if (d)
releaseMqttClientProxyMessageToPool(d);
@@ -327,9 +330,9 @@ void MeshService::sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage
void MeshService::sendClientNotification(meshtastic_ClientNotification *n)
{
- LOG_DEBUG("Sending client notification to phone\n");
+ LOG_DEBUG("Sending client notification to phone");
if (toPhoneClientNotificationQueue.numFree() == 0) {
- LOG_WARN("ClientNotification queue is full, discarding oldest\n");
+ LOG_WARN("ClientNotification queue is full, discarding oldest");
meshtastic_ClientNotification *d = toPhoneClientNotificationQueue.dequeuePtr(0);
if (d)
releaseClientNotificationToPool(d);
@@ -378,12 +381,12 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus)
} else {
// The GPS has lost lock
#ifdef GPS_EXTRAVERBOSE
- LOG_DEBUG("onGPSchanged() - lost validLocation\n");
+ LOG_DEBUG("onGPSchanged() - lost validLocation");
#endif
}
// Used fixed position if configured regardless of GPS lock
if (config.position.fixed_position) {
- LOG_WARN("Using fixed position\n");
+ LOG_WARN("Using fixed position");
pos = TypeConversions::ConvertToPosition(node->position);
}
@@ -391,7 +394,7 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus)
pos.time = getValidTime(RTCQualityFromNet);
// In debug logs, identify position by @timestamp:stage (stage 4 = nodeDB)
- LOG_DEBUG("onGPSChanged() pos@%x time=%u lat=%d lon=%d alt=%d\n", pos.timestamp, pos.time, pos.latitude_i, pos.longitude_i,
+ LOG_DEBUG("onGPSChanged() pos@%x time=%u lat=%d lon=%d alt=%d", pos.timestamp, pos.time, pos.latitude_i, pos.longitude_i,
pos.altitude);
// Update our current position in the local DB
@@ -403,4 +406,16 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus)
bool MeshService::isToPhoneQueueEmpty()
{
return toPhoneQueue.isEmpty();
+}
+
+uint32_t MeshService::GetTimeSinceMeshPacket(const meshtastic_MeshPacket *mp)
+{
+ uint32_t now = getTime();
+
+ uint32_t last_seen = mp->rx_time;
+ int delta = (int)(now - last_seen);
+ if (delta < 0) // our clock must be slightly off still - not set from GPS yet
+ delta = 0;
+
+ return delta;
}
\ No newline at end of file
diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h
index ea1c4e345..1ccca4e6d 100644
--- a/src/mesh/MeshService.h
+++ b/src/mesh/MeshService.h
@@ -13,9 +13,9 @@
#if defined(ARCH_PORTDUINO) && !HAS_RADIO
#include "../platform/portduino/SimRadio.h"
#endif
-#ifdef ARCH_ESP32
+#if defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
-#include "modules/esp32/StoreForwardModule.h"
+#include "modules/StoreForwardModule.h"
#endif
#endif
@@ -92,6 +92,9 @@ class MeshService
/// Return the next MqttClientProxyMessage packet destined to the phone.
meshtastic_MqttClientProxyMessage *getMqttClientProxyMessageForPhone() { return toPhoneMqttProxyQueue.dequeuePtr(0); }
+ /// Return the next ClientNotification packet destined to the phone.
+ meshtastic_ClientNotification *getClientNotificationForPhone() { return toPhoneClientNotificationQueue.dequeuePtr(0); }
+
// search the queue for a request id and return the matching nodenum
NodeNum getNodenumFromRequestId(uint32_t request_id);
@@ -148,6 +151,8 @@ class MeshService
ErrorCode sendQueueStatusToPhone(const meshtastic_QueueStatus &qs, ErrorCode res, uint32_t mesh_packet_id);
+ uint32_t GetTimeSinceMeshPacket(const meshtastic_MeshPacket *mp);
+
private:
#if HAS_GPS
/// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh
@@ -160,4 +165,4 @@ class MeshService
friend class RoutingModule;
};
-extern MeshService *service;
\ No newline at end of file
+extern MeshService *service;
diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h
index e8187b4c1..db7d4f9b7 100644
--- a/src/mesh/MeshTypes.h
+++ b/src/mesh/MeshTypes.h
@@ -53,5 +53,13 @@ extern Allocator &packetPool;
*/
NodeNum getFrom(const meshtastic_MeshPacket *p);
+// Returns true if the packet originated from the local node
+bool isFromUs(const meshtastic_MeshPacket *p);
+
+// Returns true if the packet is destined to us
+bool isToUs(const meshtastic_MeshPacket *p);
+
/* Some clients might not properly set priority, therefore we fix it here. */
-void fixPriority(meshtastic_MeshPacket *p);
\ No newline at end of file
+void fixPriority(meshtastic_MeshPacket *p);
+
+bool isBroadcast(uint32_t dest);
\ No newline at end of file
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index cb333edf9..6b2991958 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -32,12 +32,18 @@
#if HAS_WIFI
#include "mesh/wifi/WiFiAPClient.h"
#endif
-#include "modules/esp32/StoreForwardModule.h"
+#include "SPILock.h"
+#include "modules/StoreForwardModule.h"
#include
+#include
+#include
#include
+#include
+#include
#endif
#ifdef ARCH_PORTDUINO
+#include "modules/StoreForwardModule.h"
#include "platform/portduino/PortduinoGlue.h"
#endif
@@ -49,13 +55,11 @@
NodeDB *nodeDB = nullptr;
// we have plenty of ram so statically alloc this tempbuf (for now)
-EXT_RAM_ATTR meshtastic_DeviceState devicestate;
+EXT_RAM_BSS_ATTR meshtastic_DeviceState devicestate;
meshtastic_MyNodeInfo &myNodeInfo = devicestate.my_node;
meshtastic_LocalConfig config;
meshtastic_LocalModuleConfig moduleConfig;
meshtastic_ChannelFile channelFile;
-meshtastic_OEMStore oemStore;
-static bool hasOemStore = false;
bool meshtastic_DeviceState_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field)
{
@@ -101,7 +105,7 @@ static uint8_t ourMacAddr[6];
NodeDB::NodeDB()
{
- LOG_INFO("Initializing NodeDB\n");
+ LOG_INFO("Initializing NodeDB");
loadFromDisk();
cleanupMeshDB();
@@ -110,6 +114,43 @@ NodeDB::NodeDB()
uint32_t channelFileCRC = crc32Buffer(&channelFile, sizeof(channelFile));
int saveWhat = 0;
+ bool hasUniqueId = false;
+ // Get device unique id
+#if defined(ARCH_ESP32) && defined(ESP_EFUSE_OPTIONAL_UNIQUE_ID)
+ uint32_t unique_id[4];
+ // ESP32 factory burns a unique id in efuse for S2+ series and evidently C3+ series
+ // This is used for HMACs in the esp-rainmaker AIOT platform and seems to be a good choice for us
+ esp_err_t err = esp_efuse_read_field_blob(ESP_EFUSE_OPTIONAL_UNIQUE_ID, unique_id, sizeof(unique_id) * 8);
+ if (err == ESP_OK) {
+ memcpy(myNodeInfo.device_id.bytes, unique_id, sizeof(unique_id));
+ myNodeInfo.device_id.size = 16;
+ hasUniqueId = true;
+ } else {
+ LOG_WARN("Failed to read unique id from efuse");
+ }
+#elif defined(ARCH_NRF52)
+ // Nordic applies a FIPS compliant Random ID to each chip at the factory
+ // We concatenate the device address to the Random ID to create a unique ID for now
+ // This will likely utilize a crypto module in the future
+ uint64_t device_id_start = ((uint64_t)NRF_FICR->DEVICEID[1] << 32) | NRF_FICR->DEVICEID[0];
+ uint64_t device_id_end = ((uint64_t)NRF_FICR->DEVICEADDR[1] << 32) | NRF_FICR->DEVICEADDR[0];
+ memcpy(myNodeInfo.device_id.bytes, &device_id_start, sizeof(device_id_start));
+ memcpy(myNodeInfo.device_id.bytes + sizeof(device_id_start), &device_id_end, sizeof(device_id_end));
+ myNodeInfo.device_id.size = 16;
+ hasUniqueId = true;
+#else
+ // FIXME - implement for other platforms
+#endif
+ // Uncomment below to print the device id
+ // if (hasUniqueId) {
+ // std::string deviceIdHex;
+ // for (size_t i = 0; i < myNodeInfo.device_id.size; ++i) {
+ // char buf[3];
+ // snprintf(buf, sizeof(buf), "%02X", myNodeInfo.device_id.bytes[i]);
+ // deviceIdHex += buf;
+ // }
+ // LOG_DEBUG("Device ID (HEX): %s", deviceIdHex.c_str());
+ // }
// likewise - we always want the app requirements to come from the running appload
myNodeInfo.min_app_version = 30200; // format is Mmmss (where M is 1+the numeric major number. i.e. 30200 means 2.2.00
@@ -121,6 +162,8 @@ NodeDB::NodeDB()
owner.hw_model = HW_VENDOR;
// Ensure user (nodeinfo) role is set to whatever we're configured to
owner.role = config.device.role;
+ // Ensure macaddr is set to our macaddr as it will be copied in our info below
+ memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
// Include our owner in the node db under our nodenum
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum());
@@ -129,39 +172,33 @@ NodeDB::NodeDB()
config.security.serial_enabled = config.device.serial_enabled;
config.security.is_managed = config.device.is_managed;
}
-#if !(MESHTASTIC_EXCLUDE_PKI)
- // Calculate Curve25519 public and private keys
- printBytes("Old Pubkey", config.security.public_key.bytes, 32);
- if (config.security.private_key.size == 32 && config.security.public_key.size == 32) {
- LOG_INFO("Using saved PKI keys\n");
- owner.public_key.size = config.security.public_key.size;
- memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size);
- crypto->setDHPrivateKey(config.security.private_key.bytes);
- } else {
-#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN)
+
+#if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
+ if (!owner.is_licensed) {
bool keygenSuccess = false;
if (config.security.private_key.size == 32) {
- LOG_INFO("Calculating PKI Public Key\n");
if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) {
keygenSuccess = true;
}
} else {
- LOG_INFO("Generating new PKI keys\n");
+ LOG_INFO("Generating new PKI keys");
crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
keygenSuccess = true;
}
if (keygenSuccess) {
config.security.public_key.size = 32;
config.security.private_key.size = 32;
- printBytes("New Pubkey", config.security.public_key.bytes, 32);
owner.public_key.size = 32;
memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
}
-#else
- LOG_INFO("No PKI keys set, and generation disabled!\n");
-#endif
}
-
+#elif !(MESHTASTIC_EXCLUDE_PKI)
+ // Calculate Curve25519 public and private keys
+ if (config.security.private_key.size == 32 && config.security.public_key.size == 32) {
+ owner.public_key.size = config.security.public_key.size;
+ memcpy(owner.public_key.bytes, config.security.public_key.bytes, config.security.public_key.size);
+ crypto->setDHPrivateKey(config.security.private_key.bytes);
+ }
#endif
info->user = TypeConversions::ConvertToUserLite(owner);
@@ -172,11 +209,27 @@ NodeDB::NodeDB()
preferences.begin("meshtastic", false);
myNodeInfo.reboot_count = preferences.getUInt("rebootCounter", 0);
preferences.end();
- LOG_DEBUG("Number of Device Reboots: %d\n", myNodeInfo.reboot_count);
+ LOG_DEBUG("Number of Device Reboots: %d", myNodeInfo.reboot_count);
#endif
resetRadioConfig(); // If bogus settings got saved, then fix them
- // nodeDB->LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d\n", config.lora.region, myNodeInfo.my_node_num, numMeshNodes);
+ // nodeDB->LOG_DEBUG("region=%d, NODENUM=0x%x, dbsize=%d", config.lora.region, myNodeInfo.my_node_num, numMeshNodes);
+
+ // If we are setup to broadcast on the default channel, ensure that the telemetry intervals are coerced to the minimum value
+ // of 30 minutes or more
+ if (channels.isDefaultChannel(channels.getPrimaryIndex())) {
+ LOG_DEBUG("Coercing telemetry to min of 30 minutes on defaults");
+ moduleConfig.telemetry.device_update_interval = Default::getConfiguredOrMinimumValue(
+ moduleConfig.telemetry.device_update_interval, min_default_telemetry_interval_secs);
+ moduleConfig.telemetry.environment_update_interval = Default::getConfiguredOrMinimumValue(
+ moduleConfig.telemetry.environment_update_interval, min_default_telemetry_interval_secs);
+ moduleConfig.telemetry.air_quality_interval = Default::getConfiguredOrMinimumValue(
+ moduleConfig.telemetry.air_quality_interval, min_default_telemetry_interval_secs);
+ moduleConfig.telemetry.power_update_interval = Default::getConfiguredOrMinimumValue(
+ moduleConfig.telemetry.power_update_interval, min_default_telemetry_interval_secs);
+ moduleConfig.telemetry.health_update_interval = Default::getConfiguredOrMinimumValue(
+ moduleConfig.telemetry.health_update_interval, min_default_telemetry_interval_secs);
+ }
if (devicestateCRC != crc32Buffer(&devicestate, sizeof(devicestate)))
saveWhat |= SEGMENT_DEVICESTATE;
@@ -201,6 +254,23 @@ NodeNum getFrom(const meshtastic_MeshPacket *p)
return (p->from == 0) ? nodeDB->getNodeNum() : p->from;
}
+// Returns true if the packet originated from the local node
+bool isFromUs(const meshtastic_MeshPacket *p)
+{
+ return p->from == 0 || p->from == nodeDB->getNodeNum();
+}
+
+// Returns true if the packet is destined to us
+bool isToUs(const meshtastic_MeshPacket *p)
+{
+ return p->to == nodeDB->getNodeNum();
+}
+
+bool isBroadcast(uint32_t dest)
+{
+ return dest == NODENUM_BROADCAST || dest == NODENUM_BROADCAST_NO_LORA;
+}
+
bool NodeDB::resetRadioConfig(bool factory_reset)
{
bool didFactoryReset = false;
@@ -212,7 +282,7 @@ bool NodeDB::resetRadioConfig(bool factory_reset)
}
if (channelFile.channels_count != MAX_NUM_CHANNELS) {
- LOG_INFO("Setting default channel and radio preferences!\n");
+ LOG_INFO("Setting default channel and radio preferences!");
channels.initDefaults();
}
@@ -233,30 +303,30 @@ bool NodeDB::resetRadioConfig(bool factory_reset)
bool NodeDB::factoryReset(bool eraseBleBonds)
{
- LOG_INFO("Performing factory reset!\n");
+ LOG_INFO("Performing factory reset!");
// first, remove the "/prefs" (this removes most prefs)
rmDir("/prefs");
#ifdef FSCom
if (FSCom.exists("/static/rangetest.csv") && !FSCom.remove("/static/rangetest.csv")) {
- LOG_ERROR("Could not remove rangetest.csv file\n");
+ LOG_ERROR("Could not remove rangetest.csv file");
}
#endif
// second, install default state (this will deal with the duplicate mac address issue)
installDefaultDeviceState();
- installDefaultConfig();
+ installDefaultConfig(!eraseBleBonds); // Also preserve the private key if we're not erasing BLE bonds
installDefaultModuleConfig();
installDefaultChannels();
// third, write everything to disk
saveToDisk();
if (eraseBleBonds) {
- LOG_INFO("Erasing BLE bonds\n");
+ LOG_INFO("Erasing BLE bonds");
#ifdef ARCH_ESP32
// This will erase what's in NVS including ssl keys, persistent variables and ble pairing
nvs_flash_erase();
#endif
#ifdef ARCH_NRF52
Bluefruit.begin();
- LOG_INFO("Clearing bluetooth bonds!\n");
+ LOG_INFO("Clearing bluetooth bonds!");
bond_print_list(BLE_GAP_ROLE_PERIPH);
bond_print_list(BLE_GAP_ROLE_CENTRAL);
Bluefruit.Periph.clearBonds();
@@ -266,9 +336,14 @@ bool NodeDB::factoryReset(bool eraseBleBonds)
return true;
}
-void NodeDB::installDefaultConfig()
+void NodeDB::installDefaultConfig(bool preserveKey = false)
{
- LOG_INFO("Installing default LocalConfig\n");
+ uint8_t private_key_temp[32];
+ bool shouldPreserveKey = preserveKey && config.has_security && config.security.private_key.size > 0;
+ if (shouldPreserveKey) {
+ memcpy(private_key_temp, config.security.private_key.bytes, config.security.private_key.size);
+ }
+ LOG_INFO("Installing default LocalConfig");
memset(&config, 0, sizeof(meshtastic_LocalConfig));
config.version = DEVICESTATE_CUR_VER;
config.has_device = true;
@@ -285,30 +360,36 @@ void NodeDB::installDefaultConfig()
config.lora.tx_enabled =
true; // FIXME: maybe false in the future, and setting region to enable it. (unset region forces it off)
config.lora.override_duty_cycle = false;
-#ifdef CONFIG_LORA_REGION_USERPREFS
- config.lora.region = CONFIG_LORA_REGION_USERPREFS;
+ config.lora.config_ok_to_mqtt = false;
+#ifdef USERPREFS_CONFIG_LORA_REGION
+ config.lora.region = USERPREFS_CONFIG_LORA_REGION;
#else
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_UNSET;
#endif
-#ifdef LORACONFIG_MODEM_PRESET_USERPREFS
- config.lora.modem_preset = LORACONFIG_MODEM_PRESET_USERPREFS;
+#ifdef USERPREFS_LORACONFIG_MODEM_PRESET
+ config.lora.modem_preset = USERPREFS_LORACONFIG_MODEM_PRESET;
#else
config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST;
#endif
config.lora.hop_limit = HOP_RELIABLE;
-#ifdef CONFIG_LORA_IGNORE_MQTT_USERPREFS
- config.lora.ignore_mqtt = CONFIG_LORA_IGNORE_MQTT_USERPREFS;
+#ifdef USERPREFS_CONFIG_LORA_IGNORE_MQTT
+ config.lora.ignore_mqtt = USERPREFS_CONFIG_LORA_IGNORE_MQTT;
#else
config.lora.ignore_mqtt = false;
#endif
-#ifdef ADMIN_KEY_USERPREFS
- memcpy(config.security.admin_key[0].bytes, admin_key_userprefs, 32);
+#ifdef USERPREFS_USE_ADMIN_KEY
+ memcpy(config.security.admin_key[0].bytes, USERPREFS_ADMIN_KEY, 32);
config.security.admin_key[0].size = 32;
-#else
- config.security.admin_key[0].size = 0;
+ config.security.admin_key_count = 1;
#endif
+ if (shouldPreserveKey) {
+ config.security.private_key.size = 32;
+ memcpy(config.security.private_key.bytes, private_key_temp, config.security.private_key.size);
+ printBytes("Restored key", config.security.private_key.bytes, config.security.private_key.size);
+ } else {
+ config.security.private_key.size = 0;
+ }
config.security.public_key.size = 0;
- config.security.private_key.size = 0;
#ifdef PIN_GPS_EN
config.position.gps_en_gpio = PIN_GPS_EN;
#endif
@@ -317,7 +398,9 @@ void NodeDB::installDefaultConfig()
#else
config.device.disable_triple_click = true;
#endif
-#if !HAS_GPS || defined(T_DECK) || defined(TLORA_T3S3_EPAPER)
+#if defined(USERPREFS_CONFIG_GPS_MODE)
+ config.position.gps_mode = USERPREFS_CONFIG_GPS_MODE;
+#elif !HAS_GPS || defined(T_DECK) || defined(TLORA_T3S3_EPAPER)
config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT;
#elif !defined(GPS_RX_PIN)
if (config.position.rx_gpio == 0)
@@ -339,8 +422,8 @@ void NodeDB::installDefaultConfig()
// 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) || defined(HX8357_CS) || \
- defined(USE_ST7789)
+#if defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || defined(ST7789_CS) || \
+ defined(HX8357_CS) || defined(USE_ST7789)
bool hasScreen = true;
#elif ARCH_PORTDUINO
bool hasScreen = false;
@@ -362,6 +445,9 @@ void NodeDB::installDefaultConfig()
#ifdef DISPLAY_FLIP_SCREEN
config.display.flip_screen = true;
#endif
+#ifdef RAK4630
+ config.display.wake_on_tap_or_motion = true;
+#endif
#ifdef T_WATCH_S3
config.display.screen_on_secs = 30;
config.display.wake_on_tap_or_motion = true;
@@ -395,7 +481,7 @@ void NodeDB::initConfigIntervals()
void NodeDB::installDefaultModuleConfig()
{
- LOG_INFO("Installing default ModuleConfig\n");
+ LOG_INFO("Installing default ModuleConfig");
memset(&moduleConfig, 0, sizeof(meshtastic_ModuleConfig));
moduleConfig.version = DEVICESTATE_CUR_VER;
@@ -455,7 +541,7 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.has_detection_sensor = true;
moduleConfig.detection_sensor.enabled = false;
- moduleConfig.detection_sensor.detection_triggered_high = true;
+ moduleConfig.detection_sensor.detection_trigger_type = meshtastic_ModuleConfig_DetectionSensorConfig_TriggerType_LOGIC_HIGH;
moduleConfig.detection_sensor.minimum_broadcast_secs = 45;
moduleConfig.has_ambient_lighting = true;
@@ -473,8 +559,10 @@ void NodeDB::installRoleDefaults(meshtastic_Config_DeviceConfig_Role role)
if (role == meshtastic_Config_DeviceConfig_Role_ROUTER) {
initConfigIntervals();
initModuleConfigIntervals();
+ config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY;
} else if (role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
config.display.screen_on_secs = 1;
+ config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY;
} else if (role == meshtastic_Config_DeviceConfig_Role_SENSOR) {
moduleConfig.telemetry.environment_measurement_enabled = true;
moduleConfig.telemetry.environment_update_interval = 300;
@@ -510,6 +598,7 @@ void NodeDB::installRoleDefaults(meshtastic_Config_DeviceConfig_Role role)
moduleConfig.telemetry.device_update_interval = UINT32_MAX;
moduleConfig.telemetry.environment_update_interval = UINT32_MAX;
moduleConfig.telemetry.air_quality_interval = UINT32_MAX;
+ moduleConfig.telemetry.health_update_interval = UINT32_MAX;
}
}
@@ -520,22 +609,26 @@ void NodeDB::initModuleConfigIntervals()
moduleConfig.telemetry.environment_update_interval = 0;
moduleConfig.telemetry.air_quality_interval = 0;
moduleConfig.telemetry.power_update_interval = 0;
+ moduleConfig.telemetry.health_update_interval = 0;
moduleConfig.neighbor_info.update_interval = 0;
moduleConfig.paxcounter.paxcounter_update_interval = 0;
}
void NodeDB::installDefaultChannels()
{
- LOG_INFO("Installing default ChannelFile\n");
+ LOG_INFO("Installing default ChannelFile");
memset(&channelFile, 0, sizeof(meshtastic_ChannelFile));
channelFile.version = DEVICESTATE_CUR_VER;
}
void NodeDB::resetNodes()
{
- clearLocalPosition();
+ if (!config.position.fixed_position)
+ clearLocalPosition();
numMeshNodes = 1;
std::fill(devicestate.node_db_lite.begin() + 1, devicestate.node_db_lite.end(), meshtastic_NodeInfoLite());
+ devicestate.has_rx_text_message = false;
+ devicestate.has_rx_waypoint = false;
saveDeviceStateToDisk();
if (neighborInfoModule && moduleConfig.neighbor_info.enabled)
neighborInfoModule->resetNeighbors();
@@ -553,7 +646,7 @@ void NodeDB::removeNodeByNum(NodeNum nodeNum)
numMeshNodes -= removed;
std::fill(devicestate.node_db_lite.begin() + numMeshNodes, devicestate.node_db_lite.begin() + numMeshNodes + 1,
meshtastic_NodeInfoLite());
- LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Saving changes...\n", removed);
+ LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Saving changes...", removed);
saveDeviceStateToDisk();
}
@@ -585,12 +678,12 @@ void NodeDB::cleanupMeshDB()
numMeshNodes -= removed;
std::fill(devicestate.node_db_lite.begin() + numMeshNodes, devicestate.node_db_lite.begin() + numMeshNodes + removed,
meshtastic_NodeInfoLite());
- LOG_DEBUG("cleanupMeshDB purged %d entries\n", removed);
+ LOG_DEBUG("cleanupMeshDB purged %d entries", removed);
}
void NodeDB::installDefaultDeviceState()
{
- LOG_INFO("Installing default DeviceState\n");
+ LOG_INFO("Installing default DeviceState");
// memset(&devicestate, 0, sizeof(meshtastic_DeviceState));
numMeshNodes = 0;
@@ -602,18 +695,20 @@ void NodeDB::installDefaultDeviceState()
// devicestate.node_db_lite_count = 0;
devicestate.version = DEVICESTATE_CUR_VER;
devicestate.receive_queue_count = 0; // Not yet implemented FIXME
+ devicestate.has_rx_waypoint = false;
+ devicestate.has_rx_text_message = false;
generatePacketId(); // FIXME - ugly way to init current_packet_id;
// Set default owner name
pickNewNodeNum(); // based on macaddr now
-#ifdef CONFIG_OWNER_LONG_NAME_USERPREFS
- snprintf(owner.long_name, sizeof(owner.long_name), CONFIG_OWNER_LONG_NAME_USERPREFS);
+#ifdef USERPREFS_CONFIG_OWNER_LONG_NAME
+ snprintf(owner.long_name, sizeof(owner.long_name), USERPREFS_CONFIG_OWNER_LONG_NAME);
#else
snprintf(owner.long_name, sizeof(owner.long_name), "Meshtastic %02x%02x", ourMacAddr[4], ourMacAddr[5]);
#endif
-#ifdef CONFIG_OWNER_SHORT_NAME_USERPREFS
- snprintf(owner.short_name, sizeof(owner.short_name), CONFIG_OWNER_SHORT_NAME_USERPREFS);
+#ifdef USERPREFS_CONFIG_OWNER_SHORT_NAME
+ snprintf(owner.short_name, sizeof(owner.short_name), USERPREFS_CONFIG_OWNER_SHORT_NAME);
#else
snprintf(owner.short_name, sizeof(owner.short_name), "%02x%02x", ourMacAddr[4], ourMacAddr[5]);
#endif
@@ -637,13 +732,16 @@ void NodeDB::pickNewNodeNum()
}
meshtastic_NodeInfoLite *found;
- while ((nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED) ||
- ((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0)) {
+ while (((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0) ||
+ (nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED)) {
NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice
- LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, so trying for 0x%x\n", nodeNum, candidate);
+ if (found)
+ LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, by MAC ending in 0x%02x%02x vs our 0x%02x%02x, so "
+ "trying for 0x%x",
+ nodeNum, found->user.macaddr[4], found->user.macaddr[5], ourMacAddr[4], ourMacAddr[5], candidate);
nodeNum = candidate;
}
- LOG_DEBUG("Using nodenum 0x%x \n", nodeNum);
+ LOG_DEBUG("Using nodenum 0x%x ", nodeNum);
myNodeInfo.my_node_num = nodeNum;
}
@@ -652,7 +750,6 @@ static const char *prefFileName = "/prefs/db.proto";
static const char *configFileName = "/prefs/config.proto";
static const char *moduleConfigFileName = "/prefs/module.proto";
static const char *channelFileName = "/prefs/channels.proto";
-static const char *oemConfigFile = "/oem/oem.proto";
/** Load a protobuf from a file, return LoadFileResult */
LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t objSize, const pb_msgdesc_t *fields,
@@ -664,23 +761,23 @@ LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t
auto f = FSCom.open(filename, FILE_O_READ);
if (f) {
- LOG_INFO("Loading %s\n", filename);
+ LOG_INFO("Loading %s", filename);
pb_istream_t stream = {&readcb, &f, protoSize};
memset(dest_struct, 0, objSize);
if (!pb_decode(&stream, fields, dest_struct)) {
- LOG_ERROR("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
+ LOG_ERROR("Error: can't decode protobuf %s", PB_GET_ERROR(&stream));
state = LoadFileResult::DECODE_FAILED;
} else {
- LOG_INFO("Loaded %s successfully\n", filename);
+ LOG_INFO("Loaded %s successfully", filename);
state = LoadFileResult::LOAD_SUCCESS;
}
f.close();
} else {
- LOG_ERROR("Could not open / read %s\n", filename);
+ LOG_ERROR("Could not open / read %s", filename);
}
#else
- LOG_ERROR("ERROR: Filesystem not implemented\n");
+ LOG_ERROR("ERROR: Filesystem not implemented");
state = LoadFileResult::NO_FILESYSTEM;
#endif
return state;
@@ -705,11 +802,10 @@ void NodeDB::loadFromDisk()
// installDefaultDeviceState(); // Our in RAM copy might now be corrupt
//} else {
if (devicestate.version < DEVICESTATE_MIN_VER) {
- LOG_WARN("Devicestate %d is old, discarding\n", devicestate.version);
- factoryReset();
+ LOG_WARN("Devicestate %d is old, discarding", devicestate.version);
+ installDefaultDeviceState();
} else {
- LOG_INFO("Loaded saved devicestate version %d, with nodecount: %d\n", devicestate.version,
- devicestate.node_db_lite.size());
+ LOG_INFO("Loaded saved devicestate version %d, with nodecount: %d", devicestate.version, devicestate.node_db_lite.size());
meshNodes = &devicestate.node_db_lite;
numMeshNodes = devicestate.node_db_lite.size();
}
@@ -721,10 +817,10 @@ void NodeDB::loadFromDisk()
installDefaultConfig(); // Our in RAM copy might now be corrupt
} else {
if (config.version < DEVICESTATE_MIN_VER) {
- LOG_WARN("config %d is old, discarding\n", config.version);
- installDefaultConfig();
+ LOG_WARN("config %d is old, discarding", config.version);
+ installDefaultConfig(true);
} else {
- LOG_INFO("Loaded saved config version %d\n", config.version);
+ LOG_INFO("Loaded saved config version %d", config.version);
}
}
@@ -734,10 +830,10 @@ void NodeDB::loadFromDisk()
installDefaultModuleConfig(); // Our in RAM copy might now be corrupt
} else {
if (moduleConfig.version < DEVICESTATE_MIN_VER) {
- LOG_WARN("moduleConfig %d is old, discarding\n", moduleConfig.version);
+ LOG_WARN("moduleConfig %d is old, discarding", moduleConfig.version);
installDefaultModuleConfig();
} else {
- LOG_INFO("Loaded saved moduleConfig version %d\n", moduleConfig.version);
+ LOG_INFO("Loaded saved moduleConfig version %d", moduleConfig.version);
}
}
@@ -747,22 +843,16 @@ void NodeDB::loadFromDisk()
installDefaultChannels(); // Our in RAM copy might now be corrupt
} else {
if (channelFile.version < DEVICESTATE_MIN_VER) {
- LOG_WARN("channelFile %d is old, discarding\n", channelFile.version);
+ LOG_WARN("channelFile %d is old, discarding", channelFile.version);
installDefaultChannels();
} else {
- LOG_INFO("Loaded saved channelFile version %d\n", channelFile.version);
+ LOG_INFO("Loaded saved channelFile version %d", channelFile.version);
}
}
- state = loadProto(oemConfigFile, meshtastic_OEMStore_size, sizeof(meshtastic_OEMStore), &meshtastic_OEMStore_msg, &oemStore);
- if (state == LoadFileResult::LOAD_SUCCESS) {
- LOG_INFO("Loaded OEMStore\n");
- hasOemStore = true;
- }
-
// 2.4.X - configuration migration to update new default intervals
if (moduleConfig.version < 23) {
- LOG_DEBUG("ModuleConfig version %d is stale, upgrading to new default intervals\n", moduleConfig.version);
+ LOG_DEBUG("ModuleConfig version %d is stale, upgrading to new default intervals", moduleConfig.version);
moduleConfig.version = DEVICESTATE_CUR_VER;
if (moduleConfig.telemetry.device_update_interval == 900)
moduleConfig.telemetry.device_update_interval = 0;
@@ -785,15 +875,18 @@ void NodeDB::loadFromDisk()
bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_t *fields, const void *dest_struct,
bool fullAtomic)
{
+#ifdef ARCH_ESP32
+ concurrency::LockGuard g(spiLock);
+#endif
bool okay = false;
#ifdef FSCom
auto f = SafeFile(filename, fullAtomic);
- LOG_INFO("Saving %s\n", filename);
+ LOG_INFO("Saving %s", filename);
pb_ostream_t stream = {&writecb, static_cast(&f), protoSize};
if (!pb_encode(&stream, fields, dest_struct)) {
- LOG_ERROR("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream));
+ LOG_ERROR("Error: can't encode protobuf %s", PB_GET_ERROR(&stream));
} else {
okay = true;
}
@@ -801,10 +894,10 @@ bool NodeDB::saveProto(const char *filename, size_t protoSize, const pb_msgdesc_
bool writeSucceeded = f.close();
if (!okay || !writeSucceeded) {
- LOG_ERROR("Can't write prefs!\n");
+ LOG_ERROR("Can't write prefs!");
}
#else
- LOG_ERROR("ERROR: Filesystem not implemented\n");
+ LOG_ERROR("ERROR: Filesystem not implemented");
#endif
return okay;
}
@@ -866,11 +959,6 @@ bool NodeDB::saveToDiskNoRetry(int saveWhat)
saveProto(moduleConfigFileName, meshtastic_LocalModuleConfig_size, &meshtastic_LocalModuleConfig_msg, &moduleConfig);
}
- // We might need to rewrite the OEM data if we are reformatting the FS
- if ((saveWhat & SEGMENT_OEM) && hasOemStore) {
- success &= saveProto(oemConfigFile, meshtastic_OEMStore_size, &meshtastic_OEMStore_msg, &oemStore);
- }
-
if (saveWhat & SEGMENT_CHANNELS) {
success &= saveChannelsToDisk();
}
@@ -887,12 +975,10 @@ bool NodeDB::saveToDisk(int saveWhat)
bool success = saveToDiskNoRetry(saveWhat);
if (!success) {
- LOG_ERROR("Failed to save to disk, retrying...\n");
+ LOG_ERROR("Failed to save to disk, retrying...");
#ifdef ARCH_NRF52 // @geeksville is not ready yet to say we should do this on other platforms. See bug #4184 discussion
FSCom.format();
- // We need to rewrite the OEM data if we are reformatting the FS
- saveWhat |= SEGMENT_OEM;
#endif
success = saveToDiskNoRetry(saveWhat);
@@ -965,7 +1051,7 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou
if (src == RX_SRC_LOCAL) {
// Local packet, fully authoritative
- LOG_INFO("updatePosition LOCAL pos@%x time=%u lat=%d lon=%d alt=%d\n", p.timestamp, p.time, p.latitude_i, p.longitude_i,
+ LOG_INFO("updatePosition LOCAL pos@%x time=%u lat=%d lon=%d alt=%d", p.timestamp, p.time, p.latitude_i, p.longitude_i,
p.altitude);
setLocalPosition(p);
@@ -973,7 +1059,7 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou
} else if ((p.time > 0) && !p.latitude_i && !p.longitude_i && !p.timestamp && !p.location_source) {
// FIXME SPECIAL TIME SETTING PACKET FROM EUD TO RADIO
// (stop-gap fix for issue #900)
- LOG_DEBUG("updatePosition SPECIAL time setting time=%u\n", p.time);
+ LOG_DEBUG("updatePosition SPECIAL time setting time=%u", p.time);
info->position.time = p.time;
} else {
// Be careful to only update fields that have been set by the REMOTE sender
@@ -981,7 +1067,7 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou
// recorded based on the packet rxTime
//
// FIXME perhaps handle RX_SRC_USER separately?
- LOG_INFO("updatePosition REMOTE node=0x%x time=%u lat=%d lon=%d\n", nodeId, p.time, p.latitude_i, p.longitude_i);
+ LOG_INFO("updatePosition REMOTE node=0x%x time=%u lat=%d lon=%d", nodeId, p.time, p.latitude_i, p.longitude_i);
// First, back up fields that we want to protect from overwrite
uint32_t tmp_time = info->position.time;
@@ -1011,9 +1097,9 @@ void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxS
if (src == RX_SRC_LOCAL) {
// Local packet, fully authoritative
- LOG_DEBUG("updateTelemetry LOCAL\n");
+ LOG_DEBUG("updateTelemetry LOCAL");
} else {
- LOG_DEBUG("updateTelemetry REMOTE node=0x%x \n", nodeId);
+ LOG_DEBUG("updateTelemetry REMOTE node=0x%x ", nodeId);
}
info->device_metrics = t.variant.device_metrics;
info->has_device_metrics = true;
@@ -1030,16 +1116,16 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
return false;
}
- LOG_DEBUG("old user %s/%s, channel=%d\n", info->user.long_name, info->user.short_name, info->channel);
+ LOG_DEBUG("old user %s/%s, channel=%d", info->user.long_name, info->user.short_name, info->channel);
#if !(MESHTASTIC_EXCLUDE_PKI)
if (p.public_key.size > 0) {
printBytes("Incoming Pubkey: ", p.public_key.bytes, 32);
if (info->user.public_key.size > 0) { // if we have a key for this user already, don't overwrite with a new one
- LOG_INFO("Public Key set for node, not updateing!\n");
+ LOG_INFO("Public Key set for node, not updating!");
// we copy the key into the incoming packet, to prevent overwrite
memcpy(p.public_key.bytes, info->user.public_key.bytes, 32);
} else {
- LOG_INFO("Updating Node Pubkey!\n");
+ LOG_INFO("Updating Node Pubkey!");
}
}
#endif
@@ -1054,8 +1140,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
}
if (nodeId != getNodeNum())
info->channel = channelIndex; // Set channel we need to use to reach this node (but don't set our own channel)
- LOG_DEBUG("updating changed=%d user %s/%s, channel=%d\n", changed, info->user.long_name, info->user.short_name,
- info->channel);
+ LOG_DEBUG("updating changed=%d user %s/%s, channel=%d", changed, info->user.long_name, info->user.short_name, info->channel);
info->has_user = true;
if (changed) {
@@ -1066,7 +1151,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
// We just changed something about the user, store our DB
Throttle::execute(
&lastNodeDbSave, ONE_MINUTE_MS, []() { nodeDB->saveToDisk(SEGMENT_DEVICESTATE); },
- []() { LOG_DEBUG("Deferring NodeDB saveToDisk for now, since we saved less than a minute ago\n"); });
+ []() { LOG_DEBUG("Deferring NodeDB saveToDisk for now"); }); // since we saved less than a minute ago
}
return changed;
@@ -1077,7 +1162,7 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
{
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag && mp.from) {
- LOG_DEBUG("Update DB node 0x%x, rx_time=%u\n", mp.from, mp.rx_time);
+ LOG_DEBUG("Update DB node 0x%x, rx_time=%u", mp.from, mp.rx_time);
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getFrom(&mp));
if (!info) {
@@ -1094,6 +1179,7 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
// If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway
if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start) {
+ info->has_hops_away = true;
info->hops_away = mp.hop_start - mp.hop_limit;
}
}
@@ -1128,7 +1214,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
if ((numMeshNodes >= MAX_NUM_NODES) || (memGet.getFreeHeap() < MINIMUM_SAFE_FREE_HEAP)) {
if (screen)
screen->print("Warn: node database full!\nErasing oldest entry\n");
- LOG_WARN("Node database full with %i nodes and %i bytes free! Erasing oldest entry\n", numMeshNodes,
+ LOG_WARN("Node database full with %i nodes and %i bytes free! Erasing oldest entry", numMeshNodes,
memGet.getFreeHeap());
// look for oldest node and erase it
uint32_t oldest = UINT32_MAX;
@@ -1164,7 +1250,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
// everything is missing except the nodenum
memset(lite, 0, sizeof(*lite));
lite->num = n;
- LOG_INFO("Adding node to database with %i nodes and %i bytes free!\n", numMeshNodes, memGet.getFreeHeap());
+ LOG_INFO("Adding node to database with %i nodes and %i bytes free!", numMeshNodes, memGet.getFreeHeap());
}
return lite;
@@ -1175,11 +1261,12 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co
{
// Print error to screen and serial port
String lcd = String("Critical error ") + code + "!\n";
- screen->print(lcd.c_str());
+ if (screen)
+ screen->print(lcd.c_str());
if (filename) {
- LOG_ERROR("NOTE! Recording critical error %d at %s:%lu\n", code, filename, address);
+ LOG_ERROR("NOTE! Recording critical error %d at %s:%lu", code, filename, address);
} else {
- LOG_ERROR("NOTE! Recording critical error %d, address=0x%lx\n", code, address);
+ LOG_ERROR("NOTE! Recording critical error %d, address=0x%lx", code, address);
}
// Record error to DB
diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h
index ccd86fe5e..12ef6984c 100644
--- a/src/mesh/NodeDB.h
+++ b/src/mesh/NodeDB.h
@@ -2,11 +2,13 @@
#include "Observer.h"
#include
+#include
#include
#include
#include "MeshTypes.h"
#include "NodeStatus.h"
+#include "configuration.h"
#include "mesh-pb-constants.h"
#include "mesh/generated/meshtastic/mesh.pb.h" // For CriticalErrorCode
@@ -19,7 +21,6 @@ DeviceState versions used to be defined in the .proto file but really only this
#define SEGMENT_MODULECONFIG 2
#define SEGMENT_DEVICESTATE 4
#define SEGMENT_CHANNELS 8
-#define SEGMENT_OEM 16
#define DEVICESTATE_CUR_VER 23
#define DEVICESTATE_MIN_VER 22
@@ -29,7 +30,6 @@ extern meshtastic_ChannelFile channelFile;
extern meshtastic_MyNodeInfo &myNodeInfo;
extern meshtastic_LocalConfig config;
extern meshtastic_LocalModuleConfig moduleConfig;
-extern meshtastic_OEMStore oemStore;
extern meshtastic_User &owner;
extern meshtastic_Position localPosition;
@@ -155,12 +155,12 @@ class NodeDB
void setLocalPosition(meshtastic_Position position, bool timeOnly = false)
{
if (timeOnly) {
- LOG_DEBUG("Setting local position time only: time=%u timestamp=%u\n", position.time, position.timestamp);
+ LOG_DEBUG("Setting local position time only: time=%u timestamp=%u", position.time, position.timestamp);
localPosition.time = position.time;
localPosition.timestamp = position.timestamp > 0 ? position.timestamp : position.time;
return;
}
- LOG_DEBUG("Setting local position: lat=%i lon=%i time=%u timestamp=%u\n", position.latitude_i, position.longitude_i,
+ LOG_DEBUG("Setting local position: lat=%i lon=%i time=%u timestamp=%u", position.latitude_i, position.longitude_i,
position.time, position.timestamp);
localPosition = position;
}
@@ -185,7 +185,8 @@ class NodeDB
void cleanupMeshDB();
/// Reinit device state from scratch (not loading from disk)
- void installDefaultDeviceState(), installDefaultChannels(), installDefaultConfig(), installDefaultModuleConfig();
+ void installDefaultDeviceState(), installDefaultChannels(), installDefaultConfig(bool preserveKey),
+ installDefaultModuleConfig();
/// write to flash
/// @return true if the save was successful
diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp
index 51dc8889e..1deb0e5fa 100644
--- a/src/mesh/PacketHistory.cpp
+++ b/src/mesh/PacketHistory.cpp
@@ -5,6 +5,7 @@
#ifdef ARCH_PORTDUINO
#include "platform/portduino/PortduinoGlue.h"
#endif
+#include "Throttle.h"
PacketHistory::PacketHistory()
{
@@ -18,24 +19,23 @@ PacketHistory::PacketHistory()
bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpdate)
{
if (p->id == 0) {
- LOG_DEBUG("Ignoring message with zero id\n");
+ LOG_DEBUG("Ignoring message with zero id");
return false; // Not a floodable message ID, so we don't care
}
- uint32_t now = millis();
-
PacketRecord r;
r.id = p->id;
r.sender = getFrom(p);
- r.rxTimeMsec = now;
+ r.rxTimeMsec = millis();
r.next_hop = p->next_hop;
r.relayed_by = p->relay_node;
auto found = recentPackets.find(r);
bool seenRecently = (found != recentPackets.end()); // found not equal to .end() means packet was seen recently
- if (seenRecently && (now - found->rxTimeMsec) >= FLOOD_EXPIRE_TIME) { // Check whether found packet has already expired
- recentPackets.erase(found); // Erase and pretend packet has not been seen recently
+ if (seenRecently &&
+ !Throttle::isWithinTimespanMs(found->rxTimeMsec, FLOOD_EXPIRE_TIME)) { // Check whether found packet has already expired
+ recentPackets.erase(found); // Erase and pretend packet has not been seen recently
found = recentPackets.end();
seenRecently = false;
}
@@ -50,7 +50,7 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
}
if (seenRecently) {
- LOG_DEBUG("Found existing packet record for fr=0x%x,to=0x%x,id=0x%x\n", p->from, p->to, p->id);
+ LOG_DEBUG("Found existing packet record for fr=0x%x,to=0x%x,id=0x%x", p->from, p->to, p->id);
}
if (withUpdate) {
@@ -75,19 +75,17 @@ bool PacketHistory::wasSeenRecently(const meshtastic_MeshPacket *p, bool withUpd
*/
void PacketHistory::clearExpiredRecentPackets()
{
- uint32_t now = millis();
-
- LOG_DEBUG("recentPackets size=%ld\n", recentPackets.size());
+ LOG_DEBUG("recentPackets size=%ld", recentPackets.size());
for (auto it = recentPackets.begin(); it != recentPackets.end();) {
- if ((now - it->rxTimeMsec) >= FLOOD_EXPIRE_TIME) {
+ if (!Throttle::isWithinTimespanMs(it->rxTimeMsec, FLOOD_EXPIRE_TIME)) {
it = recentPackets.erase(it); // erase returns iterator pointing to element immediately following the one erased
} else {
++it;
}
}
- LOG_DEBUG("recentPackets size=%ld (after clearing expired packets)\n", recentPackets.size());
+ LOG_DEBUG("recentPackets size=%ld (after clearing expired packets)", recentPackets.size());
}
/* Find the relayer of a packet in the history given an ID and sender
diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp
index 30af9d7b0..98db38c47 100644
--- a/src/mesh/PhoneAPI.cpp
+++ b/src/mesh/PhoneAPI.cpp
@@ -8,6 +8,7 @@
#include "FSCommon.h"
#include "MeshService.h"
#include "NodeDB.h"
+#include "PacketHistory.h"
#include "PhoneAPI.h"
#include "PowerFSM.h"
#include "RadioInterface.h"
@@ -25,10 +26,13 @@
#if !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
#endif
+#include "Throttle.h"
+#include
PhoneAPI::PhoneAPI()
{
lastContactMsec = millis();
+ std::fill(std::begin(recentToRadioPacketIds), std::end(recentToRadioPacketIds), 0);
}
PhoneAPI::~PhoneAPI()
@@ -51,18 +55,20 @@ void PhoneAPI::handleStartConfig()
state = STATE_SEND_MY_INFO;
pauseBluetoothLogging = true;
filesManifest = getFiles("/", 10);
- LOG_DEBUG("Got %d files in manifest\n", filesManifest.size());
+ LOG_DEBUG("Got %d files in manifest", filesManifest.size());
- LOG_INFO("Starting API client config\n");
+ LOG_INFO("Starting API client config");
nodeInfoForPhone.num = 0; // Don't keep returning old nodeinfos
resetReadIndex();
}
void PhoneAPI::close()
{
+ LOG_INFO("PhoneAPI::close()");
+
if (state != STATE_SEND_NOTHING) {
state = STATE_SEND_NOTHING;
-
+ resetReadIndex();
unobserve(&service->fromNumChanged);
#ifdef FSCom
unobserve(&xModem.packetReady);
@@ -70,8 +76,17 @@ void PhoneAPI::close()
releasePhonePacket(); // Don't leak phone packets on shutdown
releaseQueueStatusPhonePacket();
releaseMqttClientProxyPhonePacket();
-
+ releaseClientNotification();
onConnectionChanged(false);
+ fromRadioScratch = {};
+ toRadioScratch = {};
+ nodeInfoForPhone = {};
+ packetForPhone = NULL;
+ filesManifest.clear();
+ fromRadioNum = 0;
+ config_nonce = 0;
+ config_state = 0;
+ pauseBluetoothLogging = false;
}
}
@@ -80,7 +95,7 @@ bool PhoneAPI::checkConnectionTimeout()
if (isConnected()) {
bool newContact = checkIsConnected();
if (!newContact) {
- LOG_INFO("Lost phone connection\n");
+ LOG_INFO("Lost phone connection");
close();
return true;
}
@@ -96,8 +111,6 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE); // As long as the phone keeps talking to us, don't let the radio go to sleep
lastContactMsec = millis();
- // return (lastContactMsec != 0) &&
-
memset(&toRadioScratch, 0, sizeof(toRadioScratch));
if (pb_decode_from_bytes(buf, bufLength, &meshtastic_ToRadio_msg, &toRadioScratch)) {
switch (toRadioScratch.which_payload_variant) {
@@ -105,41 +118,41 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
return handleToRadioPacket(toRadioScratch.packet);
case meshtastic_ToRadio_want_config_id_tag:
config_nonce = toRadioScratch.want_config_id;
- LOG_INFO("Client wants config, nonce=%u\n", config_nonce);
+ LOG_INFO("Client wants config, nonce=%u", config_nonce);
handleStartConfig();
break;
case meshtastic_ToRadio_disconnect_tag:
- LOG_INFO("Disconnecting from phone\n");
+ LOG_INFO("Disconnecting from phone");
close();
break;
case meshtastic_ToRadio_xmodemPacket_tag:
- LOG_INFO("Got xmodem packet\n");
+ LOG_INFO("Got xmodem packet");
#ifdef FSCom
xModem.handlePacket(toRadioScratch.xmodemPacket);
#endif
break;
#if !MESHTASTIC_EXCLUDE_MQTT
case meshtastic_ToRadio_mqttClientProxyMessage_tag:
- LOG_INFO("Got MqttClientProxy message\n");
+ LOG_INFO("Got MqttClientProxy message");
if (mqtt && moduleConfig.mqtt.proxy_to_client_enabled && moduleConfig.mqtt.enabled &&
(channels.anyMqttEnabled() || moduleConfig.mqtt.map_reporting_enabled)) {
mqtt->onClientProxyReceive(toRadioScratch.mqttClientProxyMessage);
} else {
LOG_WARN("MqttClientProxy received but proxy is not enabled, no channels have up/downlink, or map reporting "
- "not enabled\n");
+ "not enabled");
}
break;
#endif
case meshtastic_ToRadio_heartbeat_tag:
- LOG_DEBUG("Got client heartbeat\n");
+ LOG_DEBUG("Got client heartbeat");
break;
default:
// Ignore nop messages
- // LOG_DEBUG("Error: unexpected ToRadio variant\n");
+ // LOG_DEBUG("Error: unexpected ToRadio variant");
break;
}
} else {
- LOG_ERROR("Error: ignoring malformed toradio\n");
+ LOG_ERROR("Error: ignoring malformed toradio");
}
return false;
@@ -166,7 +179,7 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength)
size_t PhoneAPI::getFromRadio(uint8_t *buf)
{
if (!available()) {
- // LOG_DEBUG("getFromRadio=not available\n");
+ // LOG_DEBUG("getFromRadio=not available");
return 0;
}
// In case we send a FromRadio packet
@@ -175,11 +188,11 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// Advance states as needed
switch (state) {
case STATE_SEND_NOTHING:
- LOG_INFO("getFromRadio=STATE_SEND_NOTHING\n");
+ LOG_INFO("getFromRadio=STATE_SEND_NOTHING");
break;
case STATE_SEND_MY_INFO:
- LOG_INFO("getFromRadio=STATE_SEND_MY_INFO\n");
+ LOG_INFO("getFromRadio=STATE_SEND_MY_INFO");
// If the user has specified they don't want our node to share its location, make sure to tell the phone
// app not to send locations on our behalf.
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_my_info_tag;
@@ -190,11 +203,11 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
break;
case STATE_SEND_OWN_NODEINFO: {
- LOG_INFO("getFromRadio=STATE_SEND_OWN_NODEINFO\n");
+ LOG_INFO("getFromRadio=STATE_SEND_OWN_NODEINFO");
auto us = nodeDB->readNextMeshNode(readIndex);
if (us) {
nodeInfoForPhone = TypeConversions::ConvertToNodeInfo(us);
- nodeInfoForPhone.hops_away = 0;
+ nodeInfoForPhone.has_hops_away = false;
nodeInfoForPhone.is_favorite = true;
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_node_info_tag;
fromRadioScratch.node_info = nodeInfoForPhone;
@@ -206,14 +219,14 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
}
case STATE_SEND_METADATA:
- LOG_INFO("getFromRadio=STATE_SEND_METADATA\n");
+ LOG_INFO("getFromRadio=STATE_SEND_METADATA");
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_metadata_tag;
fromRadioScratch.metadata = getDeviceMetadata();
state = STATE_SEND_CHANNELS;
break;
case STATE_SEND_CHANNELS:
- LOG_INFO("getFromRadio=STATE_SEND_CHANNELS\n");
+ LOG_INFO("getFromRadio=STATE_SEND_CHANNELS");
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_channel_tag;
fromRadioScratch.channel = channels.getByIndex(config_state);
config_state++;
@@ -225,7 +238,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
break;
case STATE_SEND_CONFIG:
- LOG_INFO("getFromRadio=STATE_SEND_CONFIG\n");
+ LOG_INFO("getFromRadio=STATE_SEND_CONFIG");
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_tag;
switch (config_state) {
case meshtastic_Config_device_tag:
@@ -265,7 +278,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.config.which_payload_variant = meshtastic_Config_sessionkey_tag;
break;
default:
- LOG_ERROR("Unknown config type %d\n", config_state);
+ LOG_ERROR("Unknown config type %d", config_state);
}
// NOTE: The phone app needs to know the ls_secs value so it can properly expect sleep behavior.
// So even if we internally use 0 to represent 'use default' we still need to send the value we are
@@ -280,7 +293,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
break;
case STATE_SEND_MODULECONFIG:
- LOG_INFO("getFromRadio=STATE_SEND_MODULECONFIG\n");
+ LOG_INFO("getFromRadio=STATE_SEND_MODULECONFIG");
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_moduleConfig_tag;
switch (config_state) {
case meshtastic_ModuleConfig_mqtt_tag:
@@ -336,7 +349,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.moduleConfig.payload_variant.paxcounter = moduleConfig.paxcounter;
break;
default:
- LOG_ERROR("Unknown module config type %d\n", config_state);
+ LOG_ERROR("Unknown module config type %d", config_state);
}
config_state++;
@@ -349,16 +362,16 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
break;
case STATE_SEND_OTHER_NODEINFOS: {
- LOG_INFO("getFromRadio=STATE_SEND_OTHER_NODEINFOS\n");
+ LOG_INFO("getFromRadio=STATE_SEND_OTHER_NODEINFOS");
if (nodeInfoForPhone.num != 0) {
- LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", nodeInfoForPhone.num, nodeInfoForPhone.last_heard,
+ LOG_INFO("nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s", 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");
+ LOG_INFO("Done sending nodeinfos");
state = STATE_SEND_FILEMANIFEST;
// Go ahead and send that ID right now
return getFromRadio(buf);
@@ -367,7 +380,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
}
case STATE_SEND_FILEMANIFEST: {
- LOG_INFO("getFromRadio=STATE_SEND_FILEMANIFEST\n");
+ LOG_INFO("getFromRadio=STATE_SEND_FILEMANIFEST");
// last element
if (config_state == filesManifest.size()) { // also handles an empty filesManifest
config_state = 0;
@@ -377,7 +390,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
} else {
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_fileInfo_tag;
fromRadioScratch.fileInfo = filesManifest.at(config_state);
- LOG_DEBUG("File: %s (%d) bytes\n", fromRadioScratch.fileInfo.file_name, fromRadioScratch.fileInfo.size_bytes);
+ LOG_DEBUG("File: %s (%d) bytes", fromRadioScratch.fileInfo.file_name, fromRadioScratch.fileInfo.size_bytes);
config_state++;
}
break;
@@ -390,7 +403,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
case STATE_SEND_PACKETS:
pauseBluetoothLogging = false;
// Do we have a message from the mesh or packet from the local device?
- LOG_INFO("getFromRadio=STATE_SEND_PACKETS\n");
+ LOG_INFO("getFromRadio=STATE_SEND_PACKETS");
if (queueStatusPacketForPhone) {
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_queueStatus_tag;
fromRadioScratch.queueStatus = *queueStatusPacketForPhone;
@@ -403,6 +416,10 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_xmodemPacket_tag;
fromRadioScratch.xmodemPacket = xmodemPacketForPhone;
xmodemPacketForPhone = meshtastic_XModem_init_zero;
+ } else if (clientNotification) {
+ fromRadioScratch.which_payload_variant = meshtastic_FromRadio_clientNotification_tag;
+ fromRadioScratch.clientNotification = *clientNotification;
+ releaseClientNotification();
} else if (packetForPhone) {
printPacket("phone downloaded packet", packetForPhone);
@@ -414,7 +431,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
break;
default:
- LOG_ERROR("getFromRadio unexpected state %d\n", state);
+ LOG_ERROR("getFromRadio unexpected state %d", state);
}
// Do we have a message from the mesh?
@@ -424,17 +441,17 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
// VERY IMPORTANT to not print debug messages while writing to fromRadioScratch - because we use that same buffer
// for logging (when we are encapsulating with protobufs)
- // LOG_DEBUG("encoding toPhone packet to phone variant=%d, %d bytes\n", fromRadioScratch.which_payload_variant, numbytes);
+ // LOG_DEBUG("encoding toPhone packet to phone variant=%d, %d bytes", fromRadioScratch.which_payload_variant, numbytes);
return numbytes;
}
- LOG_DEBUG("no FromRadio packet available\n");
+ LOG_DEBUG("no FromRadio packet available");
return 0;
}
void PhoneAPI::sendConfigComplete()
{
- LOG_INFO("getFromRadio=STATE_SEND_COMPLETE_ID\n");
+ LOG_INFO("getFromRadio=STATE_SEND_COMPLETE_ID");
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_complete_id_tag;
fromRadioScratch.config_complete_id = config_nonce;
config_nonce = 0;
@@ -442,13 +459,6 @@ void PhoneAPI::sendConfigComplete()
pauseBluetoothLogging = false;
}
-void PhoneAPI::handleDisconnect()
-{
- filesManifest.clear();
- pauseBluetoothLogging = false;
- LOG_INFO("PhoneAPI disconnect\n");
-}
-
void PhoneAPI::releasePhonePacket()
{
if (packetForPhone) {
@@ -473,6 +483,14 @@ void PhoneAPI::releaseMqttClientProxyPhonePacket()
}
}
+void PhoneAPI::releaseClientNotification()
+{
+ if (clientNotification) {
+ service->releaseClientNotificationToPool(clientNotification);
+ clientNotification = NULL;
+ }
+}
+
/**
* Return true if we have data available to send to the phone
*/
@@ -507,7 +525,9 @@ bool PhoneAPI::available()
queueStatusPacketForPhone = service->getQueueStatusForPhone();
if (!mqttClientProxyMessageForPhone)
mqttClientProxyMessageForPhone = service->getMqttClientProxyMessageForPhone();
- bool hasPacket = !!queueStatusPacketForPhone || !!mqttClientProxyMessageForPhone;
+ if (!clientNotification)
+ clientNotification = service->getClientNotificationForPhone();
+ bool hasPacket = !!queueStatusPacketForPhone || !!mqttClientProxyMessageForPhone || !!clientNotification;
if (hasPacket)
return true;
@@ -531,24 +551,74 @@ bool PhoneAPI::available()
if (!packetForPhone)
packetForPhone = service->getForPhone();
hasPacket = !!packetForPhone;
- // LOG_DEBUG("available hasPacket=%d\n", hasPacket);
+ // LOG_DEBUG("available hasPacket=%d", hasPacket);
return hasPacket;
}
default:
- LOG_ERROR("PhoneAPI::available unexpected state %d\n", state);
+ LOG_ERROR("PhoneAPI::available unexpected state %d", state);
}
return false;
}
+void PhoneAPI::sendNotification(meshtastic_LogRecord_Level level, uint32_t replyId, const char *message)
+{
+ meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
+ cn->has_reply_id = true;
+ cn->reply_id = replyId;
+ cn->level = meshtastic_LogRecord_Level_WARNING;
+ cn->time = getValidTime(RTCQualityFromNet);
+ strncpy(cn->message, message, sizeof(cn->message));
+ service->sendClientNotification(cn);
+}
+
+bool PhoneAPI::wasSeenRecently(uint32_t id)
+{
+ for (int i = 0; i < 20; i++) {
+ if (recentToRadioPacketIds[i] == id) {
+ return true;
+ }
+ if (recentToRadioPacketIds[i] == 0) {
+ recentToRadioPacketIds[i] = id;
+ return false;
+ }
+ }
+ // If the array is full, shift all elements to the left and add the new id at the end
+ memmove(recentToRadioPacketIds, recentToRadioPacketIds + 1, (19) * sizeof(uint32_t));
+ recentToRadioPacketIds[19] = id;
+ return false;
+}
+
/**
* Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool
*/
bool PhoneAPI::handleToRadioPacket(meshtastic_MeshPacket &p)
{
printPacket("PACKET FROM PHONE", &p);
- service->handleToRadio(p);
+ if (p.id > 0 && wasSeenRecently(p.id)) {
+ LOG_DEBUG("Ignoring packet from phone, already seen recently");
+ return false;
+ }
+
+ if (p.decoded.portnum == meshtastic_PortNum_TRACEROUTE_APP && lastPortNumToRadio[p.decoded.portnum] &&
+ Throttle::isWithinTimespanMs(lastPortNumToRadio[p.decoded.portnum], THIRTY_SECONDS_MS)) {
+ LOG_WARN("Rate limiting portnum %d", p.decoded.portnum);
+ sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "TraceRoute can only be sent once every 30 seconds");
+ meshtastic_QueueStatus qs = router->getQueueStatus();
+ service->sendQueueStatusToPhone(qs, 0, p.id);
+ return false;
+ } else if (p.decoded.portnum == meshtastic_PortNum_POSITION_APP && lastPortNumToRadio[p.decoded.portnum] &&
+ Throttle::isWithinTimespanMs(lastPortNumToRadio[p.decoded.portnum], FIVE_SECONDS_MS)) {
+ LOG_WARN("Rate limiting portnum %d", p.decoded.portnum);
+ meshtastic_QueueStatus qs = router->getQueueStatus();
+ service->sendQueueStatusToPhone(qs, 0, p.id);
+ // FIXME: Figure out why this continues to happen
+ // sendNotification(meshtastic_LogRecord_Level_WARNING, p.id, "Position can only be sent once every 5 seconds");
+ return false;
+ }
+ lastPortNumToRadio[p.decoded.portnum] = millis();
+ service->handleToRadio(p);
return true;
}
@@ -559,10 +629,10 @@ int PhoneAPI::onNotify(uint32_t newValue)
// doesn't call this from idle)
if (state == STATE_SEND_PACKETS) {
- LOG_INFO("Telling client we have new packets %u\n", newValue);
+ LOG_INFO("Telling client we have new packets %u", newValue);
onNowHasData(newValue);
} else {
- LOG_DEBUG("(Client not yet interested in packets)\n");
+ LOG_DEBUG("(Client not yet interested in packets)");
}
return timeout ? -1 : 0; // If we timed out, MeshService should stop iterating through observers as we just removed one
diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h
index 5feb1c4bf..3247fee5c 100644
--- a/src/mesh/PhoneAPI.h
+++ b/src/mesh/PhoneAPI.h
@@ -2,8 +2,10 @@
#include "Observer.h"
#include "mesh-pb-constants.h"
+#include "meshtastic/portnums.pb.h"
#include
#include
+#include
#include
// Make sure that we never let our packets grow too large for one BLE packet
@@ -48,6 +50,10 @@ class PhoneAPI
uint8_t config_state = 0;
+ // Hashmap of timestamps for last time we received a packet on the API per portnum
+ std::unordered_map lastPortNumToRadio;
+ uint32_t recentToRadioPacketIds[20]; // Last 20 ToRadio MeshPacket IDs we have seen
+
/**
* Each packet sent to the phone has an incrementing count
*/
@@ -99,6 +105,11 @@ class PhoneAPI
*/
virtual bool handleToRadio(const uint8_t *buf, size_t len);
+ /**
+ * Send a (client)notification to the phone
+ */
+ virtual void sendNotification(meshtastic_LogRecord_Level level, uint32_t replyId, const char *message);
+
/**
* Get the next packet we want to send to the phone
*
@@ -137,11 +148,6 @@ class PhoneAPI
*/
virtual void onNowHasData(uint32_t fromRadioNum) {}
- /**
- * Subclasses can use this to find out when a client drops the link
- */
- virtual void handleDisconnect();
-
private:
void releasePhonePacket();
@@ -149,9 +155,13 @@ class PhoneAPI
void releaseMqttClientProxyPhonePacket();
+ void releaseClientNotification();
+
/// begin a new connection
void handleStartConfig();
+ bool wasSeenRecently(uint32_t packetId);
+
/**
* Handle a packet that the phone wants us to send. We can write to it but can not keep a reference to it
* @return true true if a packet was queued for sending
diff --git a/src/mesh/ProtobufModule.h b/src/mesh/ProtobufModule.h
index 3b438ebeb..4ddac9a0d 100644
--- a/src/mesh/ProtobufModule.h
+++ b/src/mesh/ProtobufModule.h
@@ -47,7 +47,7 @@ template class ProtobufModule : protected SinglePortModule
p->decoded.payload.size =
pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), fields, &payload);
- // LOG_DEBUG("did encode\n");
+ // LOG_DEBUG("did encode");
return p;
}
@@ -82,7 +82,7 @@ template class ProtobufModule : protected SinglePortModule
// it would be better to update even if the message was destined to others.
auto &p = mp.decoded;
- LOG_INFO("Received %s from=0x%0x, id=0x%x, portnum=%d, payloadlen=%d\n", name, mp.from, mp.id, p.portnum, p.payload.size);
+ LOG_INFO("Received %s from=0x%0x, id=0x%x, portnum=%d, payloadlen=%d", name, mp.from, mp.id, p.portnum, p.payload.size);
T scratch;
T *decoded = NULL;
@@ -91,7 +91,7 @@ template class ProtobufModule : protected SinglePortModule
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch)) {
decoded = &scratch;
} else {
- LOG_ERROR("Error decoding protobuf module!\n");
+ LOG_ERROR("Error decoding protobuf module!");
// if we can't decode it, nobody can process it!
return ProcessMessage::STOP;
}
@@ -108,11 +108,11 @@ template class ProtobufModule : protected SinglePortModule
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;
+ const meshtastic_Data &p = mp.decoded;
if (pb_decode_from_bytes(p.payload.bytes, p.payload.size, fields, &scratch)) {
decoded = &scratch;
} else {
- LOG_ERROR("Error decoding protobuf module!\n");
+ LOG_ERROR("Error decoding protobuf module!");
// if we can't decode it, nobody can process it!
return;
}
diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp
index 25df3258f..db56659bd 100644
--- a/src/mesh/RF95Interface.cpp
+++ b/src/mesh/RF95Interface.cpp
@@ -1,3 +1,4 @@
+#if RADIOLIB_EXCLUDE_SX127X != 1
#include "RF95Interface.h"
#include "MeshRadio.h" // kinda yucky, but we need to know which region we are in
#include "RadioLibRF95.h"
@@ -81,7 +82,7 @@ RF95Interface::RF95Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIO
RADIOLIB_PIN_TYPE busy)
: RadioLibInterface(hal, cs, irq, rst, busy)
{
- LOG_DEBUG("RF95Interface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
+ LOG_DEBUG("RF95Interface(cs=%d, irq=%d, rst=%d, busy=%d)", cs, irq, rst, busy);
}
/** Some boards require GPIO control of tx vs rx paths */
@@ -175,12 +176,12 @@ bool RF95Interface::init()
setTransmitEnable(false);
int res = lora->begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength);
- LOG_INFO("RF95 init result %d\n", res);
- 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);
+ LOG_INFO("RF95 init result %d", res);
+ LOG_INFO("Frequency set to %f", getFreq());
+ LOG_INFO("Bandwidth set to %f", bw);
+ LOG_INFO("Power output set to %d", power);
#if defined(RADIOMASTER_900_BANDIT_NANO) || defined(RADIOMASTER_900_BANDIT)
- LOG_INFO("DAC output set to %d\n", powerDAC);
+ LOG_INFO("DAC output set to %d", powerDAC);
#endif
if (res == RADIOLIB_ERR_NONE)
@@ -219,17 +220,17 @@ bool RF95Interface::reconfigure()
err = lora->setSyncWord(syncWord);
if (err != RADIOLIB_ERR_NONE)
- LOG_ERROR("Radiolib error %d when attempting RF95 setSyncWord!\n", err);
+ LOG_ERROR("RF95 setSyncWord %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
err = lora->setCurrentLimit(currentLimit);
if (err != RADIOLIB_ERR_NONE)
- LOG_ERROR("Radiolib error %d when attempting RF95 setCurrentLimit!\n", err);
+ LOG_ERROR("RF95 setCurrentLimit %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
err = lora->setPreambleLength(preambleLength);
if (err != RADIOLIB_ERR_NONE)
- LOG_ERROR("Radiolib error %d when attempting RF95 setPreambleLength!\n", err);
+ LOG_ERROR(" RF95 setPreambleLength %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
err = lora->setFrequency(getFreq());
@@ -265,7 +266,7 @@ void RF95Interface::setStandby()
{
int err = lora->standby();
if (err != RADIOLIB_ERR_NONE)
- LOG_ERROR("Radiolib error %d when attempting RF95 standby!\n", err);
+ LOG_ERROR("RF95 standby %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
isReceiving = false; // If we were receiving, not any more
@@ -289,7 +290,7 @@ void RF95Interface::startReceive()
setStandby();
int err = lora->startReceive();
if (err != RADIOLIB_ERR_NONE)
- LOG_ERROR("Radiolib error %d when attempting RF95 startReceive!\n", err);
+ LOG_ERROR("RF95 startReceive %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
isReceiving = true;
@@ -307,14 +308,14 @@ bool RF95Interface::isChannelActive()
result = lora->scanChannel();
if (result == RADIOLIB_PREAMBLE_DETECTED) {
- // LOG_DEBUG("Channel is busy!\n");
+ // LOG_DEBUG("Channel is busy!");
return true;
}
if (result != RADIOLIB_CHANNEL_FREE)
- LOG_ERROR("Radiolib error %d when attempting RF95 isChannelActive!\n", result);
+ LOG_ERROR("RF95 isChannelActive %s%d", radioLibErr, result);
assert(result != RADIOLIB_ERR_WRONG_MODEM);
- // LOG_DEBUG("Channel is free!\n");
+ // LOG_DEBUG("Channel is free!");
return false;
}
@@ -336,3 +337,4 @@ bool RF95Interface::sleep()
return true;
}
+#endif
\ No newline at end of file
diff --git a/src/mesh/RF95Interface.h b/src/mesh/RF95Interface.h
index a50cf93a2..327e57900 100644
--- a/src/mesh/RF95Interface.h
+++ b/src/mesh/RF95Interface.h
@@ -1,5 +1,5 @@
#pragma once
-
+#if RADIOLIB_EXCLUDE_SX127X != 1
#include "MeshRadio.h" // kinda yucky, but we need to know which region we are in
#include "RadioLibInterface.h"
#include "RadioLibRF95.h"
@@ -69,3 +69,4 @@ class RF95Interface : public RadioLibInterface
/** Some boards require GPIO control of tx vs rx paths */
void setTransmitEnable(bool txon);
};
+#endif
\ No newline at end of file
diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index 1909d92e6..01c3e8056 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -136,6 +136,17 @@ const RegionInfo regions[] = {
*/
RDEF(SG_923, 917.0f, 925.0f, 100, 0, 20, true, false, false),
+ /*
+ Philippines
+ 433 - 434.7 MHz <10 mW erp, NTC approved device required
+ 868 - 869.4 MHz <25 mW erp, NTC approved device required
+ 915 - 918 MHz <250 mW EIRP, no external antennna allowed
+ https://github.com/meshtastic/firmware/issues/4948#issuecomment-2394926135
+ */
+
+ RDEF(PH_433, 433.0f, 434.7f, 100, 0, 10, true, false, false), RDEF(PH_868, 868.0f, 869.4f, 100, 0, 14, true, false, false),
+ RDEF(PH_915, 915.0f, 918.0f, 100, 0, 24, true, false, false),
+
/*
2.4 GHZ WLAN Band equivalent. Only for SX128x chips.
*/
@@ -151,7 +162,7 @@ const RegionInfo regions[] = {
const RegionInfo *myRegion;
bool RadioInterface::uses_default_frequency_slot = true;
-static uint8_t bytes[MAX_RHPACKETLEN];
+static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1];
void initRegion()
{
@@ -159,11 +170,11 @@ void initRegion()
#ifdef REGULATORY_LORA_REGIONCODE
for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != REGULATORY_LORA_REGIONCODE; r++)
;
- LOG_INFO("Wanted region %d, regulatory override to %s\n", config.lora.region, r->name);
+ LOG_INFO("Wanted region %d, regulatory override to %s", config.lora.region, r->name);
#else
for (; r->code != meshtastic_Config_LoRaConfig_RegionCode_UNSET && r->code != config.lora.region; r++)
;
- LOG_INFO("Wanted region %d, using %s\n", config.lora.region, r->name);
+ LOG_INFO("Wanted region %d, using %s", config.lora.region, r->name);
#endif
myRegion = r;
}
@@ -202,8 +213,6 @@ uint32_t RadioInterface::getPacketTime(uint32_t pl)
uint32_t msecs = tPacket * 1000;
- LOG_DEBUG("(bw=%d, sf=%d, cr=4/%d) packet symLen=%d ms, payloadSize=%u, time %d ms\n", (int)bw, sf, cr, (int)(tSym * 1000),
- pl, msecs);
return msecs;
}
@@ -225,7 +234,7 @@ uint32_t RadioInterface::getRetransmissionMsec(const meshtastic_MeshPacket *p)
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded);
uint32_t packetAirtime = getPacketTime(numbytes + sizeof(PacketHeader));
// Make sure enough time has elapsed for this packet to be sent and an ACK is received.
- // LOG_DEBUG("Waiting for flooding message with airtime %d and slotTime is %d\n", packetAirtime, slotTimeMsec);
+ // LOG_DEBUG("Waiting for flooding message with airtime %d and slotTime is %d", packetAirtime, slotTimeMsec);
float channelUtil = airTime->channelUtilizationPercent();
uint8_t CWsize = map(channelUtil, 0, 100, CWmin, CWmax);
// Assuming we pick max. of CWsize and there will be a client with SNR at half the range
@@ -241,7 +250,7 @@ uint32_t RadioInterface::getTxDelayMsec()
current channel utilization. */
float channelUtil = airTime->channelUtilizationPercent();
uint8_t CWsize = map(channelUtil, 0, 100, CWmin, CWmax);
- // LOG_DEBUG("Current channel utilization is %f so setting CWsize to %d\n", channelUtil, CWsize);
+ // LOG_DEBUG("Current channel utilization is %f so setting CWsize to %d", channelUtil, CWsize);
return random(0, pow(2, CWsize)) * slotTimeMsec;
}
@@ -258,15 +267,15 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(float snr)
// low SNR = small CW size (Short Delay)
uint32_t delay = 0;
uint8_t CWsize = map(snr, SNR_MIN, SNR_MAX, CWmin, CWmax);
- // LOG_DEBUG("rx_snr of %f so setting CWsize to:%d\n", snr, CWsize);
+ // LOG_DEBUG("rx_snr of %f so setting CWsize to:%d", snr, CWsize);
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
delay = random(0, 2 * CWsize) * slotTimeMsec;
- LOG_DEBUG("rx_snr found in packet. As a router, setting tx delay:%d\n", delay);
+ LOG_DEBUG("rx_snr found in packet. Router: setting tx delay:%d", delay);
} else {
// offset the maximum delay for routers: (2 * CWmax * slotTimeMsec)
delay = (2 * CWmax * slotTimeMsec) + random(0, pow(2, CWsize)) * slotTimeMsec;
- LOG_DEBUG("rx_snr found in packet. Setting tx delay:%d\n", delay);
+ LOG_DEBUG("rx_snr found in packet. Setting tx delay:%d", delay);
}
return delay;
@@ -324,13 +333,13 @@ void printPacket(const char *prefix, const meshtastic_MeshPacket *p)
out += DEBUG_PORT.mt_sprintf(" priority=%d", p->priority);
out += ")";
- LOG_DEBUG("%s\n", out.c_str());
+ LOG_DEBUG("%s", out.c_str());
#endif
}
RadioInterface::RadioInterface()
{
- assert(sizeof(PacketHeader) == 16); // make sure the compiler did what we expected
+ assert(sizeof(PacketHeader) == MESHTASTIC_HEADER_LENGTH); // make sure the compiler did what we expected
}
bool RadioInterface::reconfigure()
@@ -341,7 +350,7 @@ bool RadioInterface::reconfigure()
bool RadioInterface::init()
{
- LOG_INFO("Starting meshradio init...\n");
+ LOG_INFO("Starting meshradio init...");
configChangedObserver.observe(&service->configChanged);
preflightSleepObserver.observe(&preflightSleep);
@@ -424,7 +433,7 @@ void RadioInterface::applyModemConfig()
switch (loraConfig.modem_preset) {
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
- bw = (myRegion->wideLora) ? 812.5 : 500;
+ bw = (myRegion->wideLora) ? 1625.0 : 500;
cr = 5;
sf = 7;
break;
@@ -489,8 +498,7 @@ void RadioInterface::applyModemConfig()
}
if ((myRegion->freqEnd - myRegion->freqStart) < bw / 1000) {
- static const char *err_string =
- "Regional frequency range is smaller than bandwidth. Falling back to default preset.\n";
+ static const char *err_string = "Regional frequency range is smaller than bandwidth. Falling back to default preset.";
LOG_ERROR(err_string);
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
@@ -551,15 +559,15 @@ void RadioInterface::applyModemConfig()
preambleTimeMsec = getPacketTime((uint32_t)0);
maxPacketTimeMsec = getPacketTime(meshtastic_Constants_DATA_PAYLOAD_LEN + sizeof(PacketHeader));
- LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f\n", freq, loraConfig.frequency_offset);
- LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d\n", myRegion->name, channelName, loraConfig.modem_preset,
+ LOG_INFO("Radio freq=%.3f, config.lora.frequency_offset=%.3f", freq, loraConfig.frequency_offset);
+ LOG_INFO("Set radio: region=%s, name=%s, config=%u, ch=%d, power=%d", myRegion->name, channelName, loraConfig.modem_preset,
channel_num, power);
- LOG_INFO("Radio myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f MHz)\n", myRegion->freqStart, myRegion->freqEnd,
+ LOG_INFO("myRegion->freqStart -> myRegion->freqEnd: %f -> %f (%f MHz)", myRegion->freqStart, myRegion->freqEnd,
myRegion->freqEnd - myRegion->freqStart);
- LOG_INFO("Radio myRegion->numChannels: %d x %.3fkHz\n", numChannels, bw);
- LOG_INFO("Radio channel_num: %d\n", channel_num + 1);
- LOG_INFO("Radio frequency: %f\n", getFreq());
- LOG_INFO("Slot time: %u msec\n", slotTimeMsec);
+ LOG_INFO("numChannels: %d x %.3fkHz", numChannels, bw);
+ LOG_INFO("channel_num: %d", channel_num + 1);
+ LOG_INFO("frequency: %f", getFreq());
+ LOG_INFO("Slot time: %u msec", slotTimeMsec);
}
/**
@@ -574,11 +582,11 @@ void RadioInterface::limitPower()
maxPower = myRegion->powerLimit;
if ((power > maxPower) && !devicestate.owner.is_licensed) {
- LOG_INFO("Lowering transmit power because of regulatory limits\n");
+ LOG_INFO("Lowering transmit power because of regulatory limits");
power = maxPower;
}
- LOG_INFO("Set radio: final power level=%d\n", power);
+ LOG_INFO("Set radio: final power level=%d", power);
}
void RadioInterface::deliverToReceiver(meshtastic_MeshPacket *p)
@@ -594,30 +602,29 @@ size_t RadioInterface::beginSending(meshtastic_MeshPacket *p)
{
assert(!sendingPacket);
- // LOG_DEBUG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad());
+ // LOG_DEBUG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)", rf95.txGood(), rf95.rxGood(), rf95.rxBad());
assert(p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag); // It should have already been encoded by now
lastTxStart = millis();
- PacketHeader *h = (PacketHeader *)radiobuf;
-
- h->from = p->from;
- h->to = p->to;
- h->id = p->id;
- h->channel = p->channel;
+ radioBuffer.header.from = p->from;
+ radioBuffer.header.to = p->to;
+ radioBuffer.header.id = p->id;
+ radioBuffer.header.channel = p->channel;
+ radioBuffer.header.next_hop = p->next_hop;
+ radioBuffer.header.relay_node = p->relay_node;
if (p->hop_limit > HOP_MAX) {
- LOG_WARN("hop limit %d is too high, setting to %d\n", p->hop_limit, HOP_RELIABLE);
+ LOG_WARN("hop limit %d is too high, setting to %d", p->hop_limit, HOP_RELIABLE);
p->hop_limit = HOP_RELIABLE;
}
- h->flags = p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) | (p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0);
- h->flags |= (p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK;
- h->next_hop = p->next_hop;
- h->relay_node = p->relay_node;
+ radioBuffer.header.flags =
+ p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0) | (p->via_mqtt ? PACKET_FLAGS_VIA_MQTT_MASK : 0);
+ radioBuffer.header.flags |= (p->hop_start << PACKET_FLAGS_HOP_START_SHIFT) & PACKET_FLAGS_HOP_START_MASK;
// if the sender nodenum is zero, that means uninitialized
- assert(h->from);
+ assert(radioBuffer.header.from);
- memcpy(radiobuf + sizeof(PacketHeader), p->encrypted.bytes, p->encrypted.size);
+ memcpy(radioBuffer.payload, p->encrypted.bytes, p->encrypted.size);
sendingPacket = p;
return p->encrypted.size + sizeof(PacketHeader);
diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h
index 9165373ad..8d3c19c42 100644
--- a/src/mesh/RadioInterface.h
+++ b/src/mesh/RadioInterface.h
@@ -9,7 +9,9 @@
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
-#define MAX_RHPACKETLEN 256
+#define MAX_LORA_PAYLOAD_LEN 255 // max length of 255 per Semtech's datasheets on SX12xx
+#define MESHTASTIC_HEADER_LENGTH 16
+#define MESHTASTIC_PKC_OVERHEAD 12
#define PACKET_FLAGS_HOP_LIMIT_MASK 0x07
#define PACKET_FLAGS_WANT_ACK_MASK 0x08
@@ -43,6 +45,20 @@ typedef struct {
uint8_t relay_node;
} PacketHeader;
+/**
+ * This structure represent the structured buffer : a PacketHeader then the payload. The whole is
+ * MAX_LORA_PAYLOAD_LEN + 1 length
+ * It makes the use of its data easier, and avoids manipulating pointers (and potential non aligned accesses)
+ */
+typedef struct {
+ /** The header, as defined just before */
+ PacketHeader header;
+
+ /** The payload, of maximum length minus the header, aligned just to be sure */
+ uint8_t payload[MAX_LORA_PAYLOAD_LEN + 1 - sizeof(PacketHeader)] __attribute__((__aligned__));
+
+} RadioBuffer;
+
/**
* Basic operations all radio chipsets must implement.
*
@@ -89,8 +105,7 @@ class RadioInterface
/**
* A temporary buffer used for sending/receiving packets, sized to hold the biggest buffer we might need
* */
- uint8_t radiobuf[MAX_RHPACKETLEN];
-
+ RadioBuffer radioBuffer __attribute__((__aligned__));
/**
* Enqueue a received packet for the registered receiver
*/
diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp
index b21ce0d0a..b9bc956a4 100644
--- a/src/mesh/RadioLibInterface.cpp
+++ b/src/mesh/RadioLibInterface.cpp
@@ -3,6 +3,7 @@
#include "NodeDB.h"
#include "PowerMon.h"
#include "SPILock.h"
+#include "Throttle.h"
#include "configuration.h"
#include "error.h"
#include "main.h"
@@ -19,9 +20,9 @@ void LockingArduinoHal::spiBeginTransaction()
void LockingArduinoHal::spiEndTransaction()
{
- spiLock->unlock();
-
ArduinoHal::spiEndTransaction();
+
+ spiLock->unlock();
}
#if ARCH_PORTDUINO
void LockingArduinoHal::spiTransfer(uint8_t *out, size_t len, uint8_t *in)
@@ -41,7 +42,7 @@ void LockingArduinoHal::spiTransfer(uint8_t *out, size_t len, uint8_t *in)
uint32_t start = millis();
while (digitalRead(busy)) {
- if (millis() - start >= 2000) {
+ if (!Throttle::isWithinTimespanMs(start, 2000)) {
LOG_ERROR("GPIO mid-transfer timeout, is it connected?");
return;
}
@@ -110,24 +111,46 @@ bool RadioLibInterface::canSendImmediately()
if (busyTx || busyRx) {
if (busyTx) {
- LOG_WARN("Can not send yet, busyTx\n");
+ LOG_WARN("Can not send yet, busyTx");
}
// If we've been trying to send the same packet more than one minute and we haven't gotten a
// TX IRQ from the radio, the radio is probably broken.
- if (busyTx && (millis() - lastTxStart > 60000)) {
- LOG_ERROR("Hardware Failure! busyTx for more than 60s\n");
+ if (busyTx && !Throttle::isWithinTimespanMs(lastTxStart, 60000)) {
+ LOG_ERROR("Hardware Failure! busyTx for more than 60s");
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_TRANSMIT_FAILED);
// reboot in 5 seconds when this condition occurs.
rebootAtMsec = lastTxStart + 65000;
}
if (busyRx) {
- LOG_WARN("Can not send yet, busyRx\n");
+ LOG_WARN("Can not send yet, busyRx");
}
return false;
} else
return true;
}
+bool RadioLibInterface::receiveDetected(uint16_t irq, ulong syncWordHeaderValidFlag, ulong preambleDetectedFlag)
+{
+ bool detected = (irq & (syncWordHeaderValidFlag | preambleDetectedFlag));
+ // Handle false detections
+ if (detected) {
+ if (!activeReceiveStart) {
+ activeReceiveStart = millis();
+ } else if (!Throttle::isWithinTimespanMs(activeReceiveStart, 2 * preambleTimeMsec) && !(irq & syncWordHeaderValidFlag)) {
+ // 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.");
+ return false;
+ } else if (!Throttle::isWithinTimespanMs(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.");
+ return false;
+ }
+ }
+ return detected;
+}
+
/// Send a packet (possibly by enquing in a private fifo). This routine will
/// later free() the packet to pool. This routine is not allowed to stall because it is called from
/// bluetooth comms code. If the txmit queue is empty it might return an error
@@ -138,13 +161,13 @@ ErrorCode RadioLibInterface::send(meshtastic_MeshPacket *p)
if (config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
if (disabled || !config.lora.tx_enabled) {
- LOG_WARN("send - !config.lora.tx_enabled\n");
+ LOG_WARN("send - !config.lora.tx_enabled");
packetPool.release(p);
return ERRNO_DISABLED;
}
} else {
- LOG_WARN("send - lora tx disable because RegionCode_Unset\n");
+ LOG_WARN("send - lora tx disabled because RegionCode_Unset");
packetPool.release(p);
return ERRNO_DISABLED;
}
@@ -152,7 +175,7 @@ ErrorCode RadioLibInterface::send(meshtastic_MeshPacket *p)
#else
if (disabled || !config.lora.tx_enabled) {
- LOG_WARN("send - !config.lora.tx_enabled\n");
+ LOG_WARN("send - !config.lora.tx_enabled");
packetPool.release(p);
return ERRNO_DISABLED;
}
@@ -163,7 +186,7 @@ ErrorCode RadioLibInterface::send(meshtastic_MeshPacket *p)
#ifndef LORA_DISABLE_SENDING
printPacket("enqueuing for send", p);
- LOG_DEBUG("txGood=%d,rxGood=%d,rxBad=%d\n", txGood, rxGood, rxBad);
+ LOG_DEBUG("txGood=%d,txRelay=%d,rxGood=%d,rxBad=%d", txGood, txRelay, rxGood, rxBad);
ErrorCode res = txQueue.enqueue(p) ? ERRNO_OK : ERRNO_UNKNOWN;
if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks
@@ -173,7 +196,7 @@ ErrorCode RadioLibInterface::send(meshtastic_MeshPacket *p)
// set (random) transmit delay to let others reconfigure their radio,
// to avoid collisions and implement timing-based flooding
- // LOG_DEBUG("Set random delay before transmitting.\n");
+ // LOG_DEBUG("Set random delay before transmitting.");
setTransmitDelay();
return res;
@@ -198,7 +221,7 @@ bool RadioLibInterface::canSleep()
{
bool res = txQueue.empty();
if (!res) { // only print debug messages if we are vetoing sleep
- LOG_DEBUG("radio wait to sleep, txEmpty=%d\n", res);
+ LOG_DEBUG("radio wait to sleep, txEmpty=%d", res);
}
return res;
}
@@ -211,7 +234,7 @@ bool RadioLibInterface::cancelSending(NodeNum from, PacketId id)
packetPool.release(p); // free the packet we just removed
bool result = (p != NULL);
- LOG_DEBUG("cancelSending id=0x%x, removed=%d\n", id, result);
+ LOG_DEBUG("cancelSending id=0x%x, removed=%d", id, result);
return result;
}
@@ -228,27 +251,27 @@ void RadioLibInterface::onNotify(uint32_t notification)
case ISR_TX:
handleTransmitInterrupt();
startReceive();
- // LOG_DEBUG("tx complete - starting timer\n");
+ // LOG_DEBUG("tx complete - starting timer");
startTransmitTimer();
break;
case ISR_RX:
handleReceiveInterrupt();
startReceive();
- // LOG_DEBUG("rx complete - starting timer\n");
+ // LOG_DEBUG("rx complete - starting timer");
startTransmitTimer();
break;
case TRANSMIT_DELAY_COMPLETED:
- // LOG_DEBUG("delay done\n");
+ // LOG_DEBUG("delay done");
// If we are not currently in receive mode, then restart the random delay (this can happen if the main thread
// has placed the unit into standby) FIXME, how will this work if the chipset is in sleep mode?
if (!txQueue.empty()) {
if (!canSendImmediately()) {
- // LOG_DEBUG("Currently Rx/Tx-ing: set random delay\n");
+ // LOG_DEBUG("Currently Rx/Tx-ing: set random delay");
setTransmitDelay(); // currently Rx/Tx-ing: reset random delay
} else {
if (isChannelActive()) { // check if there is currently a LoRa packet on the channel
- // LOG_DEBUG("Channel is active, try receiving first.\n");
+ // LOG_DEBUG("Channel is active, try receiving first.");
startReceive(); // try receiving this packet, afterwards we'll be trying to transmit again
setTransmitDelay();
} else {
@@ -263,7 +286,7 @@ void RadioLibInterface::onNotify(uint32_t notification)
}
}
} else {
- // LOG_DEBUG("done with txqueue\n");
+ // LOG_DEBUG("done with txqueue");
}
break;
default:
@@ -286,7 +309,7 @@ void RadioLibInterface::setTransmitDelay()
startTransmitTimer(true);
} else {
// If there is a SNR, start a timer scaled based on that SNR.
- LOG_DEBUG("rx_snr found. hop_limit:%d rx_snr:%f\n", p->hop_limit, p->rx_snr);
+ LOG_DEBUG("rx_snr found. hop_limit:%d rx_snr:%f", p->hop_limit, p->rx_snr);
startTransmitTimerSNR(p->rx_snr);
}
}
@@ -296,7 +319,7 @@ void RadioLibInterface::startTransmitTimer(bool withDelay)
// If we have work to do and the timer wasn't already scheduled, schedule it now
if (!txQueue.empty()) {
uint32_t delay = !withDelay ? 1 : getTxDelayMsec();
- // LOG_DEBUG("xmit timer %d\n", delay);
+ // LOG_DEBUG("xmit timer %d", delay);
notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
}
}
@@ -306,14 +329,14 @@ void RadioLibInterface::startTransmitTimerSNR(float snr)
// If we have work to do and the timer wasn't already scheduled, schedule it now
if (!txQueue.empty()) {
uint32_t delay = getTxDelayMsecWeighted(snr);
- // LOG_DEBUG("xmit timer %d\n", delay);
+ // LOG_DEBUG("xmit timer %d", delay);
notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
}
}
void RadioLibInterface::handleTransmitInterrupt()
{
- // LOG_DEBUG("handling lora TX interrupt\n");
+ // LOG_DEBUG("handling lora TX interrupt");
// This can be null if we forced the device to enter standby mode. In that case
// ignore the transmit interrupt
if (sendingPacket)
@@ -330,11 +353,13 @@ void RadioLibInterface::completeSending()
if (p) {
txGood++;
+ if (!isFromUs(p))
+ txRelay++;
printPacket("Completed sending", p);
// We are done sending that packet, release it
packetPool.release(p);
- // LOG_DEBUG("Done with send\n");
+ // LOG_DEBUG("Done with send");
}
}
@@ -345,7 +370,7 @@ void RadioLibInterface::handleReceiveInterrupt()
// when this is called, we should be in receive mode - if we are not, just jump out instead of bombing. Possible Race
// Condition?
if (!isReceiving) {
- LOG_ERROR("handleReceiveInterrupt called when not in receive mode, which shouldn't happen.\n");
+ LOG_ERROR("handleReceiveInterrupt called when not in receive mode, which shouldn't happen.");
return;
}
@@ -356,9 +381,17 @@ void RadioLibInterface::handleReceiveInterrupt()
xmitMsec = getPacketTime(length);
- int state = iface->readData(radiobuf, length);
+#ifndef DISABLE_WELCOME_UNSET
+ if (config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
+ LOG_WARN("recv - lora rx disabled because RegionCode_Unset");
+ airTime->logAirtime(RX_ALL_LOG, xmitMsec);
+ return;
+ }
+#endif
+
+ int state = iface->readData((uint8_t *)&radioBuffer, length);
if (state != RADIOLIB_ERR_NONE) {
- LOG_ERROR("ignoring received packet due to error=%d\n", state);
+ LOG_ERROR("ignoring received packet due to error=%d", state);
rxBad++;
airTime->logAirtime(RX_ALL_LOG, xmitMsec);
@@ -366,19 +399,17 @@ void RadioLibInterface::handleReceiveInterrupt()
} else {
// Skip the 4 headers that are at the beginning of the rxBuf
int32_t payloadLen = length - sizeof(PacketHeader);
- const uint8_t *payload = radiobuf + sizeof(PacketHeader);
// check for short packets
if (payloadLen < 0) {
- LOG_WARN("ignoring received packet too short\n");
+ LOG_WARN("ignoring received packet too short");
rxBad++;
airTime->logAirtime(RX_ALL_LOG, xmitMsec);
} else {
- const PacketHeader *h = (PacketHeader *)radiobuf;
rxGood++;
// altered packet with "from == 0" can do Remote Node Administration without permission
- if (h->from == 0) {
- LOG_WARN("ignoring received packet without sender\n");
+ if (radioBuffer.header.from == 0) {
+ LOG_WARN("ignoring received packet without sender");
return;
}
@@ -387,25 +418,25 @@ void RadioLibInterface::handleReceiveInterrupt()
// nodes.
meshtastic_MeshPacket *mp = packetPool.allocZeroed();
- mp->from = h->from;
- mp->to = h->to;
- mp->id = h->id;
- mp->channel = h->channel;
+ mp->from = radioBuffer.header.from;
+ mp->to = radioBuffer.header.to;
+ mp->id = radioBuffer.header.id;
+ mp->channel = radioBuffer.header.channel;
assert(HOP_MAX <= PACKET_FLAGS_HOP_LIMIT_MASK); // If hopmax changes, carefully check this code
- mp->hop_limit = h->flags & PACKET_FLAGS_HOP_LIMIT_MASK;
- mp->hop_start = (h->flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT;
- mp->want_ack = !!(h->flags & PACKET_FLAGS_WANT_ACK_MASK);
- mp->via_mqtt = !!(h->flags & PACKET_FLAGS_VIA_MQTT_MASK);
+ mp->hop_limit = radioBuffer.header.flags & PACKET_FLAGS_HOP_LIMIT_MASK;
+ mp->hop_start = (radioBuffer.header.flags & PACKET_FLAGS_HOP_START_MASK) >> PACKET_FLAGS_HOP_START_SHIFT;
+ mp->want_ack = !!(radioBuffer.header.flags & PACKET_FLAGS_WANT_ACK_MASK);
+ mp->via_mqtt = !!(radioBuffer.header.flags & PACKET_FLAGS_VIA_MQTT_MASK);
// If hop_start is not set, next_hop and relay_node are invalid (firmware <2.3)
- mp->next_hop = mp->hop_start == 0 ? NO_NEXT_HOP_PREFERENCE : h->next_hop;
- mp->relay_node = mp->hop_start == 0 ? NO_NEXT_HOP_PREFERENCE : h->relay_node;
+ mp->next_hop = mp->hop_start == 0 ? NO_NEXT_HOP_PREFERENCE : radioBuffer.header.next_hop;
+ mp->relay_node = mp->hop_start == 0 ? NO_NEXT_HOP_PREFERENCE : radioBuffer.header.relay_node;
addReceiveMetadata(mp);
mp->which_payload_variant =
meshtastic_MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point
assert(((uint32_t)payloadLen) <= sizeof(mp->encrypted.bytes));
- memcpy(mp->encrypted.bytes, payload, payloadLen);
+ memcpy(mp->encrypted.bytes, radioBuffer.payload, payloadLen);
mp->encrypted.size = payloadLen;
printPacket("Lora RX", mp);
@@ -439,17 +470,20 @@ void RadioLibInterface::setStandby()
void RadioLibInterface::startSend(meshtastic_MeshPacket *txp)
{
printPacket("Starting low level send", txp);
- if (disabled || !config.lora.tx_enabled) {
- LOG_WARN("startSend is dropping tx packet because we are disabled\n");
+ if (txp->to == NODENUM_BROADCAST_NO_LORA) {
+ LOG_DEBUG("Drop Tx packet because dest is broadcast no-lora");
+ packetPool.release(txp);
+ } else if (disabled || !config.lora.tx_enabled) {
+ LOG_WARN("Drop Tx packet because LoRa Tx disabled");
packetPool.release(txp);
} else {
configHardwareForSend(); // must be after setStandby
size_t numbytes = beginSending(txp);
- int res = iface->startTransmit(radiobuf, numbytes);
+ int res = iface->startTransmit((uint8_t *)&radioBuffer, numbytes);
if (res != RADIOLIB_ERR_NONE) {
- LOG_ERROR("startTransmit failed, error=%d\n", res);
+ LOG_ERROR("startTransmit failed, error=%d", res);
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_RADIO_SPI_BUG);
// This send failed, but make sure to 'complete' it properly
diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h
index edcbb394f..353176a5b 100644
--- a/src/mesh/RadioLibInterface.h
+++ b/src/mesh/RadioLibInterface.h
@@ -107,7 +107,7 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
/**
* Debugging counts
*/
- uint32_t rxBad = 0, rxGood = 0, txGood = 0;
+ uint32_t rxBad = 0, rxGood = 0, txGood = 0, txRelay = 0;
public:
RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
@@ -167,6 +167,10 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
meshtastic_QueueStatus getQueueStatus();
protected:
+ uint32_t activeReceiveStart = 0;
+
+ bool receiveDetected(uint16_t irq, ulong syncWordHeaderValidFlag, ulong preambleDetectedFlag);
+
/** Do any hardware setup needed on entry into send configuration for the radio.
* Subclasses can customize, but must also call this base method */
virtual void configHardwareForSend();
@@ -192,4 +196,6 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
* Subclasses must override, implement and then call into this base class implementation
*/
virtual void setStandby();
+
+ const char *radioLibErr = "RadioLib err=";
};
\ No newline at end of file
diff --git a/src/mesh/RadioLibRF95.cpp b/src/mesh/RadioLibRF95.cpp
index a202d4f4d..a34c0605f 100644
--- a/src/mesh/RadioLibRF95.cpp
+++ b/src/mesh/RadioLibRF95.cpp
@@ -1,3 +1,4 @@
+#if RADIOLIB_EXCLUDE_SX127X != 1
#include "RadioLibRF95.h"
#include "configuration.h"
@@ -17,8 +18,8 @@ int16_t RadioLibRF95::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_
// current limit was removed from module' ctor
// override default value (60 mA)
state = setCurrentLimit(currentLimit);
- LOG_DEBUG("Current limit set to %f\n", currentLimit);
- LOG_DEBUG("Current limit set result %d\n", state);
+ LOG_DEBUG("Current limit set to %f", currentLimit);
+ LOG_DEBUG("Current limit set result %d", state);
// configure settings not accessible by API
// state = config();
@@ -72,7 +73,7 @@ bool RadioLibRF95::isReceiving()
{
// 0x0b == Look for header info valid, signal synchronized or signal detected
uint8_t reg = readReg(RADIOLIB_SX127X_REG_MODEM_STAT);
- // Serial.printf("reg %x\n", reg);
+ // Serial.printf("reg %x", reg);
return (reg & (RH_RF95_MODEM_STATUS_SIGNAL_DETECTED | RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED |
RH_RF95_MODEM_STATUS_HEADER_INFO_VALID)) != 0;
}
@@ -81,4 +82,5 @@ uint8_t RadioLibRF95::readReg(uint8_t addr)
{
Module *mod = this->getMod();
return mod->SPIreadRegister(addr);
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/src/mesh/RadioLibRF95.h b/src/mesh/RadioLibRF95.h
index 3bdb794f2..916a33234 100644
--- a/src/mesh/RadioLibRF95.h
+++ b/src/mesh/RadioLibRF95.h
@@ -1,4 +1,5 @@
#pragma once
+#if RADIOLIB_EXCLUDE_SX127X != 1
#include
/*!
@@ -69,3 +70,4 @@ class RadioLibRF95 : public SX1278
// use the previous value
float currentLimit = 100;
};
+#endif
\ No newline at end of file
diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp
index d50ebaa64..f59bb22f3 100644
--- a/src/mesh/ReliableRouter.cpp
+++ b/src/mesh/ReliableRouter.cpp
@@ -53,14 +53,14 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
auto key = GlobalPacketId(getFrom(p), p->id);
auto old = findPendingPacket(key);
if (old) {
- LOG_DEBUG("generating implicit ack\n");
+ LOG_DEBUG("generating implicit ack");
// NOTE: we do NOT check p->wantAck here because p is the INCOMING rebroadcast and that packet is not expected to be
// marked as wantAck
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, old->packet->channel);
stopRetransmission(key);
} else {
- LOG_DEBUG("didn't find pending packet\n");
+ LOG_DEBUG("didn't find pending packet");
}
}
@@ -78,8 +78,8 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
* Resending real ACKs is omitted, as you might receive a packet multiple times due to flooding and
* flooding this ACK back to the original sender already adds redundancy. */
bool isRepeated = p->hop_start == 0 ? (p->hop_limit == HOP_RELIABLE) : (p->hop_start == p->hop_limit);
- if (wasSeenRecently(p, false) && isRepeated && !MeshModule::currentReply && p->to != nodeDB->getNodeNum()) {
- LOG_DEBUG("Resending implicit ack for a repeated floodmsg\n");
+ if (wasSeenRecently(p, false) && isRepeated && !MeshModule::currentReply && !isToUs(p)) {
+ LOG_DEBUG("Resending implicit ack for a repeated floodmsg");
meshtastic_MeshPacket *tosend = packetPool.allocCopy(*p);
tosend->hop_limit--; // bump down the hop count
Router::send(tosend);
@@ -102,17 +102,15 @@ bool ReliableRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
*/
void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtastic_Routing *c)
{
- NodeNum ourNode = getNodeNum();
-
- if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability)
+ if (isToUs(p)) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability)
if (p->want_ack) {
if (MeshModule::currentReply) {
- LOG_DEBUG("Some other module has replied to this message, no need for a 2nd ack\n");
+ LOG_DEBUG("Another module replied to this message, no need for 2nd ack");
} else if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, p->hop_start, p->hop_limit);
} else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 &&
(nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) {
- LOG_INFO("This looks like it might be a PKI packet from an unknown node, so send PKI_UNKNOWN_PUBKEY\n");
+ LOG_INFO("PKI packet from unknown node, send PKI_UNKNOWN_PUBKEY");
sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
p->hop_start, p->hop_limit);
} else {
@@ -124,7 +122,7 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag && c &&
c->error_reason == meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY) {
if (owner.public_key.size == 32) {
- LOG_INFO("This seems like a remote PKI decrypt failure, so send a NodeInfo");
+ LOG_INFO("PKI decrypt failure, send a NodeInfo");
nodeInfoModule->sendOurNodeInfo(p->from, false, p->channel, true);
}
}
@@ -136,15 +134,136 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
// We intentionally don't check wasSeenRecently, because it is harmless to delete non existent retransmission records
if (ackId || nakId) {
+ LOG_DEBUG("Received a %s for 0x%x, stopping retransmissions", ackId ? "ACK" : "NAK", ackId);
if (ackId) {
- LOG_DEBUG("Received an ack for 0x%x, stopping retransmissions\n", ackId);
stopRetransmission(p->to, ackId);
} else {
- LOG_DEBUG("Received a nak for 0x%x, stopping retransmissions\n", nakId);
stopRetransmission(p->to, nakId);
}
}
}
- p->to == NODENUM_BROADCAST ? FloodingRouter::sniffReceived(p, c) : NextHopRouter::sniffReceived(p, c);
+ // handle the packet as normal
+ FloodingRouter::sniffReceived(p, c);
+}
+
+#define NUM_RETRANSMISSIONS 3
+
+PendingPacket::PendingPacket(meshtastic_MeshPacket *p)
+{
+ packet = p;
+ numRetransmissions = NUM_RETRANSMISSIONS - 1; // We subtract one, because we assume the user just did the first send
+}
+
+PendingPacket *ReliableRouter::findPendingPacket(GlobalPacketId key)
+{
+ auto old = pending.find(key); // If we have an old record, someone messed up because id got reused
+ if (old != pending.end()) {
+ return &old->second;
+ } else
+ return NULL;
+}
+/**
+ * Stop any retransmissions we are doing of the specified node/packet ID pair
+ */
+bool ReliableRouter::stopRetransmission(NodeNum from, PacketId id)
+{
+ auto key = GlobalPacketId(from, id);
+ return stopRetransmission(key);
+}
+
+bool ReliableRouter::stopRetransmission(GlobalPacketId key)
+{
+ auto old = findPendingPacket(key);
+ if (old) {
+ auto p = old->packet;
+ /* Only when we already transmitted a packet via LoRa, we will cancel the packet in the Tx queue
+ to avoid canceling a transmission if it was ACKed super fast via MQTT */
+ if (old->numRetransmissions < NUM_RETRANSMISSIONS - 1) {
+ // remove the 'original' (identified by originator and packet->id) from the txqueue and free it
+ cancelSending(getFrom(p), p->id);
+ // now free the pooled copy for retransmission too
+ packetPool.release(p);
+ }
+ auto numErased = pending.erase(key);
+ assert(numErased == 1);
+ return true;
+ } else
+ return false;
+}
+
+/**
+ * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting.
+ */
+PendingPacket *ReliableRouter::startRetransmission(meshtastic_MeshPacket *p)
+{
+ auto id = GlobalPacketId(p);
+ auto rec = PendingPacket(p);
+
+ stopRetransmission(getFrom(p), p->id);
+
+ setNextTx(&rec);
+ pending[id] = rec;
+
+ return &pending[id];
+}
+
+/**
+ * Do any retransmissions that are scheduled (FIXME - for the time being called from loop)
+ */
+int32_t ReliableRouter::doRetransmissions()
+{
+ uint32_t now = millis();
+ int32_t d = INT32_MAX;
+
+ // FIXME, we should use a better datastructure rather than walking through this map.
+ // for(auto el: pending) {
+ for (auto it = pending.begin(), nextIt = it; it != pending.end(); it = nextIt) {
+ ++nextIt; // we use this odd pattern because we might be deleting it...
+ auto &p = it->second;
+
+ bool stillValid = true; // assume we'll keep this record around
+
+ // FIXME, handle 51 day rolloever here!!!
+ if (p.nextTxMsec <= now) {
+ if (p.numRetransmissions == 0) {
+ LOG_DEBUG("Reliable send failed, returning a nak for fr=0x%x,to=0x%x,id=0x%x", p.packet->from, p.packet->to,
+ p.packet->id);
+ sendAckNak(meshtastic_Routing_Error_MAX_RETRANSMIT, getFrom(p.packet), p.packet->id, p.packet->channel);
+ // Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived
+ stopRetransmission(it->first);
+ stillValid = false; // just deleted it
+ } else {
+ LOG_DEBUG("Sending reliable retransmission fr=0x%x,to=0x%x,id=0x%x, tries left=%d", p.packet->from, p.packet->to,
+ p.packet->id, p.numRetransmissions);
+
+ // Note: we call the superclass version because we don't want to have our version of send() add a new
+ // retransmission record
+ FloodingRouter::send(packetPool.allocCopy(*p.packet));
+
+ // Queue again
+ --p.numRetransmissions;
+ setNextTx(&p);
+ }
+ }
+
+ if (stillValid) {
+ // Update our desired sleep delay
+ int32_t t = p.nextTxMsec - now;
+
+ d = min(t, d);
+ }
+ }
+
+ return d;
+}
+
+void ReliableRouter::setNextTx(PendingPacket *pending)
+{
+ assert(iface);
+ auto d = iface->getRetransmissionMsec(pending->packet);
+ pending->nextTxMsec = millis() + d;
+ LOG_DEBUG("Setting next retransmission in %u msecs: ", d);
+ printPacket("", pending->packet);
+ setReceivedMessage(); // Run ASAP, so we can figure out our correct sleep time
}
\ No newline at end of file
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 67c4d05d0..523d5ba11 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -36,8 +36,8 @@ static MemoryDynamic staticPool;
Allocator &packetPool = staticPool;
-static uint8_t bytes[MAX_RHPACKETLEN];
-static uint8_t ScratchEncrypted[MAX_RHPACKETLEN];
+static uint8_t bytes[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
+static uint8_t ScratchEncrypted[MAX_LORA_PAYLOAD_LEN + 1] __attribute__((__aligned__));
/**
* Constructor
@@ -48,9 +48,9 @@ Router::Router() : concurrency::OSThread("Router"), fromRadioQueue(MAX_RX_FROMRA
{
// This is called pre main(), don't touch anything here, the following code is not safe
- /* LOG_DEBUG("Size of NodeInfo %d\n", sizeof(NodeInfo));
- LOG_DEBUG("Size of SubPacket %d\n", sizeof(SubPacket));
- LOG_DEBUG("Size of MeshPacket %d\n", sizeof(MeshPacket)); */
+ /* LOG_DEBUG("Size of NodeInfo %d", sizeof(NodeInfo));
+ LOG_DEBUG("Size of SubPacket %d", sizeof(SubPacket));
+ LOG_DEBUG("Size of MeshPacket %d", sizeof(MeshPacket)); */
fromRadioQueue.setReader(this);
@@ -71,7 +71,7 @@ int32_t Router::runOnce()
perhapsHandleReceived(mp);
}
- // LOG_DEBUG("sleeping forever!\n");
+ // LOG_DEBUG("sleeping forever!");
return INT32_MAX; // Wait a long time - until we get woken for the message queue
}
@@ -104,14 +104,14 @@ PacketId generatePacketId()
// pick a random initial sequence number at boot (to prevent repeated reboots always starting at 0)
// Note: we mask the high order bit to ensure that we never pass a 'negative' number to random
rollingPacketId = random(UINT32_MAX & 0x7fffffff);
- LOG_DEBUG("Initial packet id %u\n", rollingPacketId);
+ LOG_DEBUG("Initial packet id %u", rollingPacketId);
}
rollingPacketId++;
rollingPacketId &= ID_COUNTER_MASK; // Mask out the top 22 bits
PacketId id = rollingPacketId | random(UINT32_MAX & 0x7fffffff) << 10; // top 22 bits
- LOG_DEBUG("Partially randomized packet id %u\n", id);
+ LOG_DEBUG("Partially randomized packet id %u", id);
return id;
}
@@ -141,14 +141,14 @@ void Router::sendAckNak(meshtastic_Routing_Error err, NodeNum to, PacketId idFro
void Router::abortSendAndNak(meshtastic_Routing_Error err, meshtastic_MeshPacket *p)
{
- LOG_ERROR("Error=%d, returning NAK and dropping packet.\n", err);
+ LOG_ERROR("Error=%d, returning NAK and dropping packet.", err);
sendAckNak(err, getFrom(p), p->id, p->channel);
packetPool.release(p);
}
void Router::setReceivedMessage()
{
- // LOG_DEBUG("set interval to ASAP\n");
+ // LOG_DEBUG("set interval to ASAP");
setInterval(0); // Run ASAP, so we can figure out our correct sleep time
runASAP = true;
}
@@ -166,10 +166,10 @@ meshtastic_QueueStatus Router::getQueueStatus()
ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src)
{
if (p->to == 0) {
- LOG_ERROR("Packet received with to: of 0!\n");
+ LOG_ERROR("Packet received with to: of 0!");
}
// No need to deliver externally if the destination is the local node
- if (p->to == nodeDB->getNodeNum()) {
+ if (isToUs(p)) {
printPacket("Enqueued local", p);
enqueueReceivedMessage(p);
return ERRNO_OK;
@@ -181,13 +181,16 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src)
} else {
// If we are sending a broadcast, we also treat it as if we just received it ourself
// this allows local apps (and PCs) to see broadcasts sourced locally
- if (p->to == NODENUM_BROADCAST) {
+ if (isBroadcast(p->to)) {
handleReceived(p, src);
}
- if (!p->channel) { // don't override if a channel was requested
- p->channel = nodeDB->getMeshNodeChannel(p->to);
- LOG_DEBUG("localSend to channel %d\n", p->channel);
+ if (!p->channel && !p->pki_encrypted) { // don't override if a channel was requested
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->to);
+ if (node && node->user.public_key.size == 0) {
+ p->channel = node->channel;
+ LOG_DEBUG("localSend to channel %d", p->channel);
+ }
}
return send(p);
@@ -201,8 +204,8 @@ ErrorCode Router::sendLocal(meshtastic_MeshPacket *p, RxSource src)
*/
ErrorCode Router::send(meshtastic_MeshPacket *p)
{
- if (p->to == nodeDB->getNodeNum()) {
- LOG_ERROR("BUG! send() called with packet destined for local node!\n");
+ if (isToUs(p)) {
+ LOG_ERROR("BUG! send() called with packet destined for local node!");
packetPool.release(p);
return meshtastic_Routing_Error_BAD_REQUEST;
} // should have already been handled by sendLocal
@@ -213,7 +216,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
if (hourlyTxPercent > myRegion->dutyCycle) {
#ifdef DEBUG_PORT
uint8_t silentMinutes = airTime->getSilentMinutes(hourlyTxPercent, myRegion->dutyCycle);
- LOG_WARN("Duty cycle limit exceeded. Aborting send for now, you can send again in %d minutes.\n", silentMinutes);
+ LOG_WARN("Duty cycle limit exceeded. Aborting send for now, you can send again in %d minutes.", silentMinutes);
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
cn->has_reply_id = true;
cn->reply_id = p->id;
@@ -223,7 +226,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
service->sendClientNotification(cn);
#endif
meshtastic_Routing_Error err = meshtastic_Routing_Error_DUTY_CYCLE_LIMIT;
- if (getFrom(p) == nodeDB->getNodeNum()) { // only send NAK to API, not to the mesh
+ if (isFromUs(p)) { // only send NAK to API, not to the mesh
abortSendAndNak(err, p);
} else {
packetPool.release(p);
@@ -237,7 +240,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
// assert
// Never set the want_ack flag on broadcast packets sent over the air.
- if (p->to == NODENUM_BROADCAST)
+ if (isBroadcast(p->to))
p->want_ack = false;
// Up until this point we might have been using 0 for the from address (if it started with the phone), but when we send over
@@ -246,7 +249,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
p->relay_node = nodeDB->getLastByteOfNodeNum(getNodeNum()); // set the relayer to us
// If we are the original transmitter, set the hop limit with which we start
- if (p->from == getNodeNum())
+ if (isFromUs(p))
p->hop_start = p->hop_limit;
// If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it)
@@ -271,7 +274,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
}
#if !MESHTASTIC_EXCLUDE_MQTT
// Only publish to MQTT if we're the original transmitter of the packet
- if (moduleConfig.mqtt.enabled && p->from == nodeDB->getNodeNum() && mqtt) {
+ if (moduleConfig.mqtt.enabled && isFromUs(p) && mqtt) {
mqtt->onSend(*p, *p_decoded, chIndex);
}
#endif
@@ -307,7 +310,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY &&
(nodeDB->getMeshNode(p->from) == NULL || !nodeDB->getMeshNode(p->from)->has_user)) {
- LOG_DEBUG("Node 0x%x not in nodeDB-> Rebroadcast mode KNOWN_ONLY will ignore packet\n", p->from);
+ LOG_DEBUG("Node 0x%x not in nodeDB-> Rebroadcast mode KNOWN_ONLY will ignore packet", p->from);
return false;
}
@@ -316,7 +319,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
size_t rawSize = p->encrypted.size;
if (rawSize > sizeof(bytes)) {
- LOG_ERROR("Packet too large to attempt decryption! (rawSize=%d > 256)\n", rawSize);
+ LOG_ERROR("Packet too large to attempt decryption! (rawSize=%d > 256)", rawSize);
return false;
}
bool decrypted = false;
@@ -326,27 +329,31 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
memcpy(ScratchEncrypted, p->encrypted.bytes, rawSize);
#if !(MESHTASTIC_EXCLUDE_PKI)
// Attempt PKI decryption first
- if (p->channel == 0 && p->to == nodeDB->getNodeNum() && p->to > 0 && p->to != NODENUM_BROADCAST &&
- nodeDB->getMeshNode(p->from) != nullptr && nodeDB->getMeshNode(p->from)->user.public_key.size > 0 &&
- nodeDB->getMeshNode(p->to)->user.public_key.size > 0 && rawSize > 12) {
- LOG_DEBUG("Attempting PKI decryption\n");
+ if (p->channel == 0 && isToUs(p) && p->to > 0 && !isBroadcast(p->to) && nodeDB->getMeshNode(p->from) != nullptr &&
+ nodeDB->getMeshNode(p->from)->user.public_key.size > 0 && nodeDB->getMeshNode(p->to)->user.public_key.size > 0 &&
+ rawSize > MESHTASTIC_PKC_OVERHEAD) {
+ LOG_DEBUG("Attempting PKI decryption");
- if (crypto->decryptCurve25519(p->from, p->id, rawSize, ScratchEncrypted, bytes)) {
- LOG_INFO("PKI Decryption worked!\n");
+ if (crypto->decryptCurve25519(p->from, nodeDB->getMeshNode(p->from)->user.public_key, p->id, rawSize, ScratchEncrypted,
+ bytes)) {
+ LOG_INFO("PKI Decryption worked!");
memset(&p->decoded, 0, sizeof(p->decoded));
- rawSize -= 12;
+ rawSize -= MESHTASTIC_PKC_OVERHEAD;
if (pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &p->decoded) &&
p->decoded.portnum != meshtastic_PortNum_UNKNOWN_APP) {
decrypted = true;
- LOG_INFO("Packet decrypted using PKI!\n");
+ LOG_INFO("Packet decrypted using PKI!");
p->pki_encrypted = true;
memcpy(&p->public_key.bytes, nodeDB->getMeshNode(p->from)->user.public_key.bytes, 32);
p->public_key.size = 32;
// memcpy(bytes, ScratchEncrypted, rawSize); // TODO: Rename the bytes buffers
// chIndex = 8;
} else {
+ LOG_ERROR("PKC Decrypted, but pb_decode failed!");
return false;
}
+ } else {
+ LOG_WARN("PKC decrypt attempted but failed!");
}
}
#endif
@@ -365,9 +372,9 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
// Take those raw bytes and convert them back into a well structured protobuf we can understand
memset(&p->decoded, 0, sizeof(p->decoded));
if (!pb_decode_from_bytes(bytes, rawSize, &meshtastic_Data_msg, &p->decoded)) {
- LOG_ERROR("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)!\n", p->id);
+ LOG_ERROR("Invalid protobufs in received mesh packet id=0x%08x (bad psk?)!", p->id);
} else if (p->decoded.portnum == meshtastic_PortNum_UNKNOWN_APP) {
- LOG_ERROR("Invalid portnum (bad psk?)!\n");
+ LOG_ERROR("Invalid portnum (bad psk?)!");
} else {
decrypted = true;
break;
@@ -379,6 +386,8 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
// parsing was successful
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded
p->channel = chIndex; // change to store the index instead of the hash
+ if (p->decoded.has_bitfield)
+ p->decoded.want_response |= p->decoded.bitfield & BITFIELD_WANT_RESPONSE_MASK;
/* Not actually ever used.
// Decompress if needed. jm
@@ -392,7 +401,7 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
decompressed_len = unishox2_decompress_simple(compressed_in, p->decoded.payload.size, decompressed_out);
- // LOG_DEBUG("\n\n**\n\nDecompressed length - %d \n", decompressed_len);
+ // LOG_DEBUG("**Decompressed length - %d ", decompressed_len);
memcpy(p->decoded.payload.bytes, decompressed_out, decompressed_len);
@@ -402,15 +411,15 @@ bool perhapsDecode(meshtastic_MeshPacket *p)
printPacket("decoded message", p);
#if ENABLE_JSON_LOGGING
- LOG_TRACE("%s\n", MeshPacketSerializer::JsonSerialize(p, false).c_str());
+ LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str());
#elif ARCH_PORTDUINO
if (settingsStrings[traceFilename] != "" || settingsMap[logoutputlevel] == level_trace) {
- LOG_TRACE("%s\n", MeshPacketSerializer::JsonSerialize(p, false).c_str());
+ LOG_TRACE("%s", MeshPacketSerializer::JsonSerialize(p, false).c_str());
}
#endif
return true;
} else {
- LOG_WARN("No suitable channel found for decoding, hash was 0x%x!\n", p->channel);
+ LOG_WARN("No suitable channel found for decoding, hash was 0x%x!", p->channel);
return false;
}
}
@@ -425,6 +434,12 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
// If the packet is not yet encrypted, do so now
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
+ if (isFromUs(p)) {
+ p->decoded.has_bitfield = true;
+ p->decoded.bitfield |= (config.lora.config_ok_to_mqtt << BITFIELD_OK_TO_MQTT_SHIFT);
+ p->decoded.bitfield |= (p->decoded.want_response << BITFIELD_WANT_RESPONSE_SHIFT);
+ }
+
size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), &meshtastic_Data_msg, &p->decoded);
/* Not actually used, so save the cycles
@@ -439,20 +454,20 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
int compressed_len;
compressed_len = unishox2_compress_simple(original_payload, p->decoded.payload.size, compressed_out);
- LOG_DEBUG("Original length - %d \n", p->decoded.payload.size);
- LOG_DEBUG("Compressed length - %d \n", compressed_len);
- LOG_DEBUG("Original message - %s \n", p->decoded.payload.bytes);
+ LOG_DEBUG("Original length - %d ", p->decoded.payload.size);
+ LOG_DEBUG("Compressed length - %d ", compressed_len);
+ LOG_DEBUG("Original message - %s ", p->decoded.payload.bytes);
// If the compressed length is greater than or equal to the original size, don't use the compressed form
if (compressed_len >= p->decoded.payload.size) {
- LOG_DEBUG("Not using compressing message.\n");
+ LOG_DEBUG("Not using compressing message.");
// Set the uncompressed payload variant anyway. Shouldn't hurt?
// p->decoded.which_payloadVariant = Data_payload_tag;
// Otherwise we use the compressor
} else {
- LOG_DEBUG("Using compressed message.\n");
+ LOG_DEBUG("Using compressed message.");
// Copy the compressed data into the meshpacket
p->decoded.payload.size = compressed_len;
@@ -462,7 +477,7 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
}
} */
- if (numbytes > MAX_RHPACKETLEN)
+ if (numbytes + MESHTASTIC_HEADER_LENGTH > MAX_LORA_PAYLOAD_LEN)
return meshtastic_Routing_Error_TOO_LARGE;
// printBytes("plaintext", bytes, numbytes);
@@ -471,21 +486,31 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
#if !(MESHTASTIC_EXCLUDE_PKI)
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->to);
- if (!owner.is_licensed && config.security.private_key.size == 32 && p->to != NODENUM_BROADCAST && node != nullptr &&
- node->user.public_key.size > 0 && p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP &&
- p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP && p->decoded.portnum != meshtastic_PortNum_ROUTING_APP &&
- p->decoded.portnum != meshtastic_PortNum_POSITION_APP) {
- LOG_DEBUG("Using PKI!\n");
- if (numbytes + 12 > MAX_RHPACKETLEN)
+ // We may want to retool things so we can send a PKC packet when the client specifies a key and nodenum, even if the node
+ // is not in the local nodedb
+ if (
+ // Don't use PKC with Ham mode
+ !owner.is_licensed &&
+ // Don't use PKC if it's not explicitly requested and a non-primary channel is requested
+ !(p->pki_encrypted != true && p->channel > 0) &&
+ // Check for valid keys and single node destination
+ config.security.private_key.size == 32 && !isBroadcast(p->to) && node != nullptr &&
+ // Check for a known public key for the destination
+ (node->user.public_key.size == 32) &&
+ // Some portnums either make no sense to send with PKC
+ p->decoded.portnum != meshtastic_PortNum_TRACEROUTE_APP && p->decoded.portnum != meshtastic_PortNum_NODEINFO_APP &&
+ p->decoded.portnum != meshtastic_PortNum_ROUTING_APP && p->decoded.portnum != meshtastic_PortNum_POSITION_APP) {
+ LOG_DEBUG("Using PKI!");
+ if (numbytes + MESHTASTIC_HEADER_LENGTH + MESHTASTIC_PKC_OVERHEAD > MAX_LORA_PAYLOAD_LEN)
return meshtastic_Routing_Error_TOO_LARGE;
if (p->pki_encrypted && !memfll(p->public_key.bytes, 0, 32) &&
memcmp(p->public_key.bytes, node->user.public_key.bytes, 32) != 0) {
- LOG_WARN("Client public key for client differs from requested! Requested 0x%02x, but stored key begins 0x%02x\n",
- *p->public_key.bytes, *node->user.public_key.bytes);
+ LOG_WARN("Client public key differs from requested: 0x%02x, stored key begins 0x%02x", *p->public_key.bytes,
+ *node->user.public_key.bytes);
return meshtastic_Routing_Error_PKI_FAILED;
}
- crypto->encryptCurve25519(p->to, getFrom(p), p->id, numbytes, bytes, ScratchEncrypted);
- numbytes += 12;
+ crypto->encryptCurve25519(p->to, getFrom(p), node->user.public_key, p->id, numbytes, bytes, ScratchEncrypted);
+ numbytes += MESHTASTIC_PKC_OVERHEAD;
memcpy(p->encrypted.bytes, ScratchEncrypted, numbytes);
p->channel = 0;
p->pki_encrypted = true;
@@ -562,24 +587,25 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
p->decoded.portnum == meshtastic_PortNum_NEIGHBORINFO_APP &&
(!moduleConfig.has_neighbor_info || !moduleConfig.neighbor_info.enabled)) {
- LOG_DEBUG("Neighbor info module is disabled, ignoring neighbor packet\n");
+ LOG_DEBUG("Neighbor info module is disabled, ignoring neighbor packet");
cancelSending(p->from, p->id);
skipHandle = true;
}
-#if EVENT_MODE
- if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
- (p->decoded.portnum == meshtastic_PortNum_ATAK_FORWARDER || p->decoded.portnum == meshtastic_PortNum_ATAK_PLUGIN ||
- p->decoded.portnum == meshtastic_PortNum_PAXCOUNTER_APP || p->decoded.portnum == meshtastic_PortNum_IP_TUNNEL_APP ||
- p->decoded.portnum == meshtastic_PortNum_AUDIO_APP || p->decoded.portnum == meshtastic_PortNum_PRIVATE_APP ||
- p->decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP ||
- p->decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP ||
- p->decoded.portnum == meshtastic_PortNum_REMOTE_HARDWARE_APP)) {
- LOG_DEBUG("Ignoring packet on blacklisted portnum during event\n");
+ bool shouldIgnoreNonstandardPorts =
+ config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY;
+#if USERPREFS_EVENT_MODE
+ shouldIgnoreNonstandardPorts = true;
+#endif
+ if (shouldIgnoreNonstandardPorts && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
+ IS_ONE_OF(p->decoded.portnum, meshtastic_PortNum_ATAK_FORWARDER, meshtastic_PortNum_ATAK_PLUGIN,
+ meshtastic_PortNum_PAXCOUNTER_APP, meshtastic_PortNum_IP_TUNNEL_APP, meshtastic_PortNum_AUDIO_APP,
+ meshtastic_PortNum_PRIVATE_APP, meshtastic_PortNum_DETECTION_SENSOR_APP, meshtastic_PortNum_RANGE_TEST_APP,
+ meshtastic_PortNum_REMOTE_HARDWARE_APP)) {
+ LOG_DEBUG("Ignoring packet on blacklisted portnum for CORE_PORTNUMS_ONLY");
cancelSending(p->from, p->id);
skipHandle = true;
}
-#endif
} else {
printPacket("packet decoding failed or skipped (no PSK?)", p);
}
@@ -589,8 +615,12 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
MeshModule::callModules(*p, src);
#if !MESHTASTIC_EXCLUDE_MQTT
+ // Mark as pki_encrypted if it is not yet decoded and MQTT encryption is also enabled, hash matches and it's a DM not to
+ // us (because we would be able to decrypt it)
+ if (!decoded && moduleConfig.mqtt.encryption_enabled && p->channel == 0x00 && !isBroadcast(p->to) && !isToUs(p))
+ p_encrypted->pki_encrypted = true;
// After potentially altering it, publish received message to MQTT if we're not the original transmitter of the packet
- if (decoded && moduleConfig.mqtt.enabled && getFrom(p) != nodeDB->getNodeNum() && mqtt)
+ if ((decoded || p_encrypted->pki_encrypted) && moduleConfig.mqtt.enabled && !isFromUs(p) && mqtt)
mqtt->onSend(*p_encrypted, *p, p->channel);
#endif
}
@@ -603,29 +633,35 @@ void Router::perhapsHandleReceived(meshtastic_MeshPacket *p)
#if ENABLE_JSON_LOGGING
// Even ignored packets get logged in the trace
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
- LOG_TRACE("%s\n", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str());
+ LOG_TRACE("%s", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str());
#elif ARCH_PORTDUINO
// Even ignored packets get logged in the trace
if (settingsStrings[traceFilename] != "" || settingsMap[logoutputlevel] == level_trace) {
p->rx_time = getValidTime(RTCQualityFromNet); // store the arrival timestamp for the phone
- LOG_TRACE("%s\n", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str());
+ LOG_TRACE("%s", MeshPacketSerializer::JsonSerializeEncrypted(p).c_str());
}
#endif
// assert(radioConfig.has_preferences);
if (is_in_repeated(config.lora.ignore_incoming, p->from)) {
- LOG_DEBUG("Ignoring incoming message, 0x%x is in our ignore list\n", p->from);
+ LOG_DEBUG("Ignoring msg, 0x%x is in our ignore list", p->from);
+ packetPool.release(p);
+ return;
+ }
+
+ if (p->from == NODENUM_BROADCAST) {
+ LOG_DEBUG("Ignoring msg from broadcast address");
packetPool.release(p);
return;
}
if (config.lora.ignore_mqtt && p->via_mqtt) {
- LOG_DEBUG("Message came in via MQTT from 0x%x\n", p->from);
+ LOG_DEBUG("Msg came in via MQTT from 0x%x", p->from);
packetPool.release(p);
return;
}
if (shouldFilterReceived(p)) {
- LOG_DEBUG("Incoming message was filtered from 0x%x\n", p->from);
+ LOG_DEBUG("Incoming msg was filtered from 0x%x", p->from);
packetPool.release(p);
return;
}
diff --git a/src/mesh/Router.h b/src/mesh/Router.h
index 98486745b..8ebc1a3e5 100644
--- a/src/mesh/Router.h
+++ b/src/mesh/Router.h
@@ -82,6 +82,10 @@ class Router : protected concurrency::OSThread
*/
virtual ErrorCode send(meshtastic_MeshPacket *p);
+ /* Statistics for the amount of duplicate received packets and the amount of times we cancel a relay because someone did it
+ before us */
+ uint32_t rxDupe = 0, txRelayCanceled = 0;
+
protected:
friend class RoutingModule;
@@ -148,3 +152,8 @@ extern Router *router;
/// Generate a unique packet id
// FIXME, move this someplace better
PacketId generatePacketId();
+
+#define BITFIELD_WANT_RESPONSE_SHIFT 1
+#define BITFIELD_OK_TO_MQTT_SHIFT 0
+#define BITFIELD_WANT_RESPONSE_MASK (1 << BITFIELD_WANT_RESPONSE_SHIFT)
+#define BITFIELD_OK_TO_MQTT_MASK (1 << BITFIELD_OK_TO_MQTT_SHIFT)
\ No newline at end of file
diff --git a/src/mesh/STM32WLE5JCInterface.cpp b/src/mesh/STM32WLE5JCInterface.cpp
index 3c1870d3b..499db9176 100644
--- a/src/mesh/STM32WLE5JCInterface.cpp
+++ b/src/mesh/STM32WLE5JCInterface.cpp
@@ -27,11 +27,11 @@ bool STM32WLE5JCInterface::init()
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage);
- LOG_INFO("STM32WLx init result %d\n", res);
+ LOG_INFO("STM32WLx init result %d", res);
- 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);
+ LOG_INFO("Frequency set to %f", getFreq());
+ LOG_INFO("Bandwidth set to %f", bw);
+ LOG_INFO("Power output set to %d", power);
if (res == RADIOLIB_ERR_NONE)
startReceive(); // start receiving
diff --git a/src/mesh/SX1262Interface.cpp b/src/mesh/SX1262Interface.cpp
index e96e72b71..4c0dea00b 100644
--- a/src/mesh/SX1262Interface.cpp
+++ b/src/mesh/SX1262Interface.cpp
@@ -1,3 +1,4 @@
+#if RADIOLIB_EXCLUDE_SX126X != 1
#include "SX1262Interface.h"
#include "configuration.h"
#include "error.h"
@@ -6,4 +7,5 @@ SX1262Interface::SX1262Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, R
RADIOLIB_PIN_TYPE busy)
: SX126xInterface(hal, cs, irq, rst, busy)
{
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/src/mesh/SX1262Interface.h b/src/mesh/SX1262Interface.h
index 31a12ae90..6e4616c8b 100644
--- a/src/mesh/SX1262Interface.h
+++ b/src/mesh/SX1262Interface.h
@@ -1,3 +1,4 @@
+#if RADIOLIB_EXCLUDE_SX126X != 1
#pragma once
#include "SX126xInterface.h"
@@ -10,4 +11,5 @@ class SX1262Interface : public SX126xInterface
public:
SX1262Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
RADIOLIB_PIN_TYPE busy);
-};
\ No newline at end of file
+};
+#endif
\ No newline at end of file
diff --git a/src/mesh/SX1268Interface.cpp b/src/mesh/SX1268Interface.cpp
index ea299fce5..fe6e9af89 100644
--- a/src/mesh/SX1268Interface.cpp
+++ b/src/mesh/SX1268Interface.cpp
@@ -1,3 +1,4 @@
+#if RADIOLIB_EXCLUDE_SX126X != 1
#include "SX1268Interface.h"
#include "configuration.h"
#include "error.h"
@@ -15,4 +16,5 @@ float SX1268Interface::getFreq()
return 433.125f;
else
return savedFreq;
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/src/mesh/SX1268Interface.h b/src/mesh/SX1268Interface.h
index c8bcf20a7..cc6dd3534 100644
--- a/src/mesh/SX1268Interface.h
+++ b/src/mesh/SX1268Interface.h
@@ -1,4 +1,5 @@
#pragma once
+#if RADIOLIB_EXCLUDE_SX126X != 1
#include "SX126xInterface.h"
@@ -13,3 +14,4 @@ class SX1268Interface : public SX126xInterface
SX1268Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
RADIOLIB_PIN_TYPE busy);
};
+#endif
\ No newline at end of file
diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp
index fcb3e4edb..c88ed39d9 100644
--- a/src/mesh/SX126xInterface.cpp
+++ b/src/mesh/SX126xInterface.cpp
@@ -1,3 +1,4 @@
+#if RADIOLIB_EXCLUDE_SX126X != 1
#include "SX126xInterface.h"
#include "configuration.h"
#include "error.h"
@@ -6,6 +7,8 @@
#include "PortduinoGlue.h"
#endif
+#include "Throttle.h"
+
// 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 SX126x power config forgotten)
#ifndef SX126X_MAX_POWER
@@ -17,7 +20,7 @@ SX126xInterface::SX126xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs
RADIOLIB_PIN_TYPE busy)
: RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module)
{
- LOG_DEBUG("SX126xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
+ LOG_DEBUG("SX126xInterface(cs=%d, irq=%d, rst=%d, busy=%d)", cs, irq, rst, busy);
}
/// Initialise the Driver transport hardware and software.
@@ -25,15 +28,35 @@ SX126xInterface::SX126xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs
/// \return true if initialisation succeeded.
template bool SX126xInterface::init()
{
-#ifdef SX126X_POWER_EN
- pinMode(SX126X_POWER_EN, OUTPUT);
+
+// Typically, the RF switch on SX126x boards is controlled by two signals, which are negations of each other (switched RFIO
+// paths). The negation is usually performed in hardware, or (suboptimal design) TXEN and RXEN are the two inputs to this style of
+// RF switch. On some boards, there is no hardware negation between CTRL and ¬CTRL, but CTRL is internally connected to DIO2, and
+// DIO2's switching is done by the SX126X itself, so the MCU can't control ¬CTRL at exactly the same time. One solution would be
+// to set ¬CTRL as SX126X_TXEN or SX126X_RXEN, but they may already be used for another purpose, such as controlling another
+// PA/LNA. Keeping ¬CTRL high seems to work, as long CTRL=1, ¬CTRL=1 has the opposite and stable RF path effect as CTRL=0 and
+// ¬CTRL=1, this depends on the RF switch, but it seems this usually works. Better hardware design, which is done most the time,
+// means this workaround is not necessary.
+#ifdef SX126X_ANT_SW // Perhaps add RADIOLIB_NC check, and beforehand define as such if it is undefined, but it is not commonly
+ // used and not part of the 'default' set of pin definitions.
+ digitalWrite(SX126X_ANT_SW, HIGH);
+ pinMode(SX126X_ANT_SW, OUTPUT);
+#endif
+
+#ifdef SX126X_POWER_EN // Perhaps add RADIOLIB_NC check, and beforehand define as such if it is undefined, but it is not commonly
+ // used and not part of the 'default' set of pin definitions.
digitalWrite(SX126X_POWER_EN, HIGH);
+ pinMode(SX126X_POWER_EN, OUTPUT);
#endif
#if ARCH_PORTDUINO
float tcxoVoltage = 0;
if (settingsMap[dio3_tcxo_voltage])
tcxoVoltage = 1.8;
+ if (settingsMap[sx126x_ant_sw] != RADIOLIB_NC) {
+ digitalWrite(settingsMap[sx126x_ant_sw], HIGH);
+ pinMode(settingsMap[sx126x_ant_sw], OUTPUT);
+ }
// FIXME: correct logic to default to not using TCXO if no voltage is specified for SX126X_DIO3_TCXO_VOLTAGE
#elif !defined(SX126X_DIO3_TCXO_VOLTAGE)
float tcxoVoltage =
@@ -45,9 +68,9 @@ template bool SX126xInterface::init()
// (DIO3 is not free to be used as an IRQ)
#endif
if (tcxoVoltage == 0)
- LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage\n");
+ LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage");
else
- LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V\n", tcxoVoltage);
+ LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", tcxoVoltage);
// FIXME: May want to set depending on a definition, currently all SX126x variant files use the DC-DC regulator option
bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC?
@@ -61,13 +84,13 @@ template bool SX126xInterface::init()
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength, tcxoVoltage, useRegulatorLDO);
// \todo Display actual typename of the adapter, not just `SX126x`
- LOG_INFO("SX126x init result %d\n", res);
+ LOG_INFO("SX126x init result %d", 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);
+ LOG_INFO("Frequency set to %f", getFreq());
+ LOG_INFO("Bandwidth set to %f", bw);
+ LOG_INFO("Power output set to %d", power);
// Overriding current limit
// (https://github.com/jgromes/RadioLib/blob/690a050ebb46e6097c5d00c371e961c1caa3b52e/src/modules/SX126x/SX126x.cpp#L85) using
@@ -78,53 +101,51 @@ template bool SX126xInterface::init()
// FIXME: Not ideal to increase SX1261 current limit above 60mA as it can only transmit max 15dBm, should probably only do it
// if using SX1262 or SX1268
res = lora.setCurrentLimit(currentLimit);
- LOG_DEBUG("Current limit set to %f\n", currentLimit);
- LOG_DEBUG("Current limit set result %d\n", res);
+ LOG_DEBUG("Current limit set to %f", currentLimit);
+ LOG_DEBUG("Current limit set result %d", res);
-#ifdef SX126X_DIO2_AS_RF_SWITCH
- LOG_DEBUG("Setting DIO2 as RF switch\n");
- bool dio2AsRfSwitch = true;
-#elif defined(ARCH_PORTDUINO)
- bool dio2AsRfSwitch = false;
- if (settingsMap[dio2_as_rf_switch]) {
- LOG_DEBUG("Setting DIO2 as RF switch\n");
- dio2AsRfSwitch = true;
- }
-#else
- LOG_DEBUG("Setting DIO2 as not RF switch\n");
- bool dio2AsRfSwitch = false;
-#endif
if (res == RADIOLIB_ERR_NONE) {
+#ifdef SX126X_DIO2_AS_RF_SWITCH
+ bool dio2AsRfSwitch = true;
+#elif defined(ARCH_PORTDUINO)
+ bool dio2AsRfSwitch = false;
+ if (settingsMap[dio2_as_rf_switch]) {
+ dio2AsRfSwitch = true;
+ }
+#else
+ bool dio2AsRfSwitch = false;
+#endif
res = lora.setDio2AsRfSwitch(dio2AsRfSwitch);
+ LOG_DEBUG("Set DIO2 as %sRF switch, result: %d", dio2AsRfSwitch ? "" : "not ", res);
}
// If a pin isn't defined, we set it to RADIOLIB_NC, it is safe to always do external RF switching with RADIOLIB_NC as it has
// no effect
#if ARCH_PORTDUINO
if (res == RADIOLIB_ERR_NONE) {
- LOG_DEBUG("Using MCU pin %i as RXEN and pin %i as TXEN to control RF switching\n", settingsMap[rxen], settingsMap[txen]);
+ LOG_DEBUG("Using MCU pin %i as RXEN and pin %i as TXEN to control RF switching", settingsMap[rxen], settingsMap[txen]);
lora.setRfSwitchPins(settingsMap[rxen], settingsMap[txen]);
}
#else
#ifndef SX126X_RXEN
#define SX126X_RXEN RADIOLIB_NC
- LOG_DEBUG("SX126X_RXEN not defined, defaulting to RADIOLIB_NC\n");
+ LOG_DEBUG("SX126X_RXEN not defined, defaulting to RADIOLIB_NC");
#endif
#ifndef SX126X_TXEN
#define SX126X_TXEN RADIOLIB_NC
- LOG_DEBUG("SX126X_TXEN not defined, defaulting to RADIOLIB_NC\n");
+ LOG_DEBUG("SX126X_TXEN not defined, defaulting to RADIOLIB_NC");
#endif
if (res == RADIOLIB_ERR_NONE) {
- LOG_DEBUG("Using MCU pin %i as RXEN and pin %i as TXEN to control RF switching\n", SX126X_RXEN, SX126X_TXEN);
+ LOG_DEBUG("Using MCU pin %i as RXEN and pin %i as TXEN to control RF switching", SX126X_RXEN, SX126X_TXEN);
lora.setRfSwitchPins(SX126X_RXEN, SX126X_TXEN);
}
#endif
if (config.lora.sx126x_rx_boosted_gain) {
uint16_t result = lora.setRxBoostedGainMode(true);
- LOG_INFO("Set RX gain to boosted mode; result: %d\n", result);
+ LOG_INFO("Set RX gain to boosted mode; result: %d", result);
} else {
uint16_t result = lora.setRxBoostedGainMode(false);
- LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d\n", result);
+ LOG_INFO("Set RX gain to power saving mode (boosted mode off); result: %d", result);
}
#if 0
@@ -182,17 +203,17 @@ template bool SX126xInterface::reconfigure()
err = lora.setSyncWord(syncWord);
if (err != RADIOLIB_ERR_NONE)
- LOG_ERROR("Radiolib error %d when attempting SX126X setSyncWord!\n", err);
+ LOG_ERROR("SX126X setSyncWord %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
err = lora.setCurrentLimit(currentLimit);
if (err != RADIOLIB_ERR_NONE)
- LOG_ERROR("Radiolib error %d when attempting SX126X setCurrentLimit!\n", err);
+ LOG_ERROR("SX126X setCurrentLimit %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
err = lora.setPreambleLength(preambleLength);
if (err != RADIOLIB_ERR_NONE)
- LOG_ERROR("Radiolib error %d when attempting SX126X setPreambleLength!\n", err);
+ LOG_ERROR("SX126X setPreambleLength %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
err = lora.setFrequency(getFreq());
@@ -204,7 +225,7 @@ template bool SX126xInterface::reconfigure()
err = lora.setOutputPower(power);
if (err != RADIOLIB_ERR_NONE)
- LOG_ERROR("Radiolib error %d when attempting SX126X setOutputPower!\n", err);
+ LOG_ERROR("SX126X setOutputPower %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
startReceive(); // restart receiving
@@ -224,7 +245,7 @@ template void SX126xInterface::setStandby()
int err = lora.standby();
if (err != RADIOLIB_ERR_NONE)
- LOG_DEBUG("SX126x standby failed with error %d\n", err);
+ LOG_DEBUG("SX126x standby %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
isReceiving = false; // If we were receiving, not any more
@@ -239,7 +260,7 @@ template void SX126xInterface::setStandby()
*/
template void SX126xInterface::addReceiveMetadata(meshtastic_MeshPacket *mp)
{
- // LOG_DEBUG("PacketStatus %x\n", lora.getPacketStatus());
+ // LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
mp->rx_snr = lora.getSNR();
mp->rx_rssi = lround(lora.getRSSI());
}
@@ -266,7 +287,7 @@ template void SX126xInterface::startReceive()
// Furthermore, we need the PREAMBLE_DETECTED and HEADER_VALID IRQ flag to detect whether we are actively receiving
int err = lora.startReceiveDutyCycleAuto(preambleLength, 8, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | RADIOLIB_IRQ_PREAMBLE_DETECTED);
if (err != RADIOLIB_ERR_NONE)
- LOG_ERROR("Radiolib error %d when attempting SX126X startReceiveDutyCycleAuto!\n", err);
+ LOG_ERROR("SX126X startReceiveDutyCycleAuto %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
RadioLibInterface::startReceive();
@@ -287,7 +308,7 @@ template bool SX126xInterface::isChannelActive()
if (result == RADIOLIB_LORA_DETECTED)
return true;
if (result != RADIOLIB_CHANNEL_FREE)
- LOG_ERROR("Radiolib error %d when attempting SX126X scanChannel!\n", result);
+ LOG_ERROR("SX126X scanChannel %s%d", radioLibErr, result);
assert(result != RADIOLIB_ERR_WRONG_MODEM);
return false;
@@ -298,37 +319,15 @@ template bool SX126xInterface::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.getIrqFlags();
- bool detected = (irq & (RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED));
- // Handle false detections
- if (detected) {
- uint32_t now = millis();
- if (!activeReceiveStart) {
- activeReceiveStart = now;
- } else if ((now - activeReceiveStart > 2 * preambleTimeMsec) && !(irq & RADIOLIB_SX126X_IRQ_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;
+ return receiveDetected(lora.getIrqFlags(), RADIOLIB_SX126X_IRQ_HEADER_VALID, RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED);
}
template bool SX126xInterface::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 `SX126x`
- LOG_DEBUG("SX126x entering sleep mode (FIXME, don't keep config)\n");
- setStandby(); // Stop any pending operations
+ LOG_DEBUG("SX126x entering sleep mode"); // (FIXME, don't keep config)
+ setStandby(); // Stop any pending operations
// turn off TCXO if it was powered
// FIXME - this isn't correct
@@ -344,3 +343,4 @@ template bool SX126xInterface::sleep()
return true;
}
+#endif
\ No newline at end of file
diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h
index b392cd3e4..45b39a68a 100644
--- a/src/mesh/SX126xInterface.h
+++ b/src/mesh/SX126xInterface.h
@@ -1,4 +1,5 @@
#pragma once
+#if RADIOLIB_EXCLUDE_SX126X != 1
#include "RadioLibInterface.h"
@@ -67,7 +68,5 @@ template class SX126xInterface : public RadioLibInterface
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
virtual void setStandby() override;
-
- private:
- uint32_t activeReceiveStart = 0;
};
+#endif
\ No newline at end of file
diff --git a/src/mesh/SX1280Interface.cpp b/src/mesh/SX1280Interface.cpp
index 3287f141f..9e0d42122 100644
--- a/src/mesh/SX1280Interface.cpp
+++ b/src/mesh/SX1280Interface.cpp
@@ -1,3 +1,4 @@
+#if RADIOLIB_EXCLUDE_SX128X != 1
#include "SX1280Interface.h"
#include "configuration.h"
#include "error.h"
@@ -7,3 +8,4 @@ SX1280Interface::SX1280Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, R
: SX128xInterface(hal, cs, irq, rst, busy)
{
}
+#endif
\ No newline at end of file
diff --git a/src/mesh/SX1280Interface.h b/src/mesh/SX1280Interface.h
index 8f2c4ec2e..534dd8084 100644
--- a/src/mesh/SX1280Interface.h
+++ b/src/mesh/SX1280Interface.h
@@ -1,5 +1,5 @@
#pragma once
-
+#if RADIOLIB_EXCLUDE_SX128X != 1
#include "SX128xInterface.h"
/**
@@ -12,3 +12,4 @@ class SX1280Interface : public SX128xInterface
SX1280Interface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst,
RADIOLIB_PIN_TYPE busy);
};
+#endif
diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp
index 9ff9ac2d7..9fe86cc6e 100644
--- a/src/mesh/SX128xInterface.cpp
+++ b/src/mesh/SX128xInterface.cpp
@@ -1,4 +1,6 @@
+#if RADIOLIB_EXCLUDE_SX128X != 1
#include "SX128xInterface.h"
+#include "Throttle.h"
#include "configuration.h"
#include "error.h"
#include "mesh/NodeDB.h"
@@ -17,7 +19,7 @@ SX128xInterface::SX128xInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE cs
RADIOLIB_PIN_TYPE busy)
: RadioLibInterface(hal, cs, irq, rst, busy, &lora), lora(&module)
{
- LOG_DEBUG("SX128xInterface(cs=%d, irq=%d, rst=%d, busy=%d)\n", cs, irq, rst, busy);
+ LOG_DEBUG("SX128xInterface(cs=%d, irq=%d, rst=%d, busy=%d)", cs, irq, rst, busy);
}
/// Initialise the Driver transport hardware and software.
@@ -66,10 +68,10 @@ template bool SX128xInterface::init()
int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength);
// \todo Display actual typename of the adapter, not just `SX128x`
- LOG_INFO("SX128x init result %d\n", res);
+ LOG_INFO("SX128x init result %d", res);
if ((config.lora.region != meshtastic_Config_LoRaConfig_RegionCode_LORA_24) && (res == RADIOLIB_ERR_INVALID_FREQUENCY)) {
- LOG_WARN("Radio chip only supports 2.4GHz LoRa. Adjusting Region and rebooting.\n");
+ LOG_WARN("Radio chip only supports 2.4GHz LoRa. Adjusting Region and rebooting.");
config.lora.region = meshtastic_Config_LoRaConfig_RegionCode_LORA_24;
nodeDB->saveToDisk(SEGMENT_CONFIG);
delay(2000);
@@ -78,13 +80,13 @@ template bool SX128xInterface::init()
#elif defined(ARCH_NRF52)
NVIC_SystemReset();
#else
- LOG_ERROR("FIXME implement reboot for this platform. Skipping for now.\n");
+ LOG_ERROR("FIXME implement reboot for this platform. Skipping for now.");
#endif
}
- 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);
+ LOG_INFO("Frequency set to %f", getFreq());
+ LOG_INFO("Bandwidth set to %f", bw);
+ LOG_INFO("Power output set to %d", power);
#if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC) && defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC)
if (res == RADIOLIB_ERR_NONE) {
@@ -127,12 +129,12 @@ template bool SX128xInterface::reconfigure()
err = lora.setSyncWord(syncWord);
if (err != RADIOLIB_ERR_NONE)
- LOG_ERROR("Radiolib error %d when attempting SX128X setSyncWord!\n", err);
+ LOG_ERROR("SX128X setSyncWord %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
err = lora.setPreambleLength(preambleLength);
if (err != RADIOLIB_ERR_NONE)
- LOG_ERROR("Radiolib error %d when attempting SX128X setPreambleLength!\n", err);
+ LOG_ERROR("SX128X setPreambleLength %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
err = lora.setFrequency(getFreq());
@@ -144,7 +146,7 @@ template bool SX128xInterface::reconfigure()
err = lora.setOutputPower(power);
if (err != RADIOLIB_ERR_NONE)
- LOG_ERROR("Radiolib error %d when attempting SX128X setOutputPower!\n", err);
+ LOG_ERROR("SX128X setOutputPower %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
startReceive(); // restart receiving
@@ -169,7 +171,7 @@ template void SX128xInterface::setStandby()
int err = lora.standby();
if (err != RADIOLIB_ERR_NONE)
- LOG_ERROR("SX128x standby failed with error %d\n", err);
+ LOG_ERROR("SX128x standby %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
#if ARCH_PORTDUINO
if (settingsMap[rxen] != RADIOLIB_NC) {
@@ -198,7 +200,7 @@ template void SX128xInterface::setStandby()
*/
template void SX128xInterface::addReceiveMetadata(meshtastic_MeshPacket *mp)
{
- // LOG_DEBUG("PacketStatus %x\n", lora.getPacketStatus());
+ // LOG_DEBUG("PacketStatus %x", lora.getPacketStatus());
mp->rx_snr = lora.getSNR();
mp->rx_rssi = lround(lora.getRSSI());
}
@@ -259,7 +261,7 @@ template void SX128xInterface::startReceive()
int err = lora.startReceive(RADIOLIB_SX128X_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | RADIOLIB_IRQ_PREAMBLE_DETECTED);
if (err != RADIOLIB_ERR_NONE)
- LOG_ERROR("Radiolib error %d when attempting SX128X startReceive!\n", err);
+ LOG_ERROR("SX128X startReceive %s%d", radioLibErr, err);
assert(err == RADIOLIB_ERR_NONE);
RadioLibInterface::startReceive();
@@ -280,7 +282,7 @@ template bool SX128xInterface::isChannelActive()
if (result == RADIOLIB_LORA_DETECTED)
return true;
if (result != RADIOLIB_CHANNEL_FREE)
- LOG_ERROR("Radiolib error %d when attempting SX128X scanChannel!\n", result);
+ LOG_ERROR("SX128X scanChannel %s%d", radioLibErr, result);
assert(result != RADIOLIB_ERR_WRONG_MODEM);
return false;
@@ -289,36 +291,15 @@ template bool SX128xInterface::isChannelActive()
/** Could we send right now (i.e. either not actively receiving or transmitting)? */
template bool SX128xInterface::isActivelyReceiving()
{
- uint16_t irq = lora.getIrqStatus();
- bool detected = (irq & (RADIOLIB_SX128X_IRQ_HEADER_VALID | RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED));
-
- // Handle false detections
- if (detected) {
- uint32_t now = millis();
- if (!activeReceiveStart) {
- activeReceiveStart = now;
- } else if ((now - activeReceiveStart > 2 * preambleTimeMsec) && !(irq & RADIOLIB_SX128X_IRQ_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;
- }
- }
-
- return detected;
+ return receiveDetected(lora.getIrqStatus(), RADIOLIB_SX128X_IRQ_HEADER_VALID, RADIOLIB_SX128X_IRQ_PREAMBLE_DETECTED);
}
template bool SX128xInterface::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 `SX128x`
- LOG_DEBUG("SX128x entering sleep mode (FIXME, don't keep config)\n");
- setStandby(); // Stop any pending operations
+ LOG_DEBUG("SX128x entering sleep mode"); // (FIXME, don't keep config)
+ setStandby(); // Stop any pending operations
// turn off TCXO if it was powered
// FIXME - this isn't correct
@@ -333,4 +314,5 @@ template bool SX128xInterface::sleep()
#endif
return true;
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/src/mesh/SX128xInterface.h b/src/mesh/SX128xInterface.h
index f7fd35b25..bba31dab4 100644
--- a/src/mesh/SX128xInterface.h
+++ b/src/mesh/SX128xInterface.h
@@ -67,7 +67,4 @@ template class SX128xInterface : public RadioLibInterface
virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override;
virtual void setStandby() override;
-
- private:
- uint32_t activeReceiveStart = 0;
};
diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp
index 9f59aa971..4a42e5197 100644
--- a/src/mesh/StreamAPI.cpp
+++ b/src/mesh/StreamAPI.cpp
@@ -1,6 +1,7 @@
#include "StreamAPI.h"
#include "PowerFSM.h"
#include "RTC.h"
+#include "Throttle.h"
#include "configuration.h"
#define START1 0x94
@@ -20,10 +21,9 @@ int32_t StreamAPI::runOncePart()
*/
int32_t StreamAPI::readStream()
{
- uint32_t now = millis();
if (!stream->available()) {
// Nothing available this time, if the computer has talked to us recently, poll often, otherwise let CPU sleep a long time
- bool recentRx = (now - lastRxMsec) < 2000;
+ bool recentRx = Throttle::isWithinTimespanMs(lastRxMsec, 2000);
return recentRx ? 5 : 250;
} else {
while (stream->available()) { // Currently we never want to block
@@ -71,7 +71,7 @@ int32_t StreamAPI::readStream()
}
// we had bytes available this time, so assume we might have them next time also
- lastRxMsec = now;
+ lastRxMsec = millis();
return 0;
}
}
@@ -115,7 +115,7 @@ void StreamAPI::emitRebooted()
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_rebooted_tag;
fromRadioScratch.rebooted = true;
- // LOG_DEBUG("Emitting reboot packet for serial shell\n");
+ // LOG_DEBUG("Emitting reboot packet for serial shell");
emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, meshtastic_FromRadio_size, &meshtastic_FromRadio_msg, &fromRadioScratch));
}
diff --git a/src/mesh/Throttle.cpp b/src/mesh/Throttle.cpp
index d8f23f9dc..f278cc843 100644
--- a/src/mesh/Throttle.cpp
+++ b/src/mesh/Throttle.cpp
@@ -24,4 +24,12 @@ bool Throttle::execute(uint32_t *lastExecutionMs, uint32_t minumumIntervalMs, vo
onDefer();
}
return false;
+}
+
+/// @brief Check if the last execution time is within the interval
+/// @param lastExecutionMs The last execution time in milliseconds
+/// @param timeSpanMs The interval in milliseconds of the timespan
+bool Throttle::isWithinTimespanMs(uint32_t lastExecutionMs, uint32_t timeSpanMs)
+{
+ return (millis() - lastExecutionMs) < timeSpanMs;
}
\ No newline at end of file
diff --git a/src/mesh/Throttle.h b/src/mesh/Throttle.h
index 8115595a4..8b4bb5d30 100644
--- a/src/mesh/Throttle.h
+++ b/src/mesh/Throttle.h
@@ -6,4 +6,5 @@ class Throttle
{
public:
static bool execute(uint32_t *lastExecutionMs, uint32_t minumumIntervalMs, void (*func)(void), void (*onDefer)(void) = NULL);
+ static bool isWithinTimespanMs(uint32_t lastExecutionMs, uint32_t intervalMs);
};
\ No newline at end of file
diff --git a/src/mesh/TypeConversions.cpp b/src/mesh/TypeConversions.cpp
index 513728ca5..550f87021 100644
--- a/src/mesh/TypeConversions.cpp
+++ b/src/mesh/TypeConversions.cpp
@@ -11,9 +11,13 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo
info.last_heard = lite->last_heard;
info.channel = lite->channel;
info.via_mqtt = lite->via_mqtt;
- info.hops_away = lite->hops_away;
info.is_favorite = lite->is_favorite;
+ if (lite->has_hops_away) {
+ info.has_hops_away = true;
+ info.hops_away = lite->hops_away;
+ }
+
if (lite->has_position) {
info.has_position = true;
if (lite->position.latitude_i != 0)
@@ -78,7 +82,7 @@ meshtastic_UserLite TypeConversions::ConvertToUserLite(meshtastic_User user)
lite.hw_model = user.hw_model;
lite.role = user.role;
lite.is_licensed = user.is_licensed;
- memccpy(lite.macaddr, user.macaddr, sizeof(user.macaddr), sizeof(lite.macaddr));
+ memcpy(lite.macaddr, user.macaddr, sizeof(lite.macaddr));
memcpy(lite.public_key.bytes, user.public_key.bytes, sizeof(lite.public_key.bytes));
lite.public_key.size = user.public_key.size;
return lite;
@@ -94,7 +98,7 @@ meshtastic_User TypeConversions::ConvertToUser(uint32_t nodeNum, meshtastic_User
user.hw_model = lite.hw_model;
user.role = lite.role;
user.is_licensed = lite.is_licensed;
- memccpy(user.macaddr, lite.macaddr, sizeof(lite.macaddr), sizeof(user.macaddr));
+ memcpy(user.macaddr, lite.macaddr, sizeof(user.macaddr));
memcpy(user.public_key.bytes, lite.public_key.bytes, sizeof(user.public_key.bytes));
user.public_key.size = lite.public_key.size;
diff --git a/src/mesh/TypedQueue.h b/src/mesh/TypedQueue.h
index c96edae8e..f7d016f10 100644
--- a/src/mesh/TypedQueue.h
+++ b/src/mesh/TypedQueue.h
@@ -14,7 +14,7 @@
*/
template class TypedQueue
{
- static_assert(std::is_pod::value, "T must be pod");
+ static_assert(std::is_standard_layout::value, "T must be standard layout");
QueueHandle_t h;
concurrency::OSThread *reader = NULL;
diff --git a/src/mesh/aes-ccm.cpp b/src/mesh/aes-ccm.cpp
index cd18ae6c5..b9af14fdb 100644
--- a/src/mesh/aes-ccm.cpp
+++ b/src/mesh/aes-ccm.cpp
@@ -95,7 +95,7 @@ static void aes_ccm_encr(size_t L, const uint8_t *in, size_t len, uint8_t *out,
*out++ ^= *in++;
}
}
-static void aes_ccm_encr_auth(size_t M, uint8_t *x, uint8_t *a, uint8_t *auth)
+static void aes_ccm_encr_auth(size_t M, const uint8_t *x, uint8_t *a, uint8_t *auth)
{
size_t i;
uint8_t tmp[AES_BLOCK_SIZE];
diff --git a/src/mesh/api/ServerAPI.cpp b/src/mesh/api/ServerAPI.cpp
index 140567ad2..7705858b5 100644
--- a/src/mesh/api/ServerAPI.cpp
+++ b/src/mesh/api/ServerAPI.cpp
@@ -5,7 +5,7 @@
template
ServerAPI::ServerAPI(T &_client) : StreamAPI(&client), concurrency::OSThread("ServerAPI"), client(_client)
{
- LOG_INFO("Incoming wifi connection\n");
+ LOG_INFO("Incoming API connection");
}
template ServerAPI::~ServerAPI()
@@ -30,7 +30,7 @@ template int32_t ServerAPI::runOnce()
if (client.connected()) {
return StreamAPI::runOncePart();
} else {
- LOG_INFO("Client dropped connection, suspending API service\n");
+ LOG_INFO("Client dropped connection, suspending API service");
enabled = false; // we no longer need to run
return 0;
}
@@ -45,16 +45,37 @@ template void APIServerPort::init()
template int32_t APIServerPort::runOnce()
{
+#ifdef ARCH_ESP32
+#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
+ auto client = U::accept();
+#else
auto client = U::available();
+#endif
+#else
+ auto client = U::available();
+#endif
if (client) {
// Close any previous connection (see FIXME in header file)
if (openAPI) {
- LOG_INFO("Force closing previous TCP connection\n");
+#if RAK_4631
+ // RAK13800 Ethernet requests periodically take more time
+ // This backoff addresses most cases keeping max wait < 1s
+ // Reconnections are delayed by full wait time
+ if (waitTime < 400) {
+ waitTime *= 2;
+ LOG_INFO("Previous TCP connection still open, trying again in %dms", waitTime);
+ return waitTime;
+ }
+#endif
+ LOG_INFO("Force closing previous TCP connection");
delete openAPI;
}
openAPI = new T(client);
}
+#if RAK_4631
+ waitTime = 100;
+#endif
return 100; // only check occasionally for incoming connections
}
diff --git a/src/mesh/api/ServerAPI.h b/src/mesh/api/ServerAPI.h
index dd2a767c9..5b84fddd7 100644
--- a/src/mesh/api/ServerAPI.h
+++ b/src/mesh/api/ServerAPI.h
@@ -31,7 +31,7 @@ template class ServerAPI : public StreamAPI, private concurrency::OSTh
};
/**
- * Listens for incoming connections and does accepts and creates instances of WiFiServerAPI as needed
+ * Listens for incoming connections and does accepts and creates instances of ServerAPI as needed
*/
template