Merge branch 'portexpander-keyboard' of https://github.com/meshtastic/firmware into portexpander-keyboard

# Conflicts:
#	src/input/cardKbI2cImpl.cpp
This commit is contained in:
Thomas Göttgens 2024-08-23 11:14:46 +02:00
commit d36c02f271
373 changed files with 19716 additions and 1710 deletions

View File

@ -16,6 +16,9 @@ body:
options:
- NRF52
- ESP32
- RP2040
- Linux Native
- other
validations:
required: true
- type: textarea

View File

@ -5,37 +5,25 @@ runs:
using: "composite"
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: "recursive"
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Install cppcheck
- name: Install dependencies
shell: bash
run: |
sudo apt-get install -y cppcheck
- name: Install libbluetooth
shell: bash
run: |
sudo apt-get install -y libbluetooth-dev
- name: Install libgpiod
shell: bash
run: |
sudo apt-get install -y libgpiod-dev
- name: Install libyaml-cpp
shell: bash
run: |
sudo apt-get install -y libyaml-cpp-dev
sudo apt-get -y update
sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev
- name: Setup Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: 3.x
- name: Cache python libs
uses: actions/cache@v3
uses: actions/cache@v4
id: cache-pip # needed in if test
with:
path: ~/.cache/pip

View File

@ -11,13 +11,13 @@ jobs:
build-esp32:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Build base
id: base
uses: ./.github/actions/setup-base
- name: Pull web ui
uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/web
file: build.tar
@ -41,7 +41,7 @@ jobs:
run: bin/build-esp32.sh ${{ inputs.board }}
- name: Pull OTA Firmware
uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/firmware-ota
file: firmware.bin
@ -54,9 +54,10 @@ jobs:
id: version
- name: Store binaries as an artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
overwrite: true
path: |
release/*.bin
release/*.elf

View File

@ -13,13 +13,13 @@ jobs:
build-esp32-c3:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Build base
id: base
uses: ./.github/actions/setup-base
- name: Pull web ui
uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/web
file: build.tar
@ -41,7 +41,7 @@ jobs:
run: bin/build-esp32.sh ${{ inputs.board }}
- name: Pull OTA Firmware
uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/firmware-ota
file: firmware-c3.bin
@ -54,9 +54,10 @@ jobs:
id: version
- name: Store binaries as an artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
overwrite: true
path: |
release/*.bin
release/*.elf

View File

@ -11,13 +11,13 @@ jobs:
build-esp32-s3:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Build base
id: base
uses: ./.github/actions/setup-base
- name: Pull web ui
uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/web
file: build.tar
@ -39,7 +39,7 @@ jobs:
run: bin/build-esp32.sh ${{ inputs.board }}
- name: Pull OTA Firmware
uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/firmware-ota
file: firmware-s3.bin
@ -52,9 +52,10 @@ jobs:
id: version
- name: Store binaries as an artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
overwrite: true
path: |
release/*.bin
release/*.elf

85
.github/workflows/build_native.yml vendored Normal file
View File

@ -0,0 +1,85 @@
name: Build Native
on: workflow_call
permissions:
contents: write
packages: write
jobs:
build-native:
runs-on: ubuntu-latest
steps:
- name: Install libbluetooth
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Upgrade python tools
shell: bash
run: |
python -m pip install --upgrade pip
pip install -U platformio adafruit-nrfutil
pip install -U meshtastic --pre
- name: Upgrade platformio
shell: bash
run: |
pio upgrade
- name: Build Native
run: bin/build-native.sh
- name: Get release version string
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-native-${{ steps.version.outputs.version }}.zip
overwrite: true
path: |
release/meshtasticd_linux_x86_64
bin/config-dist.yaml
- name: Docker login
if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
uses: docker/login-action@v3
continue-on-error: true # FIXME: Failing docker login auth
with:
username: meshtastic
password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
- name: Docker setup
if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
continue-on-error: true # FIXME: Failing docker login auth
uses: docker/setup-buildx-action@v3
- name: Docker build and push tagged versions
if: ${{ github.event_name == 'workflow_dispatch' }}
continue-on-error: true # FIXME: Failing docker login auth
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: meshtastic/device-simulator:${{ steps.version.outputs.version }}
- name: Docker build and push
if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
continue-on-error: true # FIXME: Failing docker login auth
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
push: true
tags: meshtastic/device-simulator:latest

View File

@ -11,7 +11,7 @@ jobs:
build-nrf52:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Build base
id: base
uses: ./.github/actions/setup-base
@ -24,9 +24,10 @@ jobs:
id: version
- name: Store binaries as an artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
overwrite: true
path: |
release/*.uf2
release/*.elf

View File

@ -10,8 +10,13 @@ jobs:
build-raspbian:
runs-on: [self-hosted, linux, ARM64]
steps:
- name: Install libbluetooth
shell: bash
run: |
apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
@ -37,9 +42,10 @@ jobs:
id: version
- name: Store binaries as an artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: firmware-raspbian-${{ steps.version.outputs.version }}.zip
overwrite: true
path: |
release/meshtasticd_linux_aarch64
bin/config-dist.yaml

View File

@ -0,0 +1,51 @@
name: Build Raspbian Arm
on: workflow_call
permissions:
contents: write
packages: write
jobs:
build-raspbian-armv7l:
runs-on: [self-hosted, linux, ARM]
steps:
- name: Install libbluetooth
shell: bash
run: |
apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Upgrade python tools
shell: bash
run: |
python -m pip install --upgrade pip
pip install -U platformio adafruit-nrfutil
pip install -U meshtastic --pre
- name: Upgrade platformio
shell: bash
run: |
pio upgrade
- name: Build Raspbian
run: bin/build-native.sh
- name: Get release version string
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Store binaries as an artifact
uses: actions/upload-artifact@v4
with:
name: firmware-raspbian-armv7l-${{ steps.version.outputs.version }}.zip
overwrite: true
path: |
release/meshtasticd_linux_armv7l
bin/config-dist.yaml

View File

@ -11,7 +11,7 @@ jobs:
build-rpi2040:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Build base
id: base
uses: ./.github/actions/setup-base
@ -24,9 +24,10 @@ jobs:
id: version
- name: Store binaries as an artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
overwrite: true
path: |
release/*.uf2
release/*.elf

View File

@ -8,7 +8,7 @@ on:
branches: [master, develop]
paths-ignore:
- "**.md"
- "version.properties"
- version.properties
# Note: This is different from "pull_request". Need to specify ref when doing checkouts.
pull_request_target:
@ -20,209 +20,104 @@ on:
workflow_dispatch:
jobs:
check:
setup:
strategy:
fail-fast: false
matrix:
include:
- board: rak11200
- board: tlora-v2-1-1_6
- board: tbeam
- board: heltec-v2_1
- board: meshtastic-diy-v1
- board: rak4631
- board: t-echo
- board: station-g2
- board: m5stack-coreink
- board: tbeam-s3-core
- board: tlora-t3s3-v1
- board: t-watch-s3
- board: t-deck
#- board: rak11310
arch: [esp32, esp32s3, esp32c3, nrf52840, rp2040, check]
runs-on: ubuntu-latest
steps:
- id: checkout
uses: actions/checkout@v4
name: Checkout base
- id: jsonStep
run: |
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
echo "$TARGETS"
echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
outputs:
esp32: ${{ steps.jsonStep.outputs.esp32 }}
esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
check: ${{ steps.jsonStep.outputs.check }}
check:
needs: setup
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.check) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Build base
id: base
uses: ./.github/actions/setup-base
- name: Trunk Check
if: ${{ github.event_name != 'workflow_dispatch' }}
uses: trunk-io/trunk-action@782e83f803ca6e369f035d64c6ba2768174ba61b
- name: Check ${{ matrix.board }}
run: bin/check-all.sh ${{ matrix.board }}
build-esp32:
needs: setup
strategy:
fail-fast: false
matrix:
include:
- board: rak11200
- board: tlora-v2
- board: tlora-v1
- board: tlora_v1_3
- board: tlora-v2-1-1_6
- board: tlora-v2-1-1_6-tcxo
- board: tlora-v2-1-1_8
- board: tbeam
- board: heltec-v2_0
- board: heltec-v2_1
- board: tbeam0_7
- board: meshtastic-diy-v1
- board: hydra
- board: meshtastic-dr-dev
- board: nano-g1
- board: station-g1
- board: m5stack-core
- board: m5stack-coreink
- board: nano-g1-explorer
- board: chatter2
matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
uses: ./.github/workflows/build_esp32.yml
with:
board: ${{ matrix.board }}
build-esp32-s3:
needs: setup
strategy:
fail-fast: false
matrix:
include:
- board: heltec-v3
- board: heltec-wsl-v3
- board: heltec-wireless-tracker
- board: heltec-wireless-tracker-V1-0
- board: heltec-wireless-paper-v1_0
- board: heltec-wireless-paper #v1.1
- board: tbeam-s3-core
- board: tlora-t3s3-v1
- board: t-watch-s3
- board: t-deck
- board: picomputer-s3
- board: station-g2
- board: unphone
matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
uses: ./.github/workflows/build_esp32_s3.yml
with:
board: ${{ matrix.board }}
build-esp32-c3:
needs: setup
strategy:
fail-fast: false
matrix:
include:
- board: heltec-ht62-esp32c3-sx1262
matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
uses: ./.github/workflows/build_esp32_c3.yml
with:
board: ${{ matrix.board }}
build-nrf52:
needs: setup
strategy:
fail-fast: false
matrix:
include:
- board: rak4631
- board: rak4631_eink
- board: monteops_hw1
- board: t-echo
- board: canaryone
- board: pca10059_diy_eink
- board: feather_diy
- board: nano-g2-ultra
matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
uses: ./.github/workflows/build_nrf52.yml
with:
board: ${{ matrix.board }}
build-rpi2040:
needs: setup
strategy:
fail-fast: false
matrix:
include:
- board: pico
- board: picow
- board: rak11310
- board: senselora_rp2040
- board: rp2040-lora
matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
uses: ./.github/workflows/build_rpi2040.yml
with:
board: ${{ matrix.board }}
build-raspbian:
strategy:
fail-fast: false
max-parallel: 1
uses: ./.github/workflows/build_raspbian.yml
package-raspbian:
uses: ./.github/workflows/package_raspbian.yml
build-native:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build base
id: base
uses: ./.github/actions/setup-base
package-raspbian-armv7l:
uses: ./.github/workflows/package_raspbian_armv7l.yml
# We now run integration test before other build steps (to quickly see runtime failures)
#- name: Build for native
# run: platformio run -e native
#- name: Integration test
# run: |
#.pio/build/native/program
#& sleep 20 # 5 seconds was not enough
#echo "Simulator started, launching python test..."
#python3 -c 'from meshtastic.test import testSimulator; testSimulator()'
- name: Build Native
run: bin/build-native.sh
- name: Get release version string
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Store binaries as an artifact
uses: actions/upload-artifact@v3
with:
name: firmware-native-${{ steps.version.outputs.version }}.zip
path: |
release/device-*.sh
release/device-*.bat
- name: Docker login
if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
uses: docker/login-action@v2
with:
username: meshtastic
password: ${{ secrets.DOCKER_TOKEN }}
- name: Docker setup
if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
uses: docker/setup-buildx-action@v2
- name: Docker build and push tagged versions
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
push: true
tags: meshtastic/device-simulator:${{ steps.version.outputs.version }}
- name: Docker build and push
if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
push: true
tags: meshtastic/device-simulator:latest
package-native:
uses: ./.github/workflows/package_amd64.yml
after-checks:
runs-on: ubuntu-latest
needs: [check]
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
@ -238,21 +133,22 @@ jobs:
build-esp32-s3,
build-esp32-c3,
build-nrf52,
build-raspbian,
build-native,
build-rpi2040,
package-raspbian,
package-raspbian-armv7l,
package-native
]
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
path: ./
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
@ -262,25 +158,30 @@ jobs:
id: version
- name: Move files up
run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./*esp32c3*/bleota-c3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase_v2.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat ./firmware-raspbian-*/release/meshtasticd_linux_aarch64 ./firmware-raspbian-*/bin/config-dist.yaml
run: mv -b -t ./ ./release/meshtasticd_linux_* ./bin/config-dist.yaml ./bin/device-*.sh ./bin/device-*.bat
- name: Repackage in single firmware zip
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: firmware-${{ steps.version.outputs.version }}
overwrite: true
path: |
./*.bin
./*.uf2
./firmware-*.bin
./firmware-*.uf2
./firmware-*-ota.zip
./device-*.sh
./device-*.bat
./meshtasticd_linux_arm64
./meshtasticd_linux_*
./config-dist.yaml
./littlefs-*.bin
./bleota*bin
./Meshtastic_nRF52_factory_erase*.uf2
retention-days: 90
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: firmware-${{ steps.version.outputs.version }}
merge-multiple: true
path: ./output
# For diagnostics
@ -296,9 +197,10 @@ jobs:
run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output
- name: Repackage in single elfs zip
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: debug-elfs-${{ steps.version.outputs.version }}.zip
overwrite: true
path: ./*.elf
retention-days: 30
@ -320,10 +222,10 @@ jobs:
needs: [gather-artifacts, after-checks]
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: 3.x
@ -331,14 +233,17 @@ jobs:
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: firmware-${{ steps.version.outputs.version }}
merge-multiple: true
path: ./output
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: artifact-deb
pattern: meshtasticd_${{ steps.version.outputs.version }}_*.deb
merge-multiple: true
path: ./output
- name: Display structure of downloaded files
run: ls -R
@ -349,11 +254,12 @@ jobs:
chmod +x ./output/device-update.sh
- name: Zip firmware
run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output
run: zip -j -9 -r ./firmware-${{ steps.version.outputs.version }}.zip ./output -x *.deb
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: debug-elfs-${{ steps.version.outputs.version }}.zip
merge-multiple: true
path: ./elfs
- name: Zip Elfs
@ -396,22 +302,42 @@ jobs:
asset_name: debug-elfs-${{ steps.version.outputs.version }}.zip
asset_content_type: application/zip
- name: Add raspbian .deb
- name: Add raspbian aarch64 .deb
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ github.token }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./meshtasticd_${{ steps.version.outputs.version }}_arm64.deb
asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_arm64.deb
asset_name: meshtasticd_${{ steps.version.outputs.version }}_arm64.deb
asset_content_type: application/vnd.debian.binary-package
- name: Add raspbian armv7l .deb
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ github.token }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_armhf.deb
asset_name: meshtasticd_${{ steps.version.outputs.version }}_armhf.deb
asset_content_type: application/vnd.debian.binary-package
- name: Add raspbian amd64 .deb
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ github.token }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_amd64.deb
asset_name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb
asset_content_type: application/vnd.debian.binary-package
- name: Bump version.properties
run: >-
bin/bump_version.py
- name: Create version.properties pull request
uses: peter-evans/create-pull-request@v3
uses: peter-evans/create-pull-request@v6
with:
add-paths: |
version.properties

View File

@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Trunk Check
uses: trunk-io/trunk-action@782e83f803ca6e369f035d64c6ba2768174ba61b

78
.github/workflows/package_amd64.yml vendored Normal file
View File

@ -0,0 +1,78 @@
name: Package Native
on:
workflow_call:
workflow_dispatch:
permissions:
contents: write
packages: write
jobs:
build-native:
uses: ./.github/workflows/build_native.yml
package-native:
runs-on: ubuntu-latest
needs: build-native
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Pull web ui
uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/web
file: build.tar
target: build.tar
token: ${{ secrets.GITHUB_TOKEN }}
- name: Get release version string
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: firmware-native-${{ steps.version.outputs.version }}.zip
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
- name: build .debpkg
run: |
mkdir -p .debpkg/DEBIAN
mkdir -p .debpkg/usr/share/doc/meshtasticd/web
mkdir -p .debpkg/usr/sbin
mkdir -p .debpkg/etc/meshtasticd
mkdir -p .debpkg/usr/lib/systemd/system/
tar -xf build.tar -C .debpkg/usr/share/doc/meshtasticd/web
gunzip .debpkg/usr/share/doc/meshtasticd/web/*.gz
cp release/meshtasticd_linux_x86_64 .debpkg/usr/sbin/meshtasticd
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
chmod +x .debpkg/usr/sbin/meshtasticd
cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
chmod +x .debpkg/DEBIAN/conffiles
- uses: jiro4989/build-deb-action@v3
with:
package: meshtasticd
package_root: .debpkg
maintainer: Jonathan Bennett
version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.*
arch: amd64
depends: libyaml-cpp0.7, openssl, libulfius2.7
desc: Native Linux Meshtastic binary.
- uses: actions/upload-artifact@v4
with:
name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb
overwrite: true
path: |
./*.deb

View File

@ -17,14 +17,14 @@ jobs:
needs: build-raspbian
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Pull web ui
uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/web
file: build.tar
@ -36,15 +36,17 @@ jobs:
id: version
- name: Download artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
name: firmware-raspbian-${{ steps.version.outputs.version }}.zip
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
- name: build .debpkg
run: |
mkdir -p .debpkg/DEBIAN
mkdir -p .debpkg/usr/share/doc/meshtasticd/web
mkdir -p .debpkg/usr/sbin
mkdir -p .debpkg/etc/meshtasticd
@ -55,6 +57,8 @@ jobs:
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
chmod +x .debpkg/usr/sbin/meshtasticd
cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
chmod +x .debpkg/DEBIAN/conffiles
- uses: jiro4989/build-deb-action@v3
with:
@ -66,8 +70,9 @@ jobs:
depends: libyaml-cpp0.7, openssl, libulfius2.7
desc: Native Linux Meshtastic binary.
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: artifact-deb
name: meshtasticd_${{ steps.version.outputs.version }}_arm64.deb
overwrite: true
path: |
./*.deb

View File

@ -0,0 +1,78 @@
name: Package Raspbian
on:
workflow_call:
workflow_dispatch:
permissions:
contents: write
packages: write
jobs:
build-raspbian_armv7l:
uses: ./.github/workflows/build_raspbian_armv7l.yml
package-raspbian_armv7l:
runs-on: ubuntu-latest
needs: build-raspbian_armv7l
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
- name: Pull web ui
uses: dsaltares/fetch-gh-release-asset@master
with:
repo: meshtastic/web
file: build.tar
target: build.tar
token: ${{ secrets.GITHUB_TOKEN }}
- name: Get release version string
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: firmware-raspbian-armv7l-${{ steps.version.outputs.version }}.zip
merge-multiple: true
- name: Display structure of downloaded files
run: ls -R
- name: build .debpkg
run: |
mkdir -p .debpkg/DEBIAN
mkdir -p .debpkg/usr/share/doc/meshtasticd/web
mkdir -p .debpkg/usr/sbin
mkdir -p .debpkg/etc/meshtasticd
mkdir -p .debpkg/usr/lib/systemd/system/
tar -xf build.tar -C .debpkg/usr/share/doc/meshtasticd/web
gunzip .debpkg/usr/share/doc/meshtasticd/web/*.gz
cp release/meshtasticd_linux_armv7l .debpkg/usr/sbin/meshtasticd
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
chmod +x .debpkg/usr/sbin/meshtasticd
cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
chmod +x .debpkg/DEBIAN/conffiles
- uses: jiro4989/build-deb-action@v3
with:
package: meshtasticd
package_root: .debpkg
maintainer: Jonathan Bennett
version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.*
arch: armhf
depends: libyaml-cpp0.7, openssl, libulfius2.7
desc: Native Linux Meshtastic binary.
- uses: actions/upload-artifact@v4
with:
name: meshtasticd_${{ steps.version.outputs.version }}_armhf.deb
overwrite: true
path: |
./*.deb

View File

@ -16,7 +16,7 @@ jobs:
steps:
# step 1
- name: clone application source code
uses: actions/checkout@v3
uses: actions/checkout@v4
# step 2
- name: flawfinder_scan
@ -27,14 +27,15 @@ jobs:
# step 3
- name: save report as pipeline artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: flawfinder_report.sarif
overwrite: true
path: flawfinder_report.sarif
# step 4
- name: publish code scanning alerts
uses: github/codeql-action/upload-sarif@v2
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: flawfinder_report.sarif
category: flawfinder

View File

@ -17,7 +17,7 @@ jobs:
steps:
# step 1
- name: clone application source code
uses: actions/checkout@v3
uses: actions/checkout@v4
# step 2
- name: full scan
@ -29,14 +29,15 @@ jobs:
# step 3
- name: save report as pipeline artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: report.sarif
overwrite: true
path: report.sarif
# step 4
- name: publish code scanning alerts
uses: github/codeql-action/upload-sarif@v2
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: report.sarif
category: semgrep

View File

@ -11,7 +11,7 @@ jobs:
steps:
# step 1
- name: clone application source code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0

View File

@ -16,7 +16,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Trunk Check
uses: trunk-io/trunk-action@v1

View File

@ -7,7 +7,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: true
@ -26,7 +26,7 @@ jobs:
./bin/regen-protos.sh
- name: Create pull request
uses: peter-evans/create-pull-request@v3
uses: peter-evans/create-pull-request@v6
with:
add-paths: |
protobufs

2
.trunk/configs/.bandit Normal file
View File

@ -0,0 +1,2 @@
[bandit]
skips = B101

View File

@ -1,32 +1,32 @@
version: 0.1
cli:
version: 1.20.1
version: 1.22.1
plugins:
sources:
- id: trunk
ref: v1.4.4
ref: v1.5.0
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- trufflehog@3.68.5
- trufflehog@3.76.3
- yamllint@1.35.1
- bandit@1.7.7
- checkov@3.2.32
- bandit@1.7.8
- checkov@3.2.95
- terrascan@1.19.1
- trivy@0.49.1
- trivy@0.51.1
#- trufflehog@3.63.2-rc0
- taplo@0.8.1
- ruff@0.3.1
- ruff@0.4.4
- isort@5.13.2
- markdownlint@0.39.0
- oxipng@9.0.0
- svgo@3.2.0
- actionlint@1.6.27
- markdownlint@0.40.0
- oxipng@9.1.1
- svgo@3.3.2
- actionlint@1.7.0
- flake8@7.0.0
- hadolint@2.12.0
- shfmt@3.6.0
- shellcheck@0.9.0
- black@24.2.0
- shellcheck@0.10.0
- black@24.4.2
- git-diff-check
- gitleaks@8.18.2
- clang-format@16.0.3

View File

@ -3,5 +3,6 @@
"editor.defaultFormatter": "trunk.io",
"trunk.enableWindows": true,
"files.insertFinalNewline": false,
"files.trimFinalNewlines": false
"files.trimFinalNewlines": false,
"cmake.configureOnOpen": false
}

View File

@ -48,6 +48,7 @@ USER mesh
WORKDIR /home/mesh
COPY --from=builder /tmp/firmware/release/meshtasticd /home/mesh/
RUN mkdir data
VOLUME /home/mesh/data
CMD [ "sh", "-cx", "./meshtasticd -d /home/mesh/data --hwid=${HWID:-$RANDOM}" ]

View File

@ -1,7 +1,8 @@
; Common settings for ESP targes, mixin with extends = esp32_base
[esp32_base]
extends = arduino_base
platform = platformio/espressif32@6.3.2 # This is a temporary fix to the S3-based devices bluetooth issues until we can determine what within ESP-IDF changed and can develop a suitable patch.
custom_esp32_kind = esp32
platform = platformio/espressif32@6.7.0
build_src_filter =
${arduino_base.build_src_filter} -<platform/nrf52/> -<platform/stm32wl> -<platform/rp2040> -<mesh/eth/> -<mesh/raspihttp>
@ -15,8 +16,10 @@ board_build.filesystem = littlefs
# Remove -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL for low level BLE logging.
# See library directory for BLE logging possible values: .pio/libdeps/tbeam/NimBLE-Arduino/src/log_common/log_common.h
# This overrides the BLE logging default of LOG_LEVEL_INFO (1) from: .pio/libdeps/tbeam/NimBLE-Arduino/src/esp_nimble_cfg.h
build_unflags = -fno-lto
build_flags =
${arduino_base.build_flags}
-flto
-Wall
-Wextra
-Isrc/platform/esp32

View File

@ -1,5 +1,6 @@
[esp32c3_base]
extends = esp32_base
custom_esp32_kind = esp32c3
monitor_speed = 115200
monitor_filters = esp32_c3_exception_decoder

View File

@ -1,5 +1,6 @@
[esp32s2_base]
extends = esp32_base
custom_esp32_kind = esp32s2
build_src_filter =
${esp32_base.build_src_filter} - <libpax/> -<nimble/> -<mesh/raspihttp>

View File

@ -1,5 +1,6 @@
[esp32s3_base]
extends = esp32_base
custom_esp32_kind = esp32s3
monitor_speed = 115200

View File

@ -1,14 +1,15 @@
[nrf52_base]
; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files
platform = platformio/nordicnrf52@^10.4.0
platform = platformio/nordicnrf52@^10.5.0
extends = arduino_base
build_type = debug ; I'm debugging with ICE a lot now
build_type = debug
build_flags =
${arduino_base.build_flags}
-DSERIAL_BUFFER_SIZE=1024
-Wno-unused-variable
-Isrc/platform/nrf52
-DLFS_NO_ASSERT ; Disable LFS assertions , see https://github.com/meshtastic/firmware/pull/3818
build_src_filter =
${arduino_base.build_src_filter} -<platform/esp32/> -<platform/stm32wl> -<nimble/> -<mesh/wifi/> -<mesh/api/> -<mesh/http/> -<modules/esp32> -<platform/rp2040> -<mesh/eth/> -<mesh/raspihttp>

View File

@ -1,6 +1,6 @@
; The Portduino based sim environment on top of any host OS, all hardware will be simulated
[portduino_base]
platform = https://github.com/meshtastic/platform-native.git#6fb39b6f94ece9c042141edb4afb91aca94dcaab
platform = https://github.com/meshtastic/platform-native.git#ad8112adf82ce1f5b917092cf32be07a077801a0
framework = arduino
build_src_filter =
@ -24,7 +24,7 @@ lib_deps =
${env.lib_deps}
${networking_base.lib_deps}
rweather/Crypto@^0.4.0
lovyan03/LovyanGFX@^1.1.12
https://github.com/lovyan03/LovyanGFX.git#5a39989aa2c9492572255b22f033843ec8900233
build_flags =
${arduino_base.build_flags}

View File

@ -2,6 +2,17 @@
set -e
platformioFailed() {
[[ $VIRTUAL_ENV != "" ]] && exit 1 # don't hint at virtualenv if it's already in use
echo -e "\nThere were issues running platformio and you are not using a virtual environment." \
"\nYou may try setting up virtualenv and downloading the latest platformio from pip:" \
"\n\tvirtualenv venv" \
"\n\tsource venv/bin/activate" \
"\n\tpip install platformio" \
"\n\t./bin/build-native.sh # retry building"
exit 1
}
VERSION=$(bin/buildinfo.py long)
SHORT_VERSION=$(bin/buildinfo.py short)
@ -13,8 +24,8 @@ mkdir -p $OUTDIR/
rm -r $OUTDIR/* || true
# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale
platformio pkg update --environment native
pio run --environment native
platformio pkg update --environment native || platformioFailed
pio run --environment native || platformioFailed
cp .pio/build/native/program "$OUTDIR/meshtasticd_linux_$(uname -m)"
cp bin/device-install.* $OUTDIR
cp bin/device-update.* $OUTDIR

View File

@ -1,9 +1,8 @@
#!/usr/bin/env python3
import configparser
import sys
from readprops import readProps
verObj = readProps('version.properties')
verObj = readProps("version.properties")
propName = sys.argv[1]
print(f"{verObj[propName]}")

View File

@ -38,11 +38,22 @@ Lora:
# Busy: 20
# Reset: 18
# Module: sx1268 # SX1268-based modules, tested with Ebyte E22 400M33S
# CS: 21
# IRQ: 16
# Busy: 20
# Reset: 18
# TXen: 6
# RXen: 12
# DIO3_TCXO_VOLTAGE: true
# DIO3_TCXO_VOLTAGE: true # the Waveshare Core1262 and others are known to need this setting
# TXen: x # TX and RX enable pins
# RXen: x
# ch341_quirk: true # Uncomment this to use the chunked SPI transfer that seems to fix the ch341
### Set gpio chip to use in /dev/. Defaults to 0.
### Notably the Raspberry Pi 5 puts the GPIO header on gpiochip4
# gpiochip: 4
@ -96,17 +107,21 @@ Display:
# Panel: ILI9341
# CS: 8
# DC: 25
# Backlight: 2
# Width: 320
# Height: 240
# Width: 240
# Height: 320
# Rotate: true
Touchscreen:
### Note, at least for now, the touchscreen must have a CS pin defined, even if you let Linux manage the CS switching.
# Module: STMPE610
# Module: STMPE610 # Option 1 for Adafruit PiTFT 2.8
# CS: 7
# IRQ: 24
# Module: FT5x06 # Option 2 for Adafruit PiTFT 2.8
# IRQ: 24
# I2CAddr: 0x38
# Module: XPT2046 # Waveshare 2.8inch
# CS: 7
# IRQ: 17

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python
"""Generate the CI matrix"""
"""Generate the CI matrix."""
import configparser
import json
@ -34,5 +34,10 @@ for subdir, dirs, files in os.walk(rootdir):
outlist.append(section)
else:
outlist.append(section)
if "board_check" in config[config[c].name]:
if (config[config[c].name]["board_check"] == "true") & (
"check" in options
):
outlist.append(section)
print(json.dumps(outlist))

View File

@ -1,13 +1,14 @@
import subprocess
import configparser
import traceback
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
import sys
from os.path import join
from readprops import readProps
Import("env")
platform = env.PioPlatform()
def esp32_create_combined_bin(source, target, env):
# this sub is borrowed from ESPEasy build toolchain. It's licensed under GPL V3
# https://github.com/letscontrolit/ESPEasy/blob/mega/tools/pio/post_esp32.py
@ -20,8 +21,8 @@ def esp32_create_combined_bin(source, target, env):
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
chip = env.get("BOARD_MCU")
flash_size = env.BoardConfig().get("upload.flash_size")
flash_freq = env.BoardConfig().get("build.f_flash", '40m')
flash_freq = flash_freq.replace('000000L', 'm')
flash_freq = env.BoardConfig().get("build.f_flash", "40m")
flash_freq = flash_freq.replace("000000L", "m")
flash_mode = env.BoardConfig().get("build.flash_mode", "dio")
memory_type = env.BoardConfig().get("build.arduino.memory_type", "qio_qspi")
if flash_mode == "qio" or flash_mode == "qout":
@ -51,23 +52,42 @@ def esp32_create_combined_bin(source, target, env):
print(f" - {hex(app_offset)} | {firmware_name}")
cmd += [hex(app_offset), firmware_name]
print('Using esptool.py arguments: %s' % ' '.join(cmd))
print("Using esptool.py arguments: %s" % " ".join(cmd))
esptool.main(cmd)
if (platform.name == "espressif32"):
if platform.name == "espressif32":
sys.path.append(join(platform.get_package_dir("tool-esptoolpy")))
import esptool
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)
esp32_kind = env.GetProjectOption("custom_esp32_kind")
if esp32_kind == "esp32":
# Free up some IRAM by removing auxiliary SPI flash chip drivers.
# Wrapped stub symbols are defined in src/platform/esp32/iram-quirk.c.
env.Append(
LINKFLAGS=[
"-Wl,--wrap=esp_flash_chip_gd",
"-Wl,--wrap=esp_flash_chip_issi",
"-Wl,--wrap=esp_flash_chip_winbond",
]
)
else:
# For newer ESP32 targets, using newlib nano works better.
env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"])
Import("projenv")
prefsLoc = projenv["PROJECT_DIR"] + "/version.properties"
verObj = readProps(prefsLoc)
print("Using meshtastic platformio-custom.py, firmware version " + verObj['long'])
print("Using meshtastic platformio-custom.py, firmware version " + verObj["long"])
# General options that are passed to the C and C++ compilers
projenv.Append(CCFLAGS=[
"-DAPP_VERSION=" + verObj['long'],
"-DAPP_VERSION_SHORT=" + verObj['short']
])
projenv.Append(
CCFLAGS=[
"-DAPP_VERSION=" + verObj["long"],
"-DAPP_VERSION_SHORT=" + verObj["short"],
]
)

View File

@ -0,0 +1,52 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x00B3"],
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"]
],
"usb_product": "ProMicro compatible nRF52840",
"mcu": "nrf52840",
"variant": "promicro_diy",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "ProMicro compatible nRF52840",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["nrfutil", "jlink", "nrfjprog", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://www.nologo.tech/product/otherboard/NRF52840.html",
"vendor": "Nologo"
}

View File

@ -7,7 +7,8 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DLILYGO_TBEAM_S3_CORE",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],

View File

@ -0,0 +1,58 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"]
],
"usb_product": "WIO-BOOT",
"mcu": "nrf52840",
"variant": "Seeed_WIO_WM1110",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "Seeed WIO WM1110",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"stlink",
"cmsis-dap",
"blackmagic"
],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://www.seeedstudio.com/Wio-WM1110-Dev-Kit-p-5677.html",
"vendor": "Seeed Studio"
}

View File

@ -0,0 +1,58 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_WIO_WM1110 -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"]
],
"usb_product": "WIO-BOOT",
"mcu": "nrf52840",
"variant": "Seeed_WIO_WM1110",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd"
},
"frameworks": ["arduino"],
"name": "Seeed WIO WM1110",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"stlink",
"cmsis-dap",
"blackmagic"
],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://www.seeedstudio.com/Wio-WM1110-Dev-Kit-p-5677.html",
"vendor": "Seeed Studio"
}

34
boards/wiphone.json Normal file
View File

@ -0,0 +1,34 @@
{
"build": {
"arduino": {
"ldscript": "esp32_out.ld",
"partitions": "default_16MB.csv"
},
"core": "esp32",
"extra_flags": [
"-DARDUINO_WIPHONE14",
"-DBOARD_HAS_PSRAM",
"-mfix-esp32-psram-cache-issue",
"-mfix-esp32-psram-cache-strategy=memw"
],
"f_cpu": "240000000L",
"f_flash": "40000000L",
"flash_mode": "dio",
"mcu": "esp32",
"variant": "wiphone",
"board": "WiPhone"
},
"connectivity": ["wifi", "bluetooth"],
"frameworks": ["arduino", "espidf"],
"name": "WIPhone Integrated 1.4",
"upload": {
"flash_size": "16MB",
"maximum_ram_size": 532480,
"maximum_size": 6553600,
"maximum_data_size": 4521984,
"require_upload_port": true,
"speed": 921600
},
"url": "https://www.wiphone.io/",
"vendor": "HackEDA"
}

View File

@ -4,7 +4,7 @@
"ldscript": "esp32_out.ld"
},
"core": "esp32",
"extra_flags": "-DARDUINO_ESP32_DEV",
"extra_flags": ["-DBOARD_HAS_PSRAM", "-DARDUINO_ESP32_DEV"],
"f_cpu": "240000000L",
"f_flash": "40000000L",
"flash_mode": "dio",

View File

@ -29,8 +29,12 @@ default_envs = tbeam
;default_envs = meshtastic-dr-dev
;default_envs = m5stack-coreink
;default_envs = rak4631
;default_envs = rak2560
;default_envs = rak10701
;default_envs = wio-e5
;default_envs = radiomaster_900_bandit_nano
;default_envs = radiomaster_900_bandit_micro
;default_envs = heltec_capsule_sensor_v3
extra_configs =
arch/*/*.ini
@ -70,18 +74,19 @@ build_flags = -Wno-missing-field-initializers
-DRADIOLIB_EXCLUDE_FSK4
-DRADIOLIB_EXCLUDE_APRS
-DRADIOLIB_EXCLUDE_LORAWAN
-DMESHTASTIC_EXCLUDE_DROPZONE=1
monitor_speed = 115200
lib_deps =
jgromes/RadioLib@~6.5.0
https://github.com/meshtastic/esp8266-oled-ssd1306.git#ee628ee6c9588d4c56c9e3da35f0fc9448ad54a8 ; ESP8266_SSD1306
jgromes/RadioLib@~6.6.0
https://github.com/meshtastic/esp8266-oled-ssd1306.git#2b40affbe7f7dc63b6c00fa88e7e12ed1f8e1719 ; ESP8266_SSD1306
mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce
robtillaart/I2CKeyPad@^0.4.0 ; port extender with keymatrix
https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159
https://github.com/meshtastic/TinyGPSPlus.git#964f75a72cccd6b53cd74e4add1f7a42c6f7344d
https://github.com/meshtastic/TinyGPSPlus.git#71a82db35f3b973440044c476d4bcdc673b104f4
https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0
nanopb/Nanopb@^0.4.7
nanopb/Nanopb@^0.4.8
erriez/ErriezCRC32@^1.0.1
; Used for the code analysis in PIO Home / Inspect
@ -97,7 +102,6 @@ check_flags =
framework = arduino
lib_deps =
${env.lib_deps}
mprograms/QMC5883LCompass@^1.2.0
end2endzone/NonBlockingRTTTL@^1.3.0
https://github.com/meshtastic/SparkFun_ATECCX08a_Arduino_Library.git#5cf62b36c6f30bc72a07bdb2c11fc9a22d1e31da
@ -120,10 +124,7 @@ lib_deps =
adafruit/Adafruit BMP280 Library@^2.6.8
adafruit/Adafruit BMP085 Library@^1.2.4
adafruit/Adafruit BME280 Library@^2.2.2
https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.5.2400
boschsensortec/BME68x Sensor Library@^1.1.40407
adafruit/Adafruit MCP9808 Library@^2.0.0
https://github.com/KodinLanewave/INA3221@^1.0.0
adafruit/Adafruit INA260 Library@^1.5.0
adafruit/Adafruit INA219@^1.2.0
adafruit/Adafruit SHTC3 Library@^1.0.0
@ -132,5 +133,23 @@ lib_deps =
adafruit/Adafruit PM25 AQI Sensor@^1.0.6
adafruit/Adafruit MPU6050@^2.2.4
adafruit/Adafruit LIS3DH@^1.2.4
https://github.com/lewisxhe/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17
adafruit/Adafruit LSM6DS@^4.7.2
adafruit/Adafruit AHTX0@^2.0.5
adafruit/Adafruit LSM6DS@^4.7.2
adafruit/Adafruit VEML7700 Library@^2.1.6
adafruit/Adafruit SHT4x Library@^1.0.4
adafruit/Adafruit TSL2591 Library@^1.4.5
sparkfun/SparkFun Qwiic Scale NAU7802 Arduino Library@^1.0.5
ClosedCube OPT3001@^1.1.2
emotibit/EmotiBit MLX90632@^1.0.8
dfrobot/DFRobot_RTU@^1.0.3
https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.7.2502
boschsensortec/BME68x Sensor Library@^1.1.40407
https://github.com/KodinLanewave/INA3221@^1.0.0
lewisxhe/SensorLib@^0.2.0
mprograms/QMC5883LCompass@^1.2.0
https://github.com/meshtastic/DFRobot_LarkWeatherStation#dee914270dc7cb3e43fbf034edd85a63a16a12ee

@ -1 +1 @@
Subproject commit eade2c6befb65a9c46c5d28ae1e8e24c37a1a3d0
Subproject commit 1c3029f2868e5fc49809fd378f6c0c66aee0eaf4

View File

@ -1,6 +1,10 @@
#pragma once
#include "configuration.h"
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "PowerFSM.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
#include "main.h"
#include "power.h"
@ -10,14 +14,15 @@
#include <Arduino.h>
#include <SensorBMA423.hpp>
#include <Wire.h>
SensorBMA423 bmaSensor;
bool BMA_IRQ = false;
#ifdef RAK_4631
#include "Fusion/Fusion.h"
#include <Rak_BMX160.h>
#endif
#define ACCELEROMETER_CHECK_INTERVAL_MS 100
#define ACCELEROMETER_CLICK_THRESHOLD 40
int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
static inline int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
{
Wire.beginTransmission(address);
Wire.write(reg);
@ -30,7 +35,7 @@ int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
return 0; // Pass
}
int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
static inline int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
{
Wire.beginTransmission(address);
Wire.write(reg);
@ -38,8 +43,6 @@ int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
return (0 != Wire.endTransmission());
}
namespace concurrency
{
class AccelerometerThread : public concurrency::OSThread
{
public:
@ -50,14 +53,122 @@ class AccelerometerThread : public concurrency::OSThread
disable();
return;
}
acceleremoter_type = type;
#ifndef RAK_4631
if (!config.display.wake_on_tap_or_motion && !config.device.double_tap_as_button_press) {
LOG_DEBUG("AccelerometerThread disabling due to no interested configurations\n");
disable();
return;
}
#endif
init();
}
acceleremoter_type = type;
void start()
{
init();
setIntervalFromNow(0);
};
protected:
int32_t runOnce() override
{
canSleep = true; // Assume we should not keep the board awake
if (acceleremoter_type == ScanI2C::DeviceType::MPU6050 && mpu.getMotionInterruptStatus()) {
wakeScreen();
} else if (acceleremoter_type == ScanI2C::DeviceType::LIS3DH && lis.getClick() > 0) {
uint8_t click = lis.getClick();
if (!config.device.double_tap_as_button_press) {
wakeScreen();
}
if (config.device.double_tap_as_button_press && (click & 0x20)) {
buttonPress();
return 500;
}
} else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.readIrqStatus() != DEV_WIRE_NONE) {
if (bmaSensor.isTilt() || bmaSensor.isDoubleTap()) {
wakeScreen();
return 500;
}
#ifdef RAK_4631
} else if (acceleremoter_type == ScanI2C::DeviceType::BMX160) {
sBmx160SensorData_t magAccel;
sBmx160SensorData_t gAccel;
/* Get a new sensor event */
bmx160.getAllData(&magAccel, NULL, &gAccel);
// expirimental calibrate routine. Limited to between 10 and 30 seconds after boot
if (millis() > 10 * 1000 && millis() < 30 * 1000) {
if (magAccel.x > highestX)
highestX = magAccel.x;
if (magAccel.x < lowestX)
lowestX = magAccel.x;
if (magAccel.y > highestY)
highestY = magAccel.y;
if (magAccel.y < lowestY)
lowestY = magAccel.y;
if (magAccel.z > highestZ)
highestZ = magAccel.z;
if (magAccel.z < lowestZ)
lowestZ = magAccel.z;
}
int highestRealX = highestX - (highestX + lowestX) / 2;
magAccel.x -= (highestX + lowestX) / 2;
magAccel.y -= (highestY + lowestY) / 2;
magAccel.z -= (highestZ + lowestZ) / 2;
FusionVector ga, ma;
ga.axis.x = -gAccel.x; // default location for the BMX160 is on the rear of the board
ga.axis.y = -gAccel.y;
ga.axis.z = gAccel.z;
ma.axis.x = -magAccel.x;
ma.axis.y = -magAccel.y;
ma.axis.z = magAccel.z * 3;
// If we're set to one of the inverted positions
if (config.display.compass_orientation > meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270) {
ma = FusionAxesSwap(ma, FusionAxesAlignmentNXNYPZ);
ga = FusionAxesSwap(ga, FusionAxesAlignmentNXNYPZ);
}
float heading = FusionCompassCalculateHeading(FusionConventionNed, ga, ma);
switch (config.display.compass_orientation) {
case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0_INVERTED:
case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_0:
break;
case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90:
case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_90_INVERTED:
heading += 90;
break;
case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180:
case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_180_INVERTED:
heading += 180;
break;
case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270:
case meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED:
heading += 270;
break;
}
screen->setHeading(heading);
#endif
} else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.shake()) {
wakeScreen();
return 500;
}
return ACCELEROMETER_CHECK_INTERVAL_MS;
}
private:
void init()
{
LOG_DEBUG("AccelerometerThread initializing\n");
if (acceleremoter_type == ScanI2C::DeviceType::MPU6050 && mpu.begin(accelerometer_found.address)) {
@ -109,6 +220,11 @@ class AccelerometerThread : public concurrency::OSThread
bmaSensor.enableTiltIRQ();
// It corresponds to isDoubleClick interrupt
bmaSensor.enableWakeupIRQ();
#ifdef RAK_4631
} else if (acceleremoter_type == ScanI2C::DeviceType::BMX160 && bmx160.begin()) {
bmx160.ODR_Config(BMX160_ACCEL_ODR_100HZ, BMX160_GYRO_ODR_100HZ); // set output data rate
#endif
} else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.begin_I2C(accelerometer_found.address)) {
LOG_DEBUG("LSM6DS3 initializing\n");
// Default threshold of 2G, less sensitive options are 4, 8 or 16G
@ -120,38 +236,6 @@ class AccelerometerThread : public concurrency::OSThread
// Duration is number of occurances needed to trigger, higher threshold is less sensitive
}
}
protected:
int32_t runOnce() override
{
canSleep = true; // Assume we should not keep the board awake
if (acceleremoter_type == ScanI2C::DeviceType::MPU6050 && mpu.getMotionInterruptStatus()) {
wakeScreen();
} else if (acceleremoter_type == ScanI2C::DeviceType::LIS3DH && lis.getClick() > 0) {
uint8_t click = lis.getClick();
if (!config.device.double_tap_as_button_press) {
wakeScreen();
}
if (config.device.double_tap_as_button_press && (click & 0x20)) {
buttonPress();
return 500;
}
} else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.readIrqStatus() != DEV_WIRE_NONE) {
if (bmaSensor.isTilt() || bmaSensor.isDoubleTap()) {
wakeScreen();
return 500;
}
} else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.shake()) {
wakeScreen();
return 500;
}
return ACCELEROMETER_CHECK_INTERVAL_MS;
}
private:
void wakeScreen()
{
if (powerFSM.getState() == &stateDARK) {
@ -170,6 +254,12 @@ class AccelerometerThread : public concurrency::OSThread
Adafruit_MPU6050 mpu;
Adafruit_LIS3DH lis;
Adafruit_LSM6DS3TRC lsm;
SensorBMA423 bmaSensor;
#ifdef RAK_4631
RAK_BMX160 bmx160;
float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0;
#endif
bool BMA_IRQ = false;
};
} // namespace concurrency
#endif

View File

@ -5,6 +5,16 @@
NCP5623 rgb;
#endif
#ifdef HAS_NEOPIXEL
#include <graphics/NeoPixel.h>
Adafruit_NeoPixel pixels(NEOPIXEL_COUNT, NEOPIXEL_DATA, NEOPIXEL_TYPE);
#endif
#ifdef UNPHONE
#include "unPhone.h"
extern unPhone unphone;
#endif
namespace concurrency
{
class AmbientLightingThread : public concurrency::OSThread
@ -27,15 +37,31 @@ class AmbientLightingThread : public concurrency::OSThread
disable();
return;
}
#endif
#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE)
if (!moduleConfig.ambient_lighting.led_state) {
LOG_DEBUG("AmbientLightingThread disabling due to moduleConfig.ambient_lighting.led_state OFF\n");
disable();
return;
}
LOG_DEBUG("AmbientLightingThread initializing\n");
#ifdef HAS_NCP5623
if (_type == ScanI2C::NCP5623) {
rgb.begin();
#endif
#ifdef RGBLED_RED
pinMode(RGBLED_RED, OUTPUT);
pinMode(RGBLED_GREEN, OUTPUT);
pinMode(RGBLED_BLUE, OUTPUT);
#endif
#ifdef HAS_NEOPIXEL
pixels.begin(); // Initialise the pixel(s)
pixels.clear(); // Set all pixel colors to 'off'
pixels.setBrightness(moduleConfig.ambient_lighting.current);
#endif
setLighting();
#endif
#ifdef HAS_NCP5623
}
#endif
}
@ -43,16 +69,17 @@ class AmbientLightingThread : public concurrency::OSThread
protected:
int32_t runOnce() override
{
#if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE)
#ifdef HAS_NCP5623
if (_type == ScanI2C::NCP5623 && moduleConfig.ambient_lighting.led_state) {
#endif
setLighting();
return 30000; // 30 seconds to reset from any animations that may have been running from Ext. Notification
} else {
return disable();
#ifdef HAS_NCP5623
}
#else
return disable();
#endif
#endif
return disable();
}
private:
@ -65,9 +92,36 @@ class AmbientLightingThread : public concurrency::OSThread
rgb.setRed(moduleConfig.ambient_lighting.red);
rgb.setGreen(moduleConfig.ambient_lighting.green);
rgb.setBlue(moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Initializing Ambient lighting w/ current=%d, red=%d, green=%d, blue=%d\n",
LOG_DEBUG("Initializing NCP5623 Ambient lighting w/ current=%d, red=%d, green=%d, blue=%d\n",
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
moduleConfig.ambient_lighting.blue);
#endif
#ifdef HAS_NEOPIXEL
pixels.fill(pixels.Color(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
moduleConfig.ambient_lighting.blue),
0, NEOPIXEL_COUNT);
pixels.show();
LOG_DEBUG("Initializing NeoPixel Ambient lighting w/ brightness(current)=%d, red=%d, green=%d, blue=%d\n",
moduleConfig.ambient_lighting.current, moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green,
moduleConfig.ambient_lighting.blue);
#endif
#ifdef RGBLED_CA
analogWrite(RGBLED_RED, 255 - moduleConfig.ambient_lighting.red);
analogWrite(RGBLED_GREEN, 255 - moduleConfig.ambient_lighting.green);
analogWrite(RGBLED_BLUE, 255 - moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Initializing Ambient lighting RGB Common Anode w/ red=%d, green=%d, blue=%d\n",
moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#elif defined(RGBLED_RED)
analogWrite(RGBLED_RED, moduleConfig.ambient_lighting.red);
analogWrite(RGBLED_GREEN, moduleConfig.ambient_lighting.green);
analogWrite(RGBLED_BLUE, moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Initializing Ambient lighting RGB Common Cathode w/ red=%d, green=%d, blue=%d\n",
moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif
#ifdef UNPHONE
unphone.rgb(moduleConfig.ambient_lighting.red, moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
LOG_DEBUG("Initializing unPhone Ambient lighting w/ red=%d, green=%d, blue=%d\n", moduleConfig.ambient_lighting.red,
moduleConfig.ambient_lighting.green, moduleConfig.ambient_lighting.blue);
#endif
}
};

View File

@ -41,7 +41,11 @@ ButtonThread::ButtonThread() : OSThread("Button")
}
#elif defined(BUTTON_PIN)
int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; // Resolved button pin
#if defined(HELTEC_CAPSULE_SENSOR_V3)
this->userButton = OneButton(pin, false, false);
#else
this->userButton = OneButton(pin, true, true);
#endif
LOG_DEBUG("Using GPIO%02d for button\n", pin);
#endif
@ -52,8 +56,8 @@ ButtonThread::ButtonThread() : OSThread("Button")
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
userButton.attachClick(userButtonPressed);
userButton.setClickMs(250);
userButton.setPressMs(c_longPressTime);
userButton.setClickMs(BUTTON_CLICK_MS);
userButton.setPressMs(BUTTON_LONGPRESS_MS);
userButton.setDebounceMs(1);
userButton.attachDoubleClick(userButtonDoublePressed);
userButton.attachMultiClick(userButtonMultiPressed, this); // Reference to instance: get click count from non-static OneButton
@ -70,8 +74,8 @@ ButtonThread::ButtonThread() : OSThread("Button")
pinMode(BUTTON_PIN_ALT, INPUT_PULLUP_SENSE);
#endif
userButtonAlt.attachClick(userButtonPressed);
userButtonAlt.setClickMs(250);
userButtonAlt.setPressMs(c_longPressTime);
userButtonAlt.setClickMs(BUTTON_CLICK_MS);
userButtonAlt.setPressMs(BUTTON_LONGPRESS_MS);
userButtonAlt.setDebounceMs(1);
userButtonAlt.attachDoubleClick(userButtonDoublePressed);
userButtonAlt.attachLongPressStart(userButtonPressedLongStart);
@ -80,7 +84,7 @@ ButtonThread::ButtonThread() : OSThread("Button")
#ifdef BUTTON_PIN_TOUCH
userButtonTouch = OneButton(BUTTON_PIN_TOUCH, true, true);
userButtonTouch.setPressMs(400);
userButtonTouch.setPressMs(BUTTON_TOUCH_MS);
userButtonTouch.attachLongPressStart(touchPressedLongStart); // Better handling with longpress than click?
#endif
@ -136,9 +140,12 @@ int32_t ButtonThread::runOnce()
case BUTTON_EVENT_DOUBLE_PRESSED: {
LOG_BUTTON("Double press!\n");
service.refreshLocalMeshNode();
service.sendNetworkPing(NODENUM_BROADCAST, true);
auto sentPosition = service.trySendPosition(NODENUM_BROADCAST, true);
if (screen) {
screen->print("Sent ad-hoc ping\n");
if (sentPosition)
screen->print("Sent ad-hoc position\n");
else
screen->print("Sent ad-hoc nodeinfo\n");
screen->forceDisplay(true); // Force a new UI frame, then force an EInk update
}
break;
@ -193,15 +200,13 @@ int32_t ButtonThread::runOnce()
#ifdef BUTTON_PIN_TOUCH
case BUTTON_EVENT_TOUCH_LONG_PRESSED: {
LOG_BUTTON("Touch press!\n");
if (config.display.wake_on_tap_or_motion) {
if (screen) {
// Wake if asleep
if (powerFSM.getState() == &stateDARK)
powerFSM.trigger(EVENT_PRESS);
if (screen) {
// Wake if asleep
if (powerFSM.getState() == &stateDARK)
powerFSM.trigger(EVENT_PRESS);
// Update display (legacy behaviour)
screen->forceDisplay();
}
// Update display (legacy behaviour)
screen->forceDisplay();
}
break;
}
@ -213,6 +218,7 @@ int32_t ButtonThread::runOnce()
btnEvent = BUTTON_EVENT_NONE;
}
runASAP = false;
return 50;
}
@ -230,9 +236,10 @@ void ButtonThread::attachButtonInterrupts()
attachInterrupt(
config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN,
[]() {
ButtonThread::userButton.tick();
runASAP = true;
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
ButtonThread::userButton.tick();
},
CHANGE);
#endif
@ -279,6 +286,7 @@ void ButtonThread::wakeOnIrq(int irq, int mode)
[] {
BaseType_t higherWake = 0;
mainDelay.interruptFromISR(&higherWake);
runASAP = true;
},
FALLING);
}

View File

@ -4,11 +4,22 @@
#include "concurrency/OSThread.h"
#include "configuration.h"
#ifndef BUTTON_CLICK_MS
#define BUTTON_CLICK_MS 250
#endif
#ifndef BUTTON_LONGPRESS_MS
#define BUTTON_LONGPRESS_MS 5000
#endif
#ifndef BUTTON_TOUCH_MS
#define BUTTON_TOCH_MS 400
#endif
class ButtonThread : public concurrency::OSThread
{
public:
static const uint32_t c_longPressTime = 5000; // shutdown after 5s
static const uint32_t c_holdOffTime = 30000; // hold off 30s after boot
static const uint32_t c_holdOffTime = 30000; // hold off 30s after boot
enum ButtonEventType {
BUTTON_EVENT_NONE,

View File

@ -36,7 +36,7 @@
#define LOG_CRIT(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#define LOG_TRACE(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#else
#ifdef DEBUG_PORT
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
#define LOG_DEBUG(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_DEBUG, __VA_ARGS__)
#define LOG_INFO(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_INFO, __VA_ARGS__)
#define LOG_WARN(...) DEBUG_PORT.log(MESHTASTIC_LOG_LEVEL_WARN, __VA_ARGS__)

View File

@ -205,6 +205,62 @@ void rmDir(const char *dirname)
#endif
}
bool fsCheck()
{
#if defined(ARCH_NRF52)
size_t write_size = 0;
size_t read_size = 0;
char buf[32] = {0};
Adafruit_LittleFS_Namespace::File file(FSCom);
const char *text = "meshtastic fs test";
size_t text_length = strlen(text);
const char *filename = "/meshtastic.txt";
LOG_DEBUG("Try create file .\n");
if (file.open(filename, FILE_O_WRITE)) {
write_size = file.write(text);
} else {
LOG_DEBUG("Open file failed .\n");
goto FORMAT_FS;
}
if (write_size != text_length) {
LOG_DEBUG("Text bytes do not match .\n");
file.close();
goto FORMAT_FS;
}
file.close();
if (!file.open(filename, FILE_O_READ)) {
LOG_DEBUG("Open file failed .\n");
goto FORMAT_FS;
}
read_size = file.readBytes(buf, text_length);
if (read_size != text_length) {
LOG_DEBUG("Text bytes do not match .\n");
file.close();
goto FORMAT_FS;
}
if (memcmp(buf, text, text_length) != 0) {
LOG_DEBUG("The written bytes do not match the read bytes .\n");
file.close();
goto FORMAT_FS;
}
return true;
FORMAT_FS:
LOG_DEBUG("Format FS ....\n");
FSCom.format();
FSCom.begin();
return false;
#else
return true;
#endif
}
void fsInit()
{
#ifdef FSCom
@ -212,8 +268,37 @@ void fsInit()
LOG_ERROR("Filesystem mount Failed.\n");
// assert(0); This auto-formats the partition, so no need to fail here.
}
#ifdef ARCH_ESP32
#if defined(ARCH_ESP32)
LOG_DEBUG("Filesystem files (%d/%d Bytes):\n", FSCom.usedBytes(), FSCom.totalBytes());
#elif defined(ARCH_NRF52)
/*
* nRF52840 has a certain chance of automatic formatting failure.
* Try to create a file after initializing the file system. If the creation fails,
* it means that the file system is not working properly. Please format it manually again.
* To check the normality of the file system, you need to disable the LFS_NO_ASSERT assertion.
* Otherwise, the assertion will be entered at the moment of reading or opening, and the FS will not be formatted.
* */
bool ret = false;
uint8_t retry = 3;
while (retry--) {
ret = fsCheck();
if (ret) {
LOG_DEBUG("File system check is OK.\n");
break;
}
delay(10);
}
// It may not be possible to reach this step.
// Add a loop here to prevent unpredictable situations from happening.
// Can add a screen to display error status later.
if (!ret) {
while (1) {
LOG_ERROR("The file system is damaged and cannot proceed to the next step.\n");
delay(1000);
}
}
#else
LOG_DEBUG("Filesystem files:\n");
#endif

32
src/Fusion/Fusion.h Normal file
View File

@ -0,0 +1,32 @@
/**
* @file Fusion.h
* @author Seb Madgwick
* @brief Main header file for the Fusion library. This is the only file that
* needs to be included when using the library.
*/
#ifndef FUSION_H
#define FUSION_H
//------------------------------------------------------------------------------
// Includes
#ifdef __cplusplus
extern "C" {
#endif
#include "FusionAhrs.h"
#include "FusionAxes.h"
#include "FusionCalibration.h"
#include "FusionCompass.h"
#include "FusionConvention.h"
#include "FusionMath.h"
#include "FusionOffset.h"
#ifdef __cplusplus
}
#endif
#endif
//------------------------------------------------------------------------------
// End of file

542
src/Fusion/FusionAhrs.c Normal file
View File

@ -0,0 +1,542 @@
/**
* @file FusionAhrs.c
* @author Seb Madgwick
* @brief AHRS algorithm to combine gyroscope, accelerometer, and magnetometer
* measurements into a single measurement of orientation relative to the Earth.
*/
//------------------------------------------------------------------------------
// Includes
#include "FusionAhrs.h"
#include <float.h> // FLT_MAX
#include <math.h> // atan2f, cosf, fabsf, powf, sinf
//------------------------------------------------------------------------------
// Definitions
/**
* @brief Initial gain used during the initialisation.
*/
#define INITIAL_GAIN (10.0f)
/**
* @brief Initialisation period in seconds.
*/
#define INITIALISATION_PERIOD (3.0f)
//------------------------------------------------------------------------------
// Function declarations
static inline FusionVector HalfGravity(const FusionAhrs *const ahrs);
static inline FusionVector HalfMagnetic(const FusionAhrs *const ahrs);
static inline FusionVector Feedback(const FusionVector sensor, const FusionVector reference);
static inline int Clamp(const int value, const int min, const int max);
//------------------------------------------------------------------------------
// Functions
/**
* @brief Initialises the AHRS algorithm structure.
* @param ahrs AHRS algorithm structure.
*/
void FusionAhrsInitialise(FusionAhrs *const ahrs)
{
const FusionAhrsSettings settings = {
.convention = FusionConventionNwu,
.gain = 0.5f,
.gyroscopeRange = 0.0f,
.accelerationRejection = 90.0f,
.magneticRejection = 90.0f,
.recoveryTriggerPeriod = 0,
};
FusionAhrsSetSettings(ahrs, &settings);
FusionAhrsReset(ahrs);
}
/**
* @brief Resets the AHRS algorithm. This is equivalent to reinitialising the
* algorithm while maintaining the current settings.
* @param ahrs AHRS algorithm structure.
*/
void FusionAhrsReset(FusionAhrs *const ahrs)
{
ahrs->quaternion = FUSION_IDENTITY_QUATERNION;
ahrs->accelerometer = FUSION_VECTOR_ZERO;
ahrs->initialising = true;
ahrs->rampedGain = INITIAL_GAIN;
ahrs->angularRateRecovery = false;
ahrs->halfAccelerometerFeedback = FUSION_VECTOR_ZERO;
ahrs->halfMagnetometerFeedback = FUSION_VECTOR_ZERO;
ahrs->accelerometerIgnored = false;
ahrs->accelerationRecoveryTrigger = 0;
ahrs->accelerationRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod;
ahrs->magnetometerIgnored = false;
ahrs->magneticRecoveryTrigger = 0;
ahrs->magneticRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod;
}
/**
* @brief Sets the AHRS algorithm settings.
* @param ahrs AHRS algorithm structure.
* @param settings Settings.
*/
void FusionAhrsSetSettings(FusionAhrs *const ahrs, const FusionAhrsSettings *const settings)
{
ahrs->settings.convention = settings->convention;
ahrs->settings.gain = settings->gain;
ahrs->settings.gyroscopeRange = settings->gyroscopeRange == 0.0f ? FLT_MAX : 0.98f * settings->gyroscopeRange;
ahrs->settings.accelerationRejection = settings->accelerationRejection == 0.0f
? FLT_MAX
: powf(0.5f * sinf(FusionDegreesToRadians(settings->accelerationRejection)), 2);
ahrs->settings.magneticRejection =
settings->magneticRejection == 0.0f ? FLT_MAX : powf(0.5f * sinf(FusionDegreesToRadians(settings->magneticRejection)), 2);
ahrs->settings.recoveryTriggerPeriod = settings->recoveryTriggerPeriod;
ahrs->accelerationRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod;
ahrs->magneticRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod;
if ((settings->gain == 0.0f) ||
(settings->recoveryTriggerPeriod == 0)) { // disable acceleration and magnetic rejection features if gain is zero
ahrs->settings.accelerationRejection = FLT_MAX;
ahrs->settings.magneticRejection = FLT_MAX;
}
if (ahrs->initialising == false) {
ahrs->rampedGain = ahrs->settings.gain;
}
ahrs->rampedGainStep = (INITIAL_GAIN - ahrs->settings.gain) / INITIALISATION_PERIOD;
}
/**
* @brief Updates the AHRS algorithm using the gyroscope, accelerometer, and
* magnetometer measurements.
* @param ahrs AHRS algorithm structure.
* @param gyroscope Gyroscope measurement in degrees per second.
* @param accelerometer Accelerometer measurement in g.
* @param magnetometer Magnetometer measurement in arbitrary units.
* @param deltaTime Delta time in seconds.
*/
void FusionAhrsUpdate(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer,
const FusionVector magnetometer, const float deltaTime)
{
#define Q ahrs->quaternion.element
// Store accelerometer
ahrs->accelerometer = accelerometer;
// Reinitialise if gyroscope range exceeded
if ((fabsf(gyroscope.axis.x) > ahrs->settings.gyroscopeRange) || (fabsf(gyroscope.axis.y) > ahrs->settings.gyroscopeRange) ||
(fabsf(gyroscope.axis.z) > ahrs->settings.gyroscopeRange)) {
const FusionQuaternion quaternion = ahrs->quaternion;
FusionAhrsReset(ahrs);
ahrs->quaternion = quaternion;
ahrs->angularRateRecovery = true;
}
// Ramp down gain during initialisation
if (ahrs->initialising) {
ahrs->rampedGain -= ahrs->rampedGainStep * deltaTime;
if ((ahrs->rampedGain < ahrs->settings.gain) || (ahrs->settings.gain == 0.0f)) {
ahrs->rampedGain = ahrs->settings.gain;
ahrs->initialising = false;
ahrs->angularRateRecovery = false;
}
}
// Calculate direction of gravity indicated by algorithm
const FusionVector halfGravity = HalfGravity(ahrs);
// Calculate accelerometer feedback
FusionVector halfAccelerometerFeedback = FUSION_VECTOR_ZERO;
ahrs->accelerometerIgnored = true;
if (FusionVectorIsZero(accelerometer) == false) {
// Calculate accelerometer feedback scaled by 0.5
ahrs->halfAccelerometerFeedback = Feedback(FusionVectorNormalise(accelerometer), halfGravity);
// Don't ignore accelerometer if acceleration error below threshold
if (ahrs->initialising ||
((FusionVectorMagnitudeSquared(ahrs->halfAccelerometerFeedback) <= ahrs->settings.accelerationRejection))) {
ahrs->accelerometerIgnored = false;
ahrs->accelerationRecoveryTrigger -= 9;
} else {
ahrs->accelerationRecoveryTrigger += 1;
}
// Don't ignore accelerometer during acceleration recovery
if (ahrs->accelerationRecoveryTrigger > ahrs->accelerationRecoveryTimeout) {
ahrs->accelerationRecoveryTimeout = 0;
ahrs->accelerometerIgnored = false;
} else {
ahrs->accelerationRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod;
}
ahrs->accelerationRecoveryTrigger = Clamp(ahrs->accelerationRecoveryTrigger, 0, ahrs->settings.recoveryTriggerPeriod);
// Apply accelerometer feedback
if (ahrs->accelerometerIgnored == false) {
halfAccelerometerFeedback = ahrs->halfAccelerometerFeedback;
}
}
// Calculate magnetometer feedback
FusionVector halfMagnetometerFeedback = FUSION_VECTOR_ZERO;
ahrs->magnetometerIgnored = true;
if (FusionVectorIsZero(magnetometer) == false) {
// Calculate direction of magnetic field indicated by algorithm
const FusionVector halfMagnetic = HalfMagnetic(ahrs);
// Calculate magnetometer feedback scaled by 0.5
ahrs->halfMagnetometerFeedback =
Feedback(FusionVectorNormalise(FusionVectorCrossProduct(halfGravity, magnetometer)), halfMagnetic);
// Don't ignore magnetometer if magnetic error below threshold
if (ahrs->initialising ||
((FusionVectorMagnitudeSquared(ahrs->halfMagnetometerFeedback) <= ahrs->settings.magneticRejection))) {
ahrs->magnetometerIgnored = false;
ahrs->magneticRecoveryTrigger -= 9;
} else {
ahrs->magneticRecoveryTrigger += 1;
}
// Don't ignore magnetometer during magnetic recovery
if (ahrs->magneticRecoveryTrigger > ahrs->magneticRecoveryTimeout) {
ahrs->magneticRecoveryTimeout = 0;
ahrs->magnetometerIgnored = false;
} else {
ahrs->magneticRecoveryTimeout = ahrs->settings.recoveryTriggerPeriod;
}
ahrs->magneticRecoveryTrigger = Clamp(ahrs->magneticRecoveryTrigger, 0, ahrs->settings.recoveryTriggerPeriod);
// Apply magnetometer feedback
if (ahrs->magnetometerIgnored == false) {
halfMagnetometerFeedback = ahrs->halfMagnetometerFeedback;
}
}
// Convert gyroscope to radians per second scaled by 0.5
const FusionVector halfGyroscope = FusionVectorMultiplyScalar(gyroscope, FusionDegreesToRadians(0.5f));
// Apply feedback to gyroscope
const FusionVector adjustedHalfGyroscope = FusionVectorAdd(
halfGyroscope,
FusionVectorMultiplyScalar(FusionVectorAdd(halfAccelerometerFeedback, halfMagnetometerFeedback), ahrs->rampedGain));
// Integrate rate of change of quaternion
ahrs->quaternion = FusionQuaternionAdd(
ahrs->quaternion,
FusionQuaternionMultiplyVector(ahrs->quaternion, FusionVectorMultiplyScalar(adjustedHalfGyroscope, deltaTime)));
// Normalise quaternion
ahrs->quaternion = FusionQuaternionNormalise(ahrs->quaternion);
#undef Q
}
/**
* @brief Returns the direction of gravity scaled by 0.5.
* @param ahrs AHRS algorithm structure.
* @return Direction of gravity scaled by 0.5.
*/
static inline FusionVector HalfGravity(const FusionAhrs *const ahrs)
{
#define Q ahrs->quaternion.element
switch (ahrs->settings.convention) {
case FusionConventionNwu:
case FusionConventionEnu: {
const FusionVector halfGravity = {.axis = {
.x = Q.x * Q.z - Q.w * Q.y,
.y = Q.y * Q.z + Q.w * Q.x,
.z = Q.w * Q.w - 0.5f + Q.z * Q.z,
}}; // third column of transposed rotation matrix scaled by 0.5
return halfGravity;
}
case FusionConventionNed: {
const FusionVector halfGravity = {.axis = {
.x = Q.w * Q.y - Q.x * Q.z,
.y = -1.0f * (Q.y * Q.z + Q.w * Q.x),
.z = 0.5f - Q.w * Q.w - Q.z * Q.z,
}}; // third column of transposed rotation matrix scaled by -0.5
return halfGravity;
}
}
return FUSION_VECTOR_ZERO; // avoid compiler warning
#undef Q
}
/**
* @brief Returns the direction of the magnetic field scaled by 0.5.
* @param ahrs AHRS algorithm structure.
* @return Direction of the magnetic field scaled by 0.5.
*/
static inline FusionVector HalfMagnetic(const FusionAhrs *const ahrs)
{
#define Q ahrs->quaternion.element
switch (ahrs->settings.convention) {
case FusionConventionNwu: {
const FusionVector halfMagnetic = {.axis = {
.x = Q.x * Q.y + Q.w * Q.z,
.y = Q.w * Q.w - 0.5f + Q.y * Q.y,
.z = Q.y * Q.z - Q.w * Q.x,
}}; // second column of transposed rotation matrix scaled by 0.5
return halfMagnetic;
}
case FusionConventionEnu: {
const FusionVector halfMagnetic = {.axis = {
.x = 0.5f - Q.w * Q.w - Q.x * Q.x,
.y = Q.w * Q.z - Q.x * Q.y,
.z = -1.0f * (Q.x * Q.z + Q.w * Q.y),
}}; // first column of transposed rotation matrix scaled by -0.5
return halfMagnetic;
}
case FusionConventionNed: {
const FusionVector halfMagnetic = {.axis = {
.x = -1.0f * (Q.x * Q.y + Q.w * Q.z),
.y = 0.5f - Q.w * Q.w - Q.y * Q.y,
.z = Q.w * Q.x - Q.y * Q.z,
}}; // second column of transposed rotation matrix scaled by -0.5
return halfMagnetic;
}
}
return FUSION_VECTOR_ZERO; // avoid compiler warning
#undef Q
}
/**
* @brief Returns the feedback.
* @param sensor Sensor.
* @param reference Reference.
* @return Feedback.
*/
static inline FusionVector Feedback(const FusionVector sensor, const FusionVector reference)
{
if (FusionVectorDotProduct(sensor, reference) < 0.0f) { // if error is >90 degrees
return FusionVectorNormalise(FusionVectorCrossProduct(sensor, reference));
}
return FusionVectorCrossProduct(sensor, reference);
}
/**
* @brief Returns a value limited to maximum and minimum.
* @param value Value.
* @param min Minimum value.
* @param max Maximum value.
* @return Value limited to maximum and minimum.
*/
static inline int Clamp(const int value, const int min, const int max)
{
if (value < min) {
return min;
}
if (value > max) {
return max;
}
return value;
}
/**
* @brief Updates the AHRS algorithm using the gyroscope and accelerometer
* measurements only.
* @param ahrs AHRS algorithm structure.
* @param gyroscope Gyroscope measurement in degrees per second.
* @param accelerometer Accelerometer measurement in g.
* @param deltaTime Delta time in seconds.
*/
void FusionAhrsUpdateNoMagnetometer(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer,
const float deltaTime)
{
// Update AHRS algorithm
FusionAhrsUpdate(ahrs, gyroscope, accelerometer, FUSION_VECTOR_ZERO, deltaTime);
// Zero heading during initialisation
if (ahrs->initialising) {
FusionAhrsSetHeading(ahrs, 0.0f);
}
}
/**
* @brief Updates the AHRS algorithm using the gyroscope, accelerometer, and
* heading measurements.
* @param ahrs AHRS algorithm structure.
* @param gyroscope Gyroscope measurement in degrees per second.
* @param accelerometer Accelerometer measurement in g.
* @param heading Heading measurement in degrees.
* @param deltaTime Delta time in seconds.
*/
void FusionAhrsUpdateExternalHeading(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer,
const float heading, const float deltaTime)
{
#define Q ahrs->quaternion.element
// Calculate roll
const float roll = atan2f(Q.w * Q.x + Q.y * Q.z, 0.5f - Q.y * Q.y - Q.x * Q.x);
// Calculate magnetometer
const float headingRadians = FusionDegreesToRadians(heading);
const float sinHeadingRadians = sinf(headingRadians);
const FusionVector magnetometer = {.axis = {
.x = cosf(headingRadians),
.y = -1.0f * cosf(roll) * sinHeadingRadians,
.z = sinHeadingRadians * sinf(roll),
}};
// Update AHRS algorithm
FusionAhrsUpdate(ahrs, gyroscope, accelerometer, magnetometer, deltaTime);
#undef Q
}
/**
* @brief Returns the quaternion describing the sensor relative to the Earth.
* @param ahrs AHRS algorithm structure.
* @return Quaternion describing the sensor relative to the Earth.
*/
FusionQuaternion FusionAhrsGetQuaternion(const FusionAhrs *const ahrs)
{
return ahrs->quaternion;
}
/**
* @brief Sets the quaternion describing the sensor relative to the Earth.
* @param ahrs AHRS algorithm structure.
* @param quaternion Quaternion describing the sensor relative to the Earth.
*/
void FusionAhrsSetQuaternion(FusionAhrs *const ahrs, const FusionQuaternion quaternion)
{
ahrs->quaternion = quaternion;
}
/**
* @brief Returns the linear acceleration measurement equal to the accelerometer
* measurement with the 1 g of gravity removed.
* @param ahrs AHRS algorithm structure.
* @return Linear acceleration measurement in g.
*/
FusionVector FusionAhrsGetLinearAcceleration(const FusionAhrs *const ahrs)
{
#define Q ahrs->quaternion.element
// Calculate gravity in the sensor coordinate frame
const FusionVector gravity = {.axis = {
.x = 2.0f * (Q.x * Q.z - Q.w * Q.y),
.y = 2.0f * (Q.y * Q.z + Q.w * Q.x),
.z = 2.0f * (Q.w * Q.w - 0.5f + Q.z * Q.z),
}}; // third column of transposed rotation matrix
// Remove gravity from accelerometer measurement
switch (ahrs->settings.convention) {
case FusionConventionNwu:
case FusionConventionEnu: {
return FusionVectorSubtract(ahrs->accelerometer, gravity);
}
case FusionConventionNed: {
return FusionVectorAdd(ahrs->accelerometer, gravity);
}
}
return FUSION_VECTOR_ZERO; // avoid compiler warning
#undef Q
}
/**
* @brief Returns the Earth acceleration measurement equal to accelerometer
* measurement in the Earth coordinate frame with the 1 g of gravity removed.
* @param ahrs AHRS algorithm structure.
* @return Earth acceleration measurement in g.
*/
FusionVector FusionAhrsGetEarthAcceleration(const FusionAhrs *const ahrs)
{
#define Q ahrs->quaternion.element
#define A ahrs->accelerometer.axis
// Calculate accelerometer measurement in the Earth coordinate frame
const float qwqw = Q.w * Q.w; // calculate common terms to avoid repeated operations
const float qwqx = Q.w * Q.x;
const float qwqy = Q.w * Q.y;
const float qwqz = Q.w * Q.z;
const float qxqy = Q.x * Q.y;
const float qxqz = Q.x * Q.z;
const float qyqz = Q.y * Q.z;
FusionVector accelerometer = {.axis = {
.x = 2.0f * ((qwqw - 0.5f + Q.x * Q.x) * A.x + (qxqy - qwqz) * A.y + (qxqz + qwqy) * A.z),
.y = 2.0f * ((qxqy + qwqz) * A.x + (qwqw - 0.5f + Q.y * Q.y) * A.y + (qyqz - qwqx) * A.z),
.z = 2.0f * ((qxqz - qwqy) * A.x + (qyqz + qwqx) * A.y + (qwqw - 0.5f + Q.z * Q.z) * A.z),
}}; // rotation matrix multiplied with the accelerometer
// Remove gravity from accelerometer measurement
switch (ahrs->settings.convention) {
case FusionConventionNwu:
case FusionConventionEnu:
accelerometer.axis.z -= 1.0f;
break;
case FusionConventionNed:
accelerometer.axis.z += 1.0f;
break;
}
return accelerometer;
#undef Q
#undef A
}
/**
* @brief Returns the AHRS algorithm internal states.
* @param ahrs AHRS algorithm structure.
* @return AHRS algorithm internal states.
*/
FusionAhrsInternalStates FusionAhrsGetInternalStates(const FusionAhrs *const ahrs)
{
const FusionAhrsInternalStates internalStates = {
.accelerationError = FusionRadiansToDegrees(FusionAsin(2.0f * FusionVectorMagnitude(ahrs->halfAccelerometerFeedback))),
.accelerometerIgnored = ahrs->accelerometerIgnored,
.accelerationRecoveryTrigger =
ahrs->settings.recoveryTriggerPeriod == 0
? 0.0f
: (float)ahrs->accelerationRecoveryTrigger / (float)ahrs->settings.recoveryTriggerPeriod,
.magneticError = FusionRadiansToDegrees(FusionAsin(2.0f * FusionVectorMagnitude(ahrs->halfMagnetometerFeedback))),
.magnetometerIgnored = ahrs->magnetometerIgnored,
.magneticRecoveryTrigger = ahrs->settings.recoveryTriggerPeriod == 0
? 0.0f
: (float)ahrs->magneticRecoveryTrigger / (float)ahrs->settings.recoveryTriggerPeriod,
};
return internalStates;
}
/**
* @brief Returns the AHRS algorithm flags.
* @param ahrs AHRS algorithm structure.
* @return AHRS algorithm flags.
*/
FusionAhrsFlags FusionAhrsGetFlags(const FusionAhrs *const ahrs)
{
const FusionAhrsFlags flags = {
.initialising = ahrs->initialising,
.angularRateRecovery = ahrs->angularRateRecovery,
.accelerationRecovery = ahrs->accelerationRecoveryTrigger > ahrs->accelerationRecoveryTimeout,
.magneticRecovery = ahrs->magneticRecoveryTrigger > ahrs->magneticRecoveryTimeout,
};
return flags;
}
/**
* @brief Sets the heading of the orientation measurement provided by the AHRS
* algorithm. This function can be used to reset drift in heading when the AHRS
* algorithm is being used without a magnetometer.
* @param ahrs AHRS algorithm structure.
* @param heading Heading angle in degrees.
*/
void FusionAhrsSetHeading(FusionAhrs *const ahrs, const float heading)
{
#define Q ahrs->quaternion.element
const float yaw = atan2f(Q.w * Q.z + Q.x * Q.y, 0.5f - Q.y * Q.y - Q.z * Q.z);
const float halfYawMinusHeading = 0.5f * (yaw - FusionDegreesToRadians(heading));
const FusionQuaternion rotation = {.element = {
.w = cosf(halfYawMinusHeading),
.x = 0.0f,
.y = 0.0f,
.z = -1.0f * sinf(halfYawMinusHeading),
}};
ahrs->quaternion = FusionQuaternionMultiply(rotation, ahrs->quaternion);
#undef Q
}
//------------------------------------------------------------------------------
// End of file

112
src/Fusion/FusionAhrs.h Normal file
View File

@ -0,0 +1,112 @@
/**
* @file FusionAhrs.h
* @author Seb Madgwick
* @brief AHRS algorithm to combine gyroscope, accelerometer, and magnetometer
* measurements into a single measurement of orientation relative to the Earth.
*/
#ifndef FUSION_AHRS_H
#define FUSION_AHRS_H
//------------------------------------------------------------------------------
// Includes
#include "FusionConvention.h"
#include "FusionMath.h"
#include <stdbool.h>
//------------------------------------------------------------------------------
// Definitions
/**
* @brief AHRS algorithm settings.
*/
typedef struct {
FusionConvention convention;
float gain;
float gyroscopeRange;
float accelerationRejection;
float magneticRejection;
unsigned int recoveryTriggerPeriod;
} FusionAhrsSettings;
/**
* @brief AHRS algorithm structure. Structure members are used internally and
* must not be accessed by the application.
*/
typedef struct {
FusionAhrsSettings settings;
FusionQuaternion quaternion;
FusionVector accelerometer;
bool initialising;
float rampedGain;
float rampedGainStep;
bool angularRateRecovery;
FusionVector halfAccelerometerFeedback;
FusionVector halfMagnetometerFeedback;
bool accelerometerIgnored;
int accelerationRecoveryTrigger;
int accelerationRecoveryTimeout;
bool magnetometerIgnored;
int magneticRecoveryTrigger;
int magneticRecoveryTimeout;
} FusionAhrs;
/**
* @brief AHRS algorithm internal states.
*/
typedef struct {
float accelerationError;
bool accelerometerIgnored;
float accelerationRecoveryTrigger;
float magneticError;
bool magnetometerIgnored;
float magneticRecoveryTrigger;
} FusionAhrsInternalStates;
/**
* @brief AHRS algorithm flags.
*/
typedef struct {
bool initialising;
bool angularRateRecovery;
bool accelerationRecovery;
bool magneticRecovery;
} FusionAhrsFlags;
//------------------------------------------------------------------------------
// Function declarations
void FusionAhrsInitialise(FusionAhrs *const ahrs);
void FusionAhrsReset(FusionAhrs *const ahrs);
void FusionAhrsSetSettings(FusionAhrs *const ahrs, const FusionAhrsSettings *const settings);
void FusionAhrsUpdate(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer,
const FusionVector magnetometer, const float deltaTime);
void FusionAhrsUpdateNoMagnetometer(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer,
const float deltaTime);
void FusionAhrsUpdateExternalHeading(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer,
const float heading, const float deltaTime);
FusionQuaternion FusionAhrsGetQuaternion(const FusionAhrs *const ahrs);
void FusionAhrsSetQuaternion(FusionAhrs *const ahrs, const FusionQuaternion quaternion);
FusionVector FusionAhrsGetLinearAcceleration(const FusionAhrs *const ahrs);
FusionVector FusionAhrsGetEarthAcceleration(const FusionAhrs *const ahrs);
FusionAhrsInternalStates FusionAhrsGetInternalStates(const FusionAhrs *const ahrs);
FusionAhrsFlags FusionAhrsGetFlags(const FusionAhrs *const ahrs);
void FusionAhrsSetHeading(FusionAhrs *const ahrs, const float heading);
#endif
//------------------------------------------------------------------------------
// End of file

188
src/Fusion/FusionAxes.h Normal file
View File

@ -0,0 +1,188 @@
/**
* @file FusionAxes.h
* @author Seb Madgwick
* @brief Swaps sensor axes for alignment with the body axes.
*/
#ifndef FUSION_AXES_H
#define FUSION_AXES_H
//------------------------------------------------------------------------------
// Includes
#include "FusionMath.h"
//------------------------------------------------------------------------------
// Definitions
/**
* @brief Axes alignment describing the sensor axes relative to the body axes.
* For example, if the body X axis is aligned with the sensor Y axis and the
* body Y axis is aligned with sensor X axis but pointing the opposite direction
* then alignment is +Y-X+Z.
*/
typedef enum {
FusionAxesAlignmentPXPYPZ, /* +X+Y+Z */
FusionAxesAlignmentPXNZPY, /* +X-Z+Y */
FusionAxesAlignmentPXNYNZ, /* +X-Y-Z */
FusionAxesAlignmentPXPZNY, /* +X+Z-Y */
FusionAxesAlignmentNXPYNZ, /* -X+Y-Z */
FusionAxesAlignmentNXPZPY, /* -X+Z+Y */
FusionAxesAlignmentNXNYPZ, /* -X-Y+Z */
FusionAxesAlignmentNXNZNY, /* -X-Z-Y */
FusionAxesAlignmentPYNXPZ, /* +Y-X+Z */
FusionAxesAlignmentPYNZNX, /* +Y-Z-X */
FusionAxesAlignmentPYPXNZ, /* +Y+X-Z */
FusionAxesAlignmentPYPZPX, /* +Y+Z+X */
FusionAxesAlignmentNYPXPZ, /* -Y+X+Z */
FusionAxesAlignmentNYNZPX, /* -Y-Z+X */
FusionAxesAlignmentNYNXNZ, /* -Y-X-Z */
FusionAxesAlignmentNYPZNX, /* -Y+Z-X */
FusionAxesAlignmentPZPYNX, /* +Z+Y-X */
FusionAxesAlignmentPZPXPY, /* +Z+X+Y */
FusionAxesAlignmentPZNYPX, /* +Z-Y+X */
FusionAxesAlignmentPZNXNY, /* +Z-X-Y */
FusionAxesAlignmentNZPYPX, /* -Z+Y+X */
FusionAxesAlignmentNZNXPY, /* -Z-X+Y */
FusionAxesAlignmentNZNYNX, /* -Z-Y-X */
FusionAxesAlignmentNZPXNY, /* -Z+X-Y */
} FusionAxesAlignment;
//------------------------------------------------------------------------------
// Inline functions
/**
* @brief Swaps sensor axes for alignment with the body axes.
* @param sensor Sensor axes.
* @param alignment Axes alignment.
* @return Sensor axes aligned with the body axes.
*/
static inline FusionVector FusionAxesSwap(const FusionVector sensor, const FusionAxesAlignment alignment)
{
FusionVector result;
switch (alignment) {
case FusionAxesAlignmentPXPYPZ:
break;
case FusionAxesAlignmentPXNZPY:
result.axis.x = +sensor.axis.x;
result.axis.y = -sensor.axis.z;
result.axis.z = +sensor.axis.y;
return result;
case FusionAxesAlignmentPXNYNZ:
result.axis.x = +sensor.axis.x;
result.axis.y = -sensor.axis.y;
result.axis.z = -sensor.axis.z;
return result;
case FusionAxesAlignmentPXPZNY:
result.axis.x = +sensor.axis.x;
result.axis.y = +sensor.axis.z;
result.axis.z = -sensor.axis.y;
return result;
case FusionAxesAlignmentNXPYNZ:
result.axis.x = -sensor.axis.x;
result.axis.y = +sensor.axis.y;
result.axis.z = -sensor.axis.z;
return result;
case FusionAxesAlignmentNXPZPY:
result.axis.x = -sensor.axis.x;
result.axis.y = +sensor.axis.z;
result.axis.z = +sensor.axis.y;
return result;
case FusionAxesAlignmentNXNYPZ:
result.axis.x = -sensor.axis.x;
result.axis.y = -sensor.axis.y;
result.axis.z = +sensor.axis.z;
return result;
case FusionAxesAlignmentNXNZNY:
result.axis.x = -sensor.axis.x;
result.axis.y = -sensor.axis.z;
result.axis.z = -sensor.axis.y;
return result;
case FusionAxesAlignmentPYNXPZ:
result.axis.x = +sensor.axis.y;
result.axis.y = -sensor.axis.x;
result.axis.z = +sensor.axis.z;
return result;
case FusionAxesAlignmentPYNZNX:
result.axis.x = +sensor.axis.y;
result.axis.y = -sensor.axis.z;
result.axis.z = -sensor.axis.x;
return result;
case FusionAxesAlignmentPYPXNZ:
result.axis.x = +sensor.axis.y;
result.axis.y = +sensor.axis.x;
result.axis.z = -sensor.axis.z;
return result;
case FusionAxesAlignmentPYPZPX:
result.axis.x = +sensor.axis.y;
result.axis.y = +sensor.axis.z;
result.axis.z = +sensor.axis.x;
return result;
case FusionAxesAlignmentNYPXPZ:
result.axis.x = -sensor.axis.y;
result.axis.y = +sensor.axis.x;
result.axis.z = +sensor.axis.z;
return result;
case FusionAxesAlignmentNYNZPX:
result.axis.x = -sensor.axis.y;
result.axis.y = -sensor.axis.z;
result.axis.z = +sensor.axis.x;
return result;
case FusionAxesAlignmentNYNXNZ:
result.axis.x = -sensor.axis.y;
result.axis.y = -sensor.axis.x;
result.axis.z = -sensor.axis.z;
return result;
case FusionAxesAlignmentNYPZNX:
result.axis.x = -sensor.axis.y;
result.axis.y = +sensor.axis.z;
result.axis.z = -sensor.axis.x;
return result;
case FusionAxesAlignmentPZPYNX:
result.axis.x = +sensor.axis.z;
result.axis.y = +sensor.axis.y;
result.axis.z = -sensor.axis.x;
return result;
case FusionAxesAlignmentPZPXPY:
result.axis.x = +sensor.axis.z;
result.axis.y = +sensor.axis.x;
result.axis.z = +sensor.axis.y;
return result;
case FusionAxesAlignmentPZNYPX:
result.axis.x = +sensor.axis.z;
result.axis.y = -sensor.axis.y;
result.axis.z = +sensor.axis.x;
return result;
case FusionAxesAlignmentPZNXNY:
result.axis.x = +sensor.axis.z;
result.axis.y = -sensor.axis.x;
result.axis.z = -sensor.axis.y;
return result;
case FusionAxesAlignmentNZPYPX:
result.axis.x = -sensor.axis.z;
result.axis.y = +sensor.axis.y;
result.axis.z = +sensor.axis.x;
return result;
case FusionAxesAlignmentNZNXPY:
result.axis.x = -sensor.axis.z;
result.axis.y = -sensor.axis.x;
result.axis.z = +sensor.axis.y;
return result;
case FusionAxesAlignmentNZNYNX:
result.axis.x = -sensor.axis.z;
result.axis.y = -sensor.axis.y;
result.axis.z = -sensor.axis.x;
return result;
case FusionAxesAlignmentNZPXNY:
result.axis.x = -sensor.axis.z;
result.axis.y = +sensor.axis.x;
result.axis.z = -sensor.axis.y;
return result;
}
return sensor; // avoid compiler warning
}
#endif
//------------------------------------------------------------------------------
// End of file

View File

@ -0,0 +1,49 @@
/**
* @file FusionCalibration.h
* @author Seb Madgwick
* @brief Gyroscope, accelerometer, and magnetometer calibration models.
*/
#ifndef FUSION_CALIBRATION_H
#define FUSION_CALIBRATION_H
//------------------------------------------------------------------------------
// Includes
#include "FusionMath.h"
//------------------------------------------------------------------------------
// Inline functions
/**
* @brief Gyroscope and accelerometer calibration model.
* @param uncalibrated Uncalibrated measurement.
* @param misalignment Misalignment matrix.
* @param sensitivity Sensitivity.
* @param offset Offset.
* @return Calibrated measurement.
*/
static inline FusionVector FusionCalibrationInertial(const FusionVector uncalibrated, const FusionMatrix misalignment,
const FusionVector sensitivity, const FusionVector offset)
{
return FusionMatrixMultiplyVector(misalignment,
FusionVectorHadamardProduct(FusionVectorSubtract(uncalibrated, offset), sensitivity));
}
/**
* @brief Magnetometer calibration model.
* @param uncalibrated Uncalibrated measurement.
* @param softIronMatrix Soft-iron matrix.
* @param hardIronOffset Hard-iron offset.
* @return Calibrated measurement.
*/
static inline FusionVector FusionCalibrationMagnetic(const FusionVector uncalibrated, const FusionMatrix softIronMatrix,
const FusionVector hardIronOffset)
{
return FusionMatrixMultiplyVector(softIronMatrix, FusionVectorSubtract(uncalibrated, hardIronOffset));
}
#endif
//------------------------------------------------------------------------------
// End of file

View File

@ -0,0 +1,51 @@
/**
* @file FusionCompass.c
* @author Seb Madgwick
* @brief Tilt-compensated compass to calculate the magnetic heading using
* accelerometer and magnetometer measurements.
*/
//------------------------------------------------------------------------------
// Includes
#include "FusionCompass.h"
#include "FusionAxes.h"
#include <math.h> // atan2f
//------------------------------------------------------------------------------
// Functions
/**
* @brief Calculates the magnetic heading.
* @param convention Earth axes convention.
* @param accelerometer Accelerometer measurement in any calibrated units.
* @param magnetometer Magnetometer measurement in any calibrated units.
* @return Heading angle in degrees.
*/
float FusionCompassCalculateHeading(const FusionConvention convention, const FusionVector accelerometer,
const FusionVector magnetometer)
{
switch (convention) {
case FusionConventionNwu: {
const FusionVector west = FusionVectorNormalise(FusionVectorCrossProduct(accelerometer, magnetometer));
const FusionVector north = FusionVectorNormalise(FusionVectorCrossProduct(west, accelerometer));
return FusionRadiansToDegrees(atan2f(west.axis.x, north.axis.x));
}
case FusionConventionEnu: {
const FusionVector west = FusionVectorNormalise(FusionVectorCrossProduct(accelerometer, magnetometer));
const FusionVector north = FusionVectorNormalise(FusionVectorCrossProduct(west, accelerometer));
const FusionVector east = FusionVectorMultiplyScalar(west, -1.0f);
return FusionRadiansToDegrees(atan2f(north.axis.x, east.axis.x));
}
case FusionConventionNed: {
const FusionVector up = FusionVectorMultiplyScalar(accelerometer, -1.0f);
const FusionVector west = FusionVectorNormalise(FusionVectorCrossProduct(up, magnetometer));
const FusionVector north = FusionVectorNormalise(FusionVectorCrossProduct(west, up));
return FusionRadiansToDegrees(atan2f(west.axis.x, north.axis.x));
}
}
return 0; // avoid compiler warning
}
//------------------------------------------------------------------------------
// End of file

View File

@ -0,0 +1,26 @@
/**
* @file FusionCompass.h
* @author Seb Madgwick
* @brief Tilt-compensated compass to calculate the magnetic heading using
* accelerometer and magnetometer measurements.
*/
#ifndef FUSION_COMPASS_H
#define FUSION_COMPASS_H
//------------------------------------------------------------------------------
// Includes
#include "FusionConvention.h"
#include "FusionMath.h"
//------------------------------------------------------------------------------
// Function declarations
float FusionCompassCalculateHeading(const FusionConvention convention, const FusionVector accelerometer,
const FusionVector magnetometer);
#endif
//------------------------------------------------------------------------------
// End of file

View File

@ -0,0 +1,25 @@
/**
* @file FusionConvention.h
* @author Seb Madgwick
* @brief Earth axes convention.
*/
#ifndef FUSION_CONVENTION_H
#define FUSION_CONVENTION_H
//------------------------------------------------------------------------------
// Definitions
/**
* @brief Earth axes convention.
*/
typedef enum {
FusionConventionNwu, /* North-West-Up */
FusionConventionEnu, /* East-North-Up */
FusionConventionNed, /* North-East-Down */
} FusionConvention;
#endif
//------------------------------------------------------------------------------
// End of file

503
src/Fusion/FusionMath.h Normal file
View File

@ -0,0 +1,503 @@
/**
* @file FusionMath.h
* @author Seb Madgwick
* @brief Math library.
*/
#ifndef FUSION_MATH_H
#define FUSION_MATH_H
//------------------------------------------------------------------------------
// Includes
#include <math.h> // M_PI, sqrtf, atan2f, asinf
#include <stdbool.h>
#include <stdint.h>
//------------------------------------------------------------------------------
// Definitions
/**
* @brief 3D vector.
*/
typedef union {
float array[3];
struct {
float x;
float y;
float z;
} axis;
} FusionVector;
/**
* @brief Quaternion.
*/
typedef union {
float array[4];
struct {
float w;
float x;
float y;
float z;
} element;
} FusionQuaternion;
/**
* @brief 3x3 matrix in row-major order.
* See http://en.wikipedia.org/wiki/Row-major_order
*/
typedef union {
float array[3][3];
struct {
float xx;
float xy;
float xz;
float yx;
float yy;
float yz;
float zx;
float zy;
float zz;
} element;
} FusionMatrix;
/**
* @brief Euler angles. Roll, pitch, and yaw correspond to rotations around
* X, Y, and Z respectively.
*/
typedef union {
float array[3];
struct {
float roll;
float pitch;
float yaw;
} angle;
} FusionEuler;
/**
* @brief Vector of zeros.
*/
#define FUSION_VECTOR_ZERO ((FusionVector){.array = {0.0f, 0.0f, 0.0f}})
/**
* @brief Vector of ones.
*/
#define FUSION_VECTOR_ONES ((FusionVector){.array = {1.0f, 1.0f, 1.0f}})
/**
* @brief Identity quaternion.
*/
#define FUSION_IDENTITY_QUATERNION ((FusionQuaternion){.array = {1.0f, 0.0f, 0.0f, 0.0f}})
/**
* @brief Identity matrix.
*/
#define FUSION_IDENTITY_MATRIX ((FusionMatrix){.array = {{1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}})
/**
* @brief Euler angles of zero.
*/
#define FUSION_EULER_ZERO ((FusionEuler){.array = {0.0f, 0.0f, 0.0f}})
/**
* @brief Pi. May not be defined in math.h.
*/
#ifndef M_PI
#define M_PI (3.14159265358979323846)
#endif
/**
* @brief Include this definition or add as a preprocessor definition to use
* normal square root operations.
*/
// #define FUSION_USE_NORMAL_SQRT
//------------------------------------------------------------------------------
// Inline functions - Degrees and radians conversion
/**
* @brief Converts degrees to radians.
* @param degrees Degrees.
* @return Radians.
*/
static inline float FusionDegreesToRadians(const float degrees)
{
return degrees * ((float)M_PI / 180.0f);
}
/**
* @brief Converts radians to degrees.
* @param radians Radians.
* @return Degrees.
*/
static inline float FusionRadiansToDegrees(const float radians)
{
return radians * (180.0f / (float)M_PI);
}
//------------------------------------------------------------------------------
// Inline functions - Arc sine
/**
* @brief Returns the arc sine of the value.
* @param value Value.
* @return Arc sine of the value.
*/
static inline float FusionAsin(const float value)
{
if (value <= -1.0f) {
return (float)M_PI / -2.0f;
}
if (value >= 1.0f) {
return (float)M_PI / 2.0f;
}
return asinf(value);
}
//------------------------------------------------------------------------------
// Inline functions - Fast inverse square root
#ifndef FUSION_USE_NORMAL_SQRT
/**
* @brief Calculates the reciprocal of the square root.
* See https://pizer.wordpress.com/2008/10/12/fast-inverse-square-root/
* @param x Operand.
* @return Reciprocal of the square root of x.
*/
static inline float FusionFastInverseSqrt(const float x)
{
typedef union {
float f;
int32_t i;
} Union32;
Union32 union32 = {.f = x};
union32.i = 0x5F1F1412 - (union32.i >> 1);
return union32.f * (1.69000231f - 0.714158168f * x * union32.f * union32.f);
}
#endif
//------------------------------------------------------------------------------
// Inline functions - Vector operations
/**
* @brief Returns true if the vector is zero.
* @param vector Vector.
* @return True if the vector is zero.
*/
static inline bool FusionVectorIsZero(const FusionVector vector)
{
return (vector.axis.x == 0.0f) && (vector.axis.y == 0.0f) && (vector.axis.z == 0.0f);
}
/**
* @brief Returns the sum of two vectors.
* @param vectorA Vector A.
* @param vectorB Vector B.
* @return Sum of two vectors.
*/
static inline FusionVector FusionVectorAdd(const FusionVector vectorA, const FusionVector vectorB)
{
const FusionVector result = {.axis = {
.x = vectorA.axis.x + vectorB.axis.x,
.y = vectorA.axis.y + vectorB.axis.y,
.z = vectorA.axis.z + vectorB.axis.z,
}};
return result;
}
/**
* @brief Returns vector B subtracted from vector A.
* @param vectorA Vector A.
* @param vectorB Vector B.
* @return Vector B subtracted from vector A.
*/
static inline FusionVector FusionVectorSubtract(const FusionVector vectorA, const FusionVector vectorB)
{
const FusionVector result = {.axis = {
.x = vectorA.axis.x - vectorB.axis.x,
.y = vectorA.axis.y - vectorB.axis.y,
.z = vectorA.axis.z - vectorB.axis.z,
}};
return result;
}
/**
* @brief Returns the sum of the elements.
* @param vector Vector.
* @return Sum of the elements.
*/
static inline float FusionVectorSum(const FusionVector vector)
{
return vector.axis.x + vector.axis.y + vector.axis.z;
}
/**
* @brief Returns the multiplication of a vector by a scalar.
* @param vector Vector.
* @param scalar Scalar.
* @return Multiplication of a vector by a scalar.
*/
static inline FusionVector FusionVectorMultiplyScalar(const FusionVector vector, const float scalar)
{
const FusionVector result = {.axis = {
.x = vector.axis.x * scalar,
.y = vector.axis.y * scalar,
.z = vector.axis.z * scalar,
}};
return result;
}
/**
* @brief Calculates the Hadamard product (element-wise multiplication).
* @param vectorA Vector A.
* @param vectorB Vector B.
* @return Hadamard product.
*/
static inline FusionVector FusionVectorHadamardProduct(const FusionVector vectorA, const FusionVector vectorB)
{
const FusionVector result = {.axis = {
.x = vectorA.axis.x * vectorB.axis.x,
.y = vectorA.axis.y * vectorB.axis.y,
.z = vectorA.axis.z * vectorB.axis.z,
}};
return result;
}
/**
* @brief Returns the cross product.
* @param vectorA Vector A.
* @param vectorB Vector B.
* @return Cross product.
*/
static inline FusionVector FusionVectorCrossProduct(const FusionVector vectorA, const FusionVector vectorB)
{
#define A vectorA.axis
#define B vectorB.axis
const FusionVector result = {.axis = {
.x = A.y * B.z - A.z * B.y,
.y = A.z * B.x - A.x * B.z,
.z = A.x * B.y - A.y * B.x,
}};
return result;
#undef A
#undef B
}
/**
* @brief Returns the dot product.
* @param vectorA Vector A.
* @param vectorB Vector B.
* @return Dot product.
*/
static inline float FusionVectorDotProduct(const FusionVector vectorA, const FusionVector vectorB)
{
return FusionVectorSum(FusionVectorHadamardProduct(vectorA, vectorB));
}
/**
* @brief Returns the vector magnitude squared.
* @param vector Vector.
* @return Vector magnitude squared.
*/
static inline float FusionVectorMagnitudeSquared(const FusionVector vector)
{
return FusionVectorSum(FusionVectorHadamardProduct(vector, vector));
}
/**
* @brief Returns the vector magnitude.
* @param vector Vector.
* @return Vector magnitude.
*/
static inline float FusionVectorMagnitude(const FusionVector vector)
{
return sqrtf(FusionVectorMagnitudeSquared(vector));
}
/**
* @brief Returns the normalised vector.
* @param vector Vector.
* @return Normalised vector.
*/
static inline FusionVector FusionVectorNormalise(const FusionVector vector)
{
#ifdef FUSION_USE_NORMAL_SQRT
const float magnitudeReciprocal = 1.0f / sqrtf(FusionVectorMagnitudeSquared(vector));
#else
const float magnitudeReciprocal = FusionFastInverseSqrt(FusionVectorMagnitudeSquared(vector));
#endif
return FusionVectorMultiplyScalar(vector, magnitudeReciprocal);
}
//------------------------------------------------------------------------------
// Inline functions - Quaternion operations
/**
* @brief Returns the sum of two quaternions.
* @param quaternionA Quaternion A.
* @param quaternionB Quaternion B.
* @return Sum of two quaternions.
*/
static inline FusionQuaternion FusionQuaternionAdd(const FusionQuaternion quaternionA, const FusionQuaternion quaternionB)
{
const FusionQuaternion result = {.element = {
.w = quaternionA.element.w + quaternionB.element.w,
.x = quaternionA.element.x + quaternionB.element.x,
.y = quaternionA.element.y + quaternionB.element.y,
.z = quaternionA.element.z + quaternionB.element.z,
}};
return result;
}
/**
* @brief Returns the multiplication of two quaternions.
* @param quaternionA Quaternion A (to be post-multiplied).
* @param quaternionB Quaternion B (to be pre-multiplied).
* @return Multiplication of two quaternions.
*/
static inline FusionQuaternion FusionQuaternionMultiply(const FusionQuaternion quaternionA, const FusionQuaternion quaternionB)
{
#define A quaternionA.element
#define B quaternionB.element
const FusionQuaternion result = {.element = {
.w = A.w * B.w - A.x * B.x - A.y * B.y - A.z * B.z,
.x = A.w * B.x + A.x * B.w + A.y * B.z - A.z * B.y,
.y = A.w * B.y - A.x * B.z + A.y * B.w + A.z * B.x,
.z = A.w * B.z + A.x * B.y - A.y * B.x + A.z * B.w,
}};
return result;
#undef A
#undef B
}
/**
* @brief Returns the multiplication of a quaternion with a vector. This is a
* normal quaternion multiplication where the vector is treated a
* quaternion with a W element value of zero. The quaternion is post-
* multiplied by the vector.
* @param quaternion Quaternion.
* @param vector Vector.
* @return Multiplication of a quaternion with a vector.
*/
static inline FusionQuaternion FusionQuaternionMultiplyVector(const FusionQuaternion quaternion, const FusionVector vector)
{
#define Q quaternion.element
#define V vector.axis
const FusionQuaternion result = {.element = {
.w = -Q.x * V.x - Q.y * V.y - Q.z * V.z,
.x = Q.w * V.x + Q.y * V.z - Q.z * V.y,
.y = Q.w * V.y - Q.x * V.z + Q.z * V.x,
.z = Q.w * V.z + Q.x * V.y - Q.y * V.x,
}};
return result;
#undef Q
#undef V
}
/**
* @brief Returns the normalised quaternion.
* @param quaternion Quaternion.
* @return Normalised quaternion.
*/
static inline FusionQuaternion FusionQuaternionNormalise(const FusionQuaternion quaternion)
{
#define Q quaternion.element
#ifdef FUSION_USE_NORMAL_SQRT
const float magnitudeReciprocal = 1.0f / sqrtf(Q.w * Q.w + Q.x * Q.x + Q.y * Q.y + Q.z * Q.z);
#else
const float magnitudeReciprocal = FusionFastInverseSqrt(Q.w * Q.w + Q.x * Q.x + Q.y * Q.y + Q.z * Q.z);
#endif
const FusionQuaternion result = {.element = {
.w = Q.w * magnitudeReciprocal,
.x = Q.x * magnitudeReciprocal,
.y = Q.y * magnitudeReciprocal,
.z = Q.z * magnitudeReciprocal,
}};
return result;
#undef Q
}
//------------------------------------------------------------------------------
// Inline functions - Matrix operations
/**
* @brief Returns the multiplication of a matrix with a vector.
* @param matrix Matrix.
* @param vector Vector.
* @return Multiplication of a matrix with a vector.
*/
static inline FusionVector FusionMatrixMultiplyVector(const FusionMatrix matrix, const FusionVector vector)
{
#define R matrix.element
const FusionVector result = {.axis = {
.x = R.xx * vector.axis.x + R.xy * vector.axis.y + R.xz * vector.axis.z,
.y = R.yx * vector.axis.x + R.yy * vector.axis.y + R.yz * vector.axis.z,
.z = R.zx * vector.axis.x + R.zy * vector.axis.y + R.zz * vector.axis.z,
}};
return result;
#undef R
}
//------------------------------------------------------------------------------
// Inline functions - Conversion operations
/**
* @brief Converts a quaternion to a rotation matrix.
* @param quaternion Quaternion.
* @return Rotation matrix.
*/
static inline FusionMatrix FusionQuaternionToMatrix(const FusionQuaternion quaternion)
{
#define Q quaternion.element
const float qwqw = Q.w * Q.w; // calculate common terms to avoid repeated operations
const float qwqx = Q.w * Q.x;
const float qwqy = Q.w * Q.y;
const float qwqz = Q.w * Q.z;
const float qxqy = Q.x * Q.y;
const float qxqz = Q.x * Q.z;
const float qyqz = Q.y * Q.z;
const FusionMatrix matrix = {.element = {
.xx = 2.0f * (qwqw - 0.5f + Q.x * Q.x),
.xy = 2.0f * (qxqy - qwqz),
.xz = 2.0f * (qxqz + qwqy),
.yx = 2.0f * (qxqy + qwqz),
.yy = 2.0f * (qwqw - 0.5f + Q.y * Q.y),
.yz = 2.0f * (qyqz - qwqx),
.zx = 2.0f * (qxqz - qwqy),
.zy = 2.0f * (qyqz + qwqx),
.zz = 2.0f * (qwqw - 0.5f + Q.z * Q.z),
}};
return matrix;
#undef Q
}
/**
* @brief Converts a quaternion to ZYX Euler angles in degrees.
* @param quaternion Quaternion.
* @return Euler angles in degrees.
*/
static inline FusionEuler FusionQuaternionToEuler(const FusionQuaternion quaternion)
{
#define Q quaternion.element
const float halfMinusQySquared = 0.5f - Q.y * Q.y; // calculate common terms to avoid repeated operations
const FusionEuler euler = {.angle = {
.roll = FusionRadiansToDegrees(atan2f(Q.w * Q.x + Q.y * Q.z, halfMinusQySquared - Q.x * Q.x)),
.pitch = FusionRadiansToDegrees(FusionAsin(2.0f * (Q.w * Q.y - Q.z * Q.x))),
.yaw = FusionRadiansToDegrees(atan2f(Q.w * Q.z + Q.x * Q.y, halfMinusQySquared - Q.z * Q.z)),
}};
return euler;
#undef Q
}
#endif
//------------------------------------------------------------------------------
// End of file

80
src/Fusion/FusionOffset.c Normal file
View File

@ -0,0 +1,80 @@
/**
* @file FusionOffset.c
* @author Seb Madgwick
* @brief Gyroscope offset correction algorithm for run-time calibration of the
* gyroscope offset.
*/
//------------------------------------------------------------------------------
// Includes
#include "FusionOffset.h"
#include <math.h> // fabsf
//------------------------------------------------------------------------------
// Definitions
/**
* @brief Cutoff frequency in Hz.
*/
#define CUTOFF_FREQUENCY (0.02f)
/**
* @brief Timeout in seconds.
*/
#define TIMEOUT (5)
/**
* @brief Threshold in degrees per second.
*/
#define THRESHOLD (3.0f)
//------------------------------------------------------------------------------
// Functions
/**
* @brief Initialises the gyroscope offset algorithm.
* @param offset Gyroscope offset algorithm structure.
* @param sampleRate Sample rate in Hz.
*/
void FusionOffsetInitialise(FusionOffset *const offset, const unsigned int sampleRate)
{
offset->filterCoefficient = 2.0f * (float)M_PI * CUTOFF_FREQUENCY * (1.0f / (float)sampleRate);
offset->timeout = TIMEOUT * sampleRate;
offset->timer = 0;
offset->gyroscopeOffset = FUSION_VECTOR_ZERO;
}
/**
* @brief Updates the gyroscope offset algorithm and returns the corrected
* gyroscope measurement.
* @param offset Gyroscope offset algorithm structure.
* @param gyroscope Gyroscope measurement in degrees per second.
* @return Corrected gyroscope measurement in degrees per second.
*/
FusionVector FusionOffsetUpdate(FusionOffset *const offset, FusionVector gyroscope)
{
// Subtract offset from gyroscope measurement
gyroscope = FusionVectorSubtract(gyroscope, offset->gyroscopeOffset);
// Reset timer if gyroscope not stationary
if ((fabsf(gyroscope.axis.x) > THRESHOLD) || (fabsf(gyroscope.axis.y) > THRESHOLD) || (fabsf(gyroscope.axis.z) > THRESHOLD)) {
offset->timer = 0;
return gyroscope;
}
// Increment timer while gyroscope stationary
if (offset->timer < offset->timeout) {
offset->timer++;
return gyroscope;
}
// Adjust offset if timer has elapsed
offset->gyroscopeOffset =
FusionVectorAdd(offset->gyroscopeOffset, FusionVectorMultiplyScalar(gyroscope, offset->filterCoefficient));
return gyroscope;
}
//------------------------------------------------------------------------------
// End of file

40
src/Fusion/FusionOffset.h Normal file
View File

@ -0,0 +1,40 @@
/**
* @file FusionOffset.h
* @author Seb Madgwick
* @brief Gyroscope offset correction algorithm for run-time calibration of the
* gyroscope offset.
*/
#ifndef FUSION_OFFSET_H
#define FUSION_OFFSET_H
//------------------------------------------------------------------------------
// Includes
#include "FusionMath.h"
//------------------------------------------------------------------------------
// Definitions
/**
* @brief Gyroscope offset algorithm structure. Structure members are used
* internally and must not be accessed by the application.
*/
typedef struct {
float filterCoefficient;
unsigned int timeout;
unsigned int timer;
FusionVector gyroscopeOffset;
} FusionOffset;
//------------------------------------------------------------------------------
// Function declarations
void FusionOffsetInitialise(FusionOffset *const offset, const unsigned int sampleRate);
FusionVector FusionOffsetUpdate(FusionOffset *const offset, FusionVector gyroscope);
#endif
//------------------------------------------------------------------------------
// End of file

View File

@ -50,7 +50,7 @@ RTC_NOINIT_ATTR uint64_t RTC_reg_b;
esp_adc_cal_characteristics_t *adc_characs = (esp_adc_cal_characteristics_t *)calloc(1, sizeof(esp_adc_cal_characteristics_t));
#ifndef ADC_ATTENUATION
static const adc_atten_t atten = ADC_ATTEN_DB_11;
static const adc_atten_t atten = ADC_ATTEN_DB_12;
#else
static const adc_atten_t atten = ADC_ATTENUATION;
#endif
@ -69,12 +69,16 @@ static const uint8_t ext_chrg_detect_value = EXT_CHRG_DETECT_VALUE;
#endif
#endif
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO)
INA260Sensor ina260Sensor;
INA219Sensor ina219Sensor;
INA3221Sensor ina3221Sensor;
#endif
#if HAS_RAKPROT && !defined(ARCH_PORTDUINO)
RAK9154Sensor rak9154Sensor;
#endif
#ifdef HAS_PMU
#include "XPowersAXP192.tpp"
#include "XPowersAXP2101.tpp"
@ -145,6 +149,12 @@ class AnalogBatteryLevel : public HasBatteryLevel
*/
virtual int getBatteryPercent() override
{
#if defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU)
if (hasRAK()) {
return rak9154Sensor.getBusBatteryPercent();
}
#endif
float v = getBattVoltage();
if (v < noBatVolt)
@ -184,7 +194,13 @@ class AnalogBatteryLevel : public HasBatteryLevel
virtual uint16_t getBattVoltage() override
{
#if defined(HAS_TELEMETRY) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU)
#if defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU)
if (hasRAK()) {
return getRAKVoltage();
}
#endif
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
if (hasINA()) {
LOG_DEBUG("Using INA on I2C addr 0x%x for device battery voltage\n", config.power.device_battery_ina_address);
return getINAVoltage();
@ -223,7 +239,17 @@ class AnalogBatteryLevel : public HasBatteryLevel
raw = raw / BATTERY_SENSE_SAMPLES;
scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw;
#endif
last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF
if (!initial_read_done) {
// Flush the smoothing filter with an ADC reading, if the reading is plausibly correct
if (scaled > last_read_value)
last_read_value = scaled;
initial_read_done = true;
} else {
// Already initialized - filter this reading
last_read_value += (scaled - last_read_value) * 0.5; // Virtual LPF
}
// LOG_DEBUG("battery gpio %d raw val=%u scaled=%u filtered=%u\n", BATTERY_PIN, raw, (uint32_t)(scaled), (uint32_t)
// (last_read_value));
}
@ -325,13 +351,20 @@ class AnalogBatteryLevel : public HasBatteryLevel
virtual bool isVbusIn() override
{
#ifdef EXT_PWR_DETECT
#ifdef HELTEC_CAPSULE_SENSOR_V3
// if external powered that pin will be pulled down
if (digitalRead(EXT_PWR_DETECT) == LOW) {
return true;
}
// if it's not LOW - check the battery
#else
// if external powered that pin will be pulled up
if (digitalRead(EXT_PWR_DETECT) == HIGH) {
return true;
}
// if it's not HIGH - check the battery
#endif
#endif
return getBattVoltage() > chargingVolt;
}
@ -339,6 +372,11 @@ class AnalogBatteryLevel : public HasBatteryLevel
/// we can't be smart enough to say 'full'?
virtual bool isCharging() override
{
#if defined(HAS_RAKPROT) && !defined(ARCH_PORTDUINO) && !defined(HAS_PMU)
if (hasRAK()) {
return (rak9154Sensor.isCharging()) ? OptTrue : OptFalse;
}
#endif
#ifdef EXT_CHRG_DETECT
return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
#else
@ -357,10 +395,24 @@ class AnalogBatteryLevel : public HasBatteryLevel
const float noBatVolt = (OCV[NUM_OCV_POINTS - 1] - 500) * NUM_CELLS;
// Start value from minimum voltage for the filter to not start from 0
// that could trigger some events.
// This value is over-written by the first ADC reading, it the voltage seems reasonable.
bool initial_read_done = false;
float last_read_value = (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS);
uint32_t last_read_time_ms = 0;
#if defined(HAS_TELEMETRY) && !defined(ARCH_PORTDUINO)
#if defined(HAS_RAKPROT)
uint16_t getRAKVoltage() { return rak9154Sensor.getBusVoltageMv(); }
bool hasRAK()
{
if (!rak9154Sensor.isInitialized())
return rak9154Sensor.runOnce() > 0;
return rak9154Sensor.isRunning();
}
#endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && !defined(ARCH_PORTDUINO)
uint16_t getINAVoltage()
{
if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA219].first == config.power.device_battery_ina_address) {
@ -409,8 +461,12 @@ Power::Power() : OSThread("Power")
bool Power::analogInit()
{
#ifdef EXT_PWR_DETECT
#ifdef HELTEC_CAPSULE_SENSOR_V3
pinMode(EXT_PWR_DETECT, INPUT_PULLUP);
#else
pinMode(EXT_PWR_DETECT, INPUT);
#endif
#endif
#ifdef EXT_CHRG_DETECT
pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode);
#endif
@ -543,14 +599,24 @@ void Power::readPowerStatus()
#ifdef NRF_APM // Section of code detects USB power on the RAK4631 and updates the power states. Takes 20 seconds or so to detect
// changes.
static nrfx_power_usb_state_t prev_nrf_usb_state = (nrfx_power_usb_state_t)-1; // -1 so that state detected at boot
nrfx_power_usb_state_t nrf_usb_state = nrfx_power_usbstatus_get();
if (nrf_usb_state == NRFX_POWER_USB_STATE_DISCONNECTED) {
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
NRF_USB = OptFalse;
} else {
powerFSM.trigger(EVENT_POWER_CONNECTED);
NRF_USB = OptTrue;
// If state changed
if (nrf_usb_state != prev_nrf_usb_state) {
// If changed to DISCONNECTED
if (nrf_usb_state == NRFX_POWER_USB_STATE_DISCONNECTED) {
powerFSM.trigger(EVENT_POWER_DISCONNECTED);
NRF_USB = OptFalse;
}
// If changed to CONNECTED / READY
else {
powerFSM.trigger(EVENT_POWER_CONNECTED);
NRF_USB = OptTrue;
}
// Cache the current state
prev_nrf_usb_state = nrf_usb_state;
}
#endif
// Notify any status instances that are observing us

View File

@ -348,12 +348,18 @@ void PowerFSM_setup()
powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone");
powerFSM.add_timed_transition(&stateON, &stateDARK,
Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
"Screen-on timeout");
powerFSM.add_timed_transition(&statePOWER, &stateDARK,
Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
"Screen-on timeout");
#ifdef USE_EINK
// Allow E-Ink devices to suppress the screensaver, if screen timeout set to 0
if (config.display.screen_on_secs > 0)
#endif
{
powerFSM.add_timed_transition(&stateON, &stateDARK,
Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs),
NULL, "Screen-on timeout");
powerFSM.add_timed_transition(&statePOWER, &stateDARK,
Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs),
NULL, "Screen-on timeout");
}
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
#ifdef ARCH_ESP32

View File

@ -59,9 +59,18 @@ class PowerStatus : public Status
int getBatteryVoltageMv() const { return batteryVoltageMv; }
/**
* Note: 0% battery means 'unknown/this board doesn't have a battery installed'
* Note: for boards with battery pin or PMU, 0% battery means 'unknown/this board doesn't have a battery installed'
*/
#if defined(HAS_PMU) || defined(BATTERY_PIN)
uint8_t getBatteryChargePercent() const { return getHasBattery() ? batteryChargePercent : 0; }
#endif
/**
* Note: for boards without battery pin and PMU, 101% battery means 'the board is using external power'
*/
#if !defined(HAS_PMU) && !defined(BATTERY_PIN)
uint8_t getBatteryChargePercent() const { return getHasBattery() ? batteryChargePercent : 101; }
#endif
bool matches(const PowerStatus *newStatus) const
{

View File

@ -2,6 +2,7 @@
#include "NodeDB.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "time.h"
#ifdef RP2040_SLOW_CLOCK
#define Port Serial2
@ -50,7 +51,9 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
}
}
#endif
#if !ARCH_PORTDUINO
emitRebooted();
#endif
}
int32_t SerialConsole::runOnce()

View File

@ -127,8 +127,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define SHTC3_ADDR 0x70
#define LPS22HB_ADDR 0x5C
#define LPS22HB_ADDR_ALT 0x5D
#define SHT31_ADDR 0x44
#define SHT31_4x_ADDR 0x44
#define PMSA0031_ADDR 0x12
#define AHT10_ADDR 0x38
#define RCWL9620_ADDR 0x57
#define VEML7700_ADDR 0x10
#define TSL25911_ADDR 0x29
#define OPT3001_ADDR 0x45
#define OPT3001_ADDR_ALT 0x44
#define MLX90632_ADDR 0x3A
#define DFROBOT_LARK_ADDR 0x42
#define NAU7802_ADDR 0x2A
// -----------------------------------------------------------------------------
// ACCELEROMETER
@ -137,6 +146,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define LIS3DH_ADR 0x18
#define BMA423_ADDR 0x19
#define LSM6DS3_ADDR 0x6A
#define BMX160_ADDR 0x69
// -----------------------------------------------------------------------------
// LED

View File

@ -1,5 +1,16 @@
#pragma once
enum LoRaRadioType { NO_RADIO, STM32WLx_RADIO, SIM_RADIO, RF95_RADIO, SX1262_RADIO, SX1268_RADIO, LLCC68_RADIO, SX1280_RADIO };
enum LoRaRadioType {
NO_RADIO,
STM32WLx_RADIO,
SIM_RADIO,
RF95_RADIO,
SX1262_RADIO,
SX1268_RADIO,
LLCC68_RADIO,
SX1280_RADIO,
LR1110_RADIO,
LR1120_RADIO
};
extern LoRaRadioType radioType;

View File

@ -6,6 +6,7 @@ const ScanI2C::FoundDevice ScanI2C::DEVICE_NONE = ScanI2C::FoundDevice(ScanI2C::
ScanI2C::ScanI2C() = default;
void ScanI2C::scanPort(ScanI2C::I2CPort port) {}
void ScanI2C::scanPort(ScanI2C::I2CPort port, uint8_t *address, uint8_t asize) {}
void ScanI2C::setSuppressScreen()
{
@ -36,8 +37,8 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
{
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3};
return firstOfOrNONE(4, types);
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160};
return firstOfOrNONE(5, types);
}
ScanI2C::FoundDevice ScanI2C::find(ScanI2C::DeviceType) const

View File

@ -30,6 +30,7 @@ class ScanI2C
INA3221,
MCP9808,
SHT31,
SHT4X,
SHTC3,
LPS22HB,
QMC6310,
@ -42,9 +43,16 @@ class ScanI2C
BQ24295,
LSM6DS3,
TCA9555,
#ifdef HAS_NCP5623
VEML7700,
RCWL9620,
NCP5623,
#endif
TSL2591,
OPT3001,
MLX90632,
AHT10,
BMX160,
DFROBOT_LARK,
NAU7802
} DeviceType;
// typedef uint8_t DeviceAddress;
@ -81,6 +89,7 @@ class ScanI2C
ScanI2C();
virtual void scanPort(ScanI2C::I2CPort);
virtual void scanPort(ScanI2C::I2CPort, uint8_t *, uint8_t);
/*
* A bit of a hack, this tells the scanner not to tell later systems there is a screen to avoid enabling it.

View File

@ -14,6 +14,15 @@
#define XPOWERS_AXP192_AXP2101_ADDRESS 0x34
#endif
bool in_array(uint8_t *array, int size, uint8_t lookfor)
{
int i;
for (i = 0; i < size; i++)
if (lookfor == array[i])
return true;
return false;
}
ScanI2C::FoundDevice ScanI2CTwoWire::find(ScanI2C::DeviceType type) const
{
concurrency::LockGuard guard((concurrency::Lock *)&lock);
@ -135,11 +144,11 @@ uint16_t ScanI2CTwoWire::getRegisterValue(const ScanI2CTwoWire::RegisterLocation
type = T; \
break;
void ScanI2CTwoWire::scanPort(I2CPort port)
void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
{
concurrency::LockGuard guard((concurrency::Lock *)&lock);
LOG_DEBUG("Scanning for i2c devices on port %d\n", port);
LOG_DEBUG("Scanning for I2C devices on port %d\n", port);
uint8_t err;
@ -163,6 +172,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
#endif
for (addr.address = 1; addr.address < 127; addr.address++) {
if (asize != 0) {
if (!in_array(address, asize, addr.address))
continue;
LOG_DEBUG("Scanning address 0x%x\n", addr.address);
}
i2cBus->beginTransmission(addr.address);
#ifdef ARCH_PORTDUINO
if (i2cBus->read() != -1)
@ -257,7 +271,12 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
type = BMP_280;
}
break;
#ifndef HAS_NCP5623
case AHT10_ADDR:
LOG_INFO("AHT10 sensor found at address 0x%x\n", (uint8_t)addr.address);
type = AHT10;
break;
#endif
case INA_ADDR:
case INA_ADDR_ALTERNATE:
case INA_ADDR_WAVESHARE_UPS:
@ -277,8 +296,9 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
if (registerValue == 0x5449) {
LOG_INFO("INA3221 sensor found at address 0x%x\n", (uint8_t)addr.address);
type = INA3221;
} else { // Unknown device
LOG_INFO("No INA3221 found at address 0x%x\n", (uint8_t)addr.address);
} else {
LOG_INFO("DFRobot Lark weather station found at address 0x%x\n", (uint8_t)addr.address);
type = DFROBOT_LARK;
}
break;
case MCP9808_ADDR:
@ -293,8 +313,23 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
break;
SCAN_SIMPLE_CASE(SHT31_ADDR, SHT31, "SHT31 sensor found\n")
case SHT31_4x_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x89), 2);
if (registerValue == 0x11a2) {
type = SHT4X;
LOG_INFO("SHT4X sensor found\n");
} else if (getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x7E), 2) == 0x5449) {
type = OPT3001;
LOG_INFO("OPT3001 light sensor found\n");
} else {
type = SHT31;
LOG_INFO("SHT31 sensor found\n");
}
break;
SCAN_SIMPLE_CASE(SHTC3_ADDR, SHTC3, "SHTC3 sensor found\n")
SCAN_SIMPLE_CASE(RCWL9620_ADDR, RCWL9620, "RCWL9620 sensor found\n")
case LPS22HB_ADDR_ALT:
SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB sensor found\n")
@ -322,15 +357,21 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031 air quality sensor found\n")
SCAN_SIMPLE_CASE(MPU6050_ADDR, MPU6050, "MPU6050 accelerometer found\n");
SCAN_SIMPLE_CASE(BMX160_ADDR, BMX160, "BMX160 accelerometer found\n");
SCAN_SIMPLE_CASE(BMA423_ADDR, BMA423, "BMA423 accelerometer found\n");
SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address);
SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555 I2C expander found\n");
SCAN_SIMPLE_CASE(VEML7700_ADDR, VEML7700, "VEML7700 light sensor found\n");
SCAN_SIMPLE_CASE(TSL25911_ADDR, TSL2591, "TSL2591 light sensor found\n");
SCAN_SIMPLE_CASE(OPT3001_ADDR, OPT3001, "OPT3001 light sensor found\n");
SCAN_SIMPLE_CASE(MLX90632_ADDR, MLX90632, "MLX90632 IR temp sensor found\n");
SCAN_SIMPLE_CASE(NAU7802_ADDR, NAU7802, "NAU7802 based scale found\n");
default:
LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address);
}
} else if (err == 4) {
LOG_ERROR("Unknown error at address 0x%x\n", addr);
LOG_ERROR("Unknown error at address 0x%x\n", addr.address);
}
// Check if a type was found for the enumerated device - save, if so
@ -341,6 +382,11 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
}
}
void ScanI2CTwoWire::scanPort(I2CPort port)
{
scanPort(port, nullptr, 0);
}
TwoWire *ScanI2CTwoWire::fetchI2CBus(ScanI2C::DeviceAddress address) const
{
if (address.port == ScanI2C::I2CPort::WIRE) {

View File

@ -16,6 +16,8 @@ class ScanI2CTwoWire : public ScanI2C
public:
void scanPort(ScanI2C::I2CPort) override;
void scanPort(ScanI2C::I2CPort, uint8_t *, uint8_t) override;
ScanI2C::FoundDevice find(ScanI2C::DeviceType) const override;
TwoWire *fetchI2CBus(ScanI2C::DeviceAddress) const;
@ -53,4 +55,4 @@ class ScanI2CTwoWire : public ScanI2C
uint16_t getRegisterValue(const RegisterLocation &, ResponseWidth) const;
DeviceType probeOLED(ScanI2C::DeviceAddress) const;
};
};

View File

@ -21,6 +21,19 @@
#define GPS_RESET_MODE HIGH
#endif
// How many minutes of sleep make it worthwhile to power-off the GPS
// Shorter than this, and GPS will only enter standby
// Affected by lock-time, and config.position.gps_update_interval
#ifndef GPS_STANDBY_THRESHOLD_MINUTES
#define GPS_STANDBY_THRESHOLD_MINUTES 15
#endif
// How many seconds of sleep make it worthwhile for the GPS to use powered-on standby
// Shorter than this, and we'll just wait instead
#ifndef GPS_IDLE_THRESHOLD_SECONDS
#define GPS_IDLE_THRESHOLD_SECONDS 10
#endif
#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
HardwareSerial *GPS::_serial_gps = &Serial1;
#else
@ -62,10 +75,10 @@ void GPS::CASChecksum(uint8_t *message, size_t length)
// Iterate over the payload as a series of uint32_t's and
// accumulate the cksum
uint32_t *payload = (uint32_t *)(message + 6);
uint32_t const *payload = (uint32_t *)(message + 6);
for (size_t i = 0; i < (length - 10) / 4; i++) {
uint32_t p = payload[i];
cksum += p;
uint32_t pl = payload[i];
cksum += pl;
}
// Place the checksum values in the message
@ -452,7 +465,7 @@ bool GPS::setup()
// Set the NEMA output messages
// Ask for only RMC and GGA
uint8_t fields[] = {CAS_NEMA_RMC, CAS_NEMA_GGA};
for (uint i = 0; i < sizeof(fields); i++) {
for (unsigned int i = 0; i < sizeof(fields); i++) {
// Construct a CAS-CFG-MSG packet
uint8_t cas_cfg_msg_packet[] = {0x4e, fields[i], 0x01, 0x00};
msglen = makeCASPacket(0x06, 0x01, sizeof(cas_cfg_msg_packet), cas_cfg_msg_packet);
@ -472,6 +485,9 @@ bool GPS::setup()
// Turn off GSV messages, we don't really care about which and where the sats are, maybe someday.
_serial_gps->write("$CFGMSG,0,3,0\r\n");
delay(250);
// Turn off GSA messages, TinyGPS++ doesn't use this message.
_serial_gps->write("$CFGMSG,0,2,0\r\n");
delay(250);
// Turn off NOTICE __TXT messages, these may provide Unicore some info but we don't care.
_serial_gps->write("$CFGMSG,6,0,0\r\n");
delay(250);
@ -764,7 +780,24 @@ GPS::~GPS()
void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime)
{
LOG_INFO("Setting GPS power=%d\n", on);
// Record the current powerState
if (on)
powerState = GPS_ACTIVE;
else if (!enabled) // User has disabled with triple press
powerState = GPS_OFF;
else if (sleepTime <= GPS_IDLE_THRESHOLD_SECONDS * 1000UL)
powerState = GPS_IDLE;
else if (standbyOnly)
powerState = GPS_STANDBY;
else
powerState = GPS_OFF;
LOG_DEBUG("GPS::powerState=%d\n", powerState);
// If the next update is due *really soon*, don't actually power off or enter standby. Just wait it out.
if (!on && powerState == GPS_IDLE)
return;
if (on) {
clearBuffer(); // drop any old data waiting in the buffer before re-enabling
if (en_gpio)
@ -858,45 +891,72 @@ void GPS::setConnected()
*
* calls sleep/wake
*/
void GPS::setAwake(bool on)
void GPS::setAwake(bool wantAwake)
{
if (isAwake != on) {
LOG_DEBUG("WANT GPS=%d\n", on);
isAwake = on;
if (!enabled) { // short circuit if the user has disabled GPS
setGPSPower(false, false, 0);
return;
}
if (on) {
// If user has disabled GPS, make sure it is off, not just in standby or idle
if (!wantAwake && !enabled && powerState != GPS_OFF) {
setGPSPower(false, false, 0);
return;
}
// If GPS power state needs to change
if ((wantAwake && powerState != GPS_ACTIVE) || (!wantAwake && powerState == GPS_ACTIVE)) {
LOG_DEBUG("WANT GPS=%d\n", wantAwake);
// Calculate how long it takes to get a GPS lock
if (wantAwake) {
// Record the time we start looking for a lock
lastWakeStartMsec = millis();
} else {
// Record by how much we missed our ideal target postion.gps_update_interval (for logging only)
// Need to calculate this before we update lastSleepStartMsec, to make the new prediction
int32_t lateByMsec = (int32_t)(millis() - lastSleepStartMsec) - (int32_t)getSleepTime();
// Record the time we finish looking for a lock
lastSleepStartMsec = millis();
if (GPSCycles == 1) { // Skipping initial lock time, as it will likely be much longer than average
averageLockTime = lastSleepStartMsec - lastWakeStartMsec;
} else if (GPSCycles > 1) {
averageLockTime += ((int32_t)(lastSleepStartMsec - lastWakeStartMsec) - averageLockTime) / (int32_t)GPSCycles;
// How long did it take to get GPS lock this time?
uint32_t lockTime = lastSleepStartMsec - lastWakeStartMsec;
// Update the lock-time prediction
// Used pre-emptively, attempting to hit target of gps.position_update_interval
switch (GPSCycles) {
case 0:
LOG_DEBUG("Initial GPS lock took %ds\n", lockTime / 1000);
break;
case 1:
predictedLockTime = lockTime; // Avoid slow ramp-up - start with a real value
LOG_DEBUG("GPS Lock took %ds\n", lockTime / 1000);
break;
default:
// Predict lock-time using exponential smoothing: respond slowly to changes
predictedLockTime = (lockTime * 0.2) + (predictedLockTime * 0.8); // Latest lock time has 20% weight on prediction
LOG_INFO("GPS Lock took %ds. %s by %ds. Next lock predicted to take %ds.\n", lockTime / 1000,
(lateByMsec > 0) ? "Late" : "Early", abs(lateByMsec) / 1000, predictedLockTime / 1000);
}
GPSCycles++;
LOG_DEBUG("GPS Lock took %d, average %d\n", (lastSleepStartMsec - lastWakeStartMsec) / 1000, averageLockTime / 1000);
}
if ((int32_t)getSleepTime() - averageLockTime >
15 * 60 * 1000) { // 15 minutes is probably long enough to make a complete poweroff worth it.
setGPSPower(on, false, getSleepTime() - averageLockTime);
return;
} else if ((int32_t)getSleepTime() - averageLockTime > 10000) { // 10 seconds is enough for standby
// How long to wait before attempting next GPS update
// Aims to hit position.gps_update_interval by using the lock-time prediction
uint32_t compensatedSleepTime = (getSleepTime() > predictedLockTime) ? (getSleepTime() - predictedLockTime) : 0;
// If long interval between updates: power off between updates
if (compensatedSleepTime > GPS_STANDBY_THRESHOLD_MINUTES * MS_IN_MINUTE) {
setGPSPower(wantAwake, false, getSleepTime() - predictedLockTime);
}
// If waking relatively frequently: don't power off. Would use more energy trying to reacquire lock each time
// We'll either use a "powered-on" standby, or just wait it out, depending on how soon the next update is due
// Will decide which inside setGPSPower method
else {
#ifdef GPS_UC6580
setGPSPower(on, false, getSleepTime() - averageLockTime);
setGPSPower(wantAwake, false, compensatedSleepTime);
#else
setGPSPower(on, true, getSleepTime() - averageLockTime);
setGPSPower(wantAwake, true, compensatedSleepTime);
#endif
return;
}
if (averageLockTime > 20000) {
averageLockTime -= 1000; // eventually want to sleep again.
}
if (on)
setGPSPower(true, true, 0); // make sure we don't have a fallthrough where GPS is stuck off
}
}
@ -1002,14 +1062,14 @@ int32_t GPS::runOnce()
uint32_t timeAsleep = now - lastSleepStartMsec;
auto sleepTime = getSleepTime();
if (!isAwake && (sleepTime != UINT32_MAX) &&
((timeAsleep > sleepTime) || (isInPowersave && timeAsleep > (sleepTime - averageLockTime)))) {
if (powerState != GPS_ACTIVE && (sleepTime != UINT32_MAX) &&
((timeAsleep > sleepTime) || (isInPowersave && timeAsleep > (sleepTime - predictedLockTime)))) {
// We now want to be awake - so wake up the GPS
setAwake(true);
}
// While we are awake
if (isAwake) {
if (powerState == GPS_ACTIVE) {
// LOG_DEBUG("looking for location\n");
// If we've already set time from the GPS, no need to ask the GPS
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
@ -1055,7 +1115,7 @@ int32_t GPS::runOnce()
// 9600bps is approx 1 byte per msec, so considering our buffer size we never need to wake more often than 200ms
// if not awake we can run super infrquently (once every 5 secs?) to see if we need to wake.
return isAwake ? GPS_THREAD_INTERVAL : 5000;
return (powerState == GPS_ACTIVE) ? GPS_THREAD_INTERVAL : 5000;
}
// clear the GPS rx buffer as quickly as possible
@ -1467,7 +1527,7 @@ bool GPS::lookForLocation()
#endif // GPS_EXTRAVERBOSE
// Is this a new point or are we re-reading the previous one?
if (!reader.location.isUpdated())
if (!reader.location.isUpdated() && !reader.altitude.isUpdated())
return false;
// check if a complete GPS solution set is available for reading
@ -1584,11 +1644,11 @@ bool GPS::hasFlow()
bool GPS::whileIdle()
{
uint charsInBuf = 0;
unsigned int charsInBuf = 0;
bool isValid = false;
if (!isAwake) {
if (powerState != GPS_ACTIVE) {
clearBuffer();
return isAwake;
return (powerState == GPS_ACTIVE);
}
#ifdef SERIAL_BUFFER_SIZE
if (_serial_gps->available() >= SERIAL_BUFFER_SIZE - 1) {
@ -1619,6 +1679,10 @@ bool GPS::whileIdle()
}
void GPS::enable()
{
// Clear the old lock-time prediction
GPSCycles = 0;
predictedLockTime = 0;
enabled = true;
setInterval(GPS_THREAD_INTERVAL);
setAwake(true);

View File

@ -38,6 +38,13 @@ typedef enum {
GNSS_RESPONSE_OK,
} GPS_RESPONSE;
enum GPSPowerState : uint8_t {
GPS_OFF = 0, // Physically powered off
GPS_ACTIVE = 1, // Awake and want a position
GPS_STANDBY = 2, // Physically powered on, but soft-sleeping
GPS_IDLE = 3, // Awake, but not wanting another position yet
};
// Generate a string representation of DOP
const char *getDOPString(uint32_t dop);
@ -66,7 +73,7 @@ class GPS : private concurrency::OSThread
uint32_t rx_gpio = 0;
uint32_t tx_gpio = 0;
uint32_t en_gpio = 0;
int32_t averageLockTime = 0;
uint32_t predictedLockTime = 0;
uint32_t GPSCycles = 0;
int speedSelect = 0;
@ -78,8 +85,6 @@ class GPS : private concurrency::OSThread
*/
bool hasValidLocation = false; // default to false, until we complete our first read
bool isAwake = false; // true if we want a location right now
bool isInPowersave = false;
bool shouldPublish = false; // If we've changed GPS state, this will force a publish the next loop()
@ -89,6 +94,8 @@ class GPS : private concurrency::OSThread
bool GPSInitFinished = false; // Init thread finished?
bool GPSInitStarted = false; // Init thread finished?
GPSPowerState powerState = GPS_OFF; // GPS_ACTIVE if we want a location right now
uint8_t numSatellites = 0;
CallbackObserver<GPS, void *> notifyDeepSleepObserver = CallbackObserver<GPS, void *>(this, &GPS::prepareDeepSleep);

View File

@ -486,3 +486,91 @@ std::shared_ptr<GeoCoord> GeoCoord::pointAtDistance(double bearing, double range
return std::make_shared<GeoCoord>(double(lat), double(lon), this->getAltitude());
}
/**
* Convert bearing to degrees
* @param bearing
* The bearing in string format
* @return Bearing in degrees
*/
uint GeoCoord::bearingToDegrees(const char *bearing)
{
if (strcmp(bearing, "N") == 0)
return 0;
else if (strcmp(bearing, "NNE") == 0)
return 22;
else if (strcmp(bearing, "NE") == 0)
return 45;
else if (strcmp(bearing, "ENE") == 0)
return 67;
else if (strcmp(bearing, "E") == 0)
return 90;
else if (strcmp(bearing, "ESE") == 0)
return 112;
else if (strcmp(bearing, "SE") == 0)
return 135;
else if (strcmp(bearing, "SSE") == 0)
return 157;
else if (strcmp(bearing, "S") == 0)
return 180;
else if (strcmp(bearing, "SSW") == 0)
return 202;
else if (strcmp(bearing, "SW") == 0)
return 225;
else if (strcmp(bearing, "WSW") == 0)
return 247;
else if (strcmp(bearing, "W") == 0)
return 270;
else if (strcmp(bearing, "WNW") == 0)
return 292;
else if (strcmp(bearing, "NW") == 0)
return 315;
else if (strcmp(bearing, "NNW") == 0)
return 337;
else
return 0;
}
/**
* Convert bearing to string
* @param degrees
* The bearing in degrees
* @return Bearing in string format
*/
const char *GeoCoord::degreesToBearing(uint degrees)
{
if (degrees >= 348 || degrees < 11)
return "N";
else if (degrees >= 11 && degrees < 34)
return "NNE";
else if (degrees >= 34 && degrees < 56)
return "NE";
else if (degrees >= 56 && degrees < 79)
return "ENE";
else if (degrees >= 79 && degrees < 101)
return "E";
else if (degrees >= 101 && degrees < 124)
return "ESE";
else if (degrees >= 124 && degrees < 146)
return "SE";
else if (degrees >= 146 && degrees < 169)
return "SSE";
else if (degrees >= 169 && degrees < 191)
return "S";
else if (degrees >= 191 && degrees < 214)
return "SSW";
else if (degrees >= 214 && degrees < 236)
return "SW";
else if (degrees >= 236 && degrees < 259)
return "WSW";
else if (degrees >= 259 && degrees < 281)
return "W";
else if (degrees >= 281 && degrees < 304)
return "WNW";
else if (degrees >= 304 && degrees < 326)
return "NW";
else if (degrees >= 326 && degrees < 348)
return "NNW";
else
return "N";
}

View File

@ -117,6 +117,8 @@ class GeoCoord
static float bearing(double lat1, double lon1, double lat2, double lon2);
static float rangeRadiansToMeters(double range_radians);
static float rangeMetersToRadians(double range_meters);
static uint bearingToDegrees(const char *bearing);
static const char *degreesToBearing(uint degrees);
// Point to point conversions
int32_t distanceTo(const GeoCoord &pointB);

View File

@ -96,13 +96,17 @@ void readFromRTC()
*
* If we haven't yet set our RTC this boot, set it from a GPS derived time
*/
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate)
{
static uint32_t lastSetMsec = 0;
uint32_t now = millis();
bool shouldSet;
if (q > currentQuality) {
if (forceUpdate) {
shouldSet = true;
LOG_DEBUG("Overriding current RTC quality (%s) with incoming time of RTC quality of %s\n", RtcName(currentQuality),
RtcName(q));
} else if (q > currentQuality) {
shouldSet = true;
LOG_DEBUG("Upgrading time to quality %s\n", RtcName(q));
} else if (q >= RTCQualityNTP && (now - lastSetMsec) > (12 * 60 * 60 * 1000UL)) {
@ -218,12 +222,11 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t)
*/
int32_t getTZOffset()
{
time_t now;
time_t now = getTime(false);
struct tm *gmt;
now = time(NULL);
gmt = gmtime(&now);
gmt->tm_isdst = -1;
return (int16_t)difftime(now, mktime(gmt));
return (int32_t)difftime(now, mktime(gmt));
}
/**
@ -261,4 +264,4 @@ time_t gm_mktime(struct tm *tm)
setenv("TZ", "UTC0", 1);
}
return res;
}
}

View File

@ -25,7 +25,7 @@ enum RTCQuality {
RTCQuality getRTCQuality();
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv);
bool perhapsSetRTC(RTCQuality q, const struct timeval *tv, bool forceUpdate = false);
bool perhapsSetRTC(RTCQuality q, struct tm &t);
/// Return a string name for the quality

View File

@ -206,14 +206,14 @@ const uint8_t GPS::_message_GLL[] = {
0x00 // Reserved
};
// Enable GSA. GSA - GPS DOP and active satellites, used for detailing the satellites used in the positioning and
// Disable GSA. GSA - GPS DOP and active satellites, used for detailing the satellites used in the positioning and
// the DOP (Dilution of Precision)
const uint8_t GPS::_message_GSA[] = {
0xF0, 0x02, // NMEA ID for GSA
0x00, // Rate for DDC
0x01, // Rate for UART1
0x00, // Rate for UART1
0x00, // Rate for UART2
0x01, // Rate for USB usefull for native linux
0x00, // Rate for USB usefull for native linux
0x00, // Rate for SPI
0x00 // Reserved
};
@ -319,6 +319,8 @@ const uint8_t GPS::_message_SAVE[] = {
// As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR.
// BBR will survive a restart, and power off for a while, but modules with small backup
// batteries or super caps will not retain the config for a long power off time.
// for all configurations using sleep / low power modes, V_BCKP needs to be hooked to permanent power for fast aquisition after
// sleep
// VALSET Commands for M10
// Please refer to the M10 Protocol Specification:
@ -327,40 +329,42 @@ const uint8_t GPS::_message_SAVE[] = {
// and:
// https://content.u-blox.com/sites/default/files/u-blox-M10-ROM-5.10_ReleaseNotes_UBX-22001426.pdf
// for interesting insights.
//
// Integration manual:
// https://content.u-blox.com/sites/default/files/documents/SAM-M10Q_IntegrationManual_UBX-22020019.pdf
// has details on low-power modes
/*
CFG-PM2 has been replaced by many CFG-PM commands
OPERATEMODE E1 2 (0 | 1 | 2)
POSUPDATEPERIOD U4 1000ms for M10 must be >= 5s try 5
ACQPERIOD U4 10 seems ok for M10 def ok
GRIDOFFSET U4 0 seems ok for M10 def ok
ONTIME U2 1 will try 1
MINACQTIME U1 0 will try 0 def ok
MAXACQTIME U1 stick with default of 0 def ok
DONOTENTEROFF L 1 stay at 1
WAITTIMEFIX L 1 stay with 1
UPDATEEPH L 1 changed to 1 for gps rework default is 1
EXTINTWAKE L 0 no ext ints
EXTINTBACKUP L 0 no ext ints
EXTINTINACTIVE L 0 no ext ints
EXTINTACTIVITY U4 0 no ext ints
LIMITPEAKCURRENT L 1 stay with 1
*/
// CFG-PMS has been removed
CFG-PMS has been removed
CFG-PM-OPERATEMODE E1 (0 | 1 | 2) -> 1 (PSMOO), because sporadic position updates are required instead of continous tracking <10s
(PSMCT) CFG-PM-POSUPDATEPERIOD U4 -> 0ms, no self-timed wakup because receiver power mode is controlled via "software standby
mode" by legacy UBX-RXM-PMREQ request CFG-PM-ACQPERIOD U4 -> 0ms, because receiver power mode is controlled via "software standby
mode" by legacy UBX-RXM-PMREQ request CFG-PM-ONTIME U4 -> 0ms, optional I guess CFG-PM-EXTINTBACKUP L -> 1, force receiver into
BACKUP mode when EXTINT (should be connected to GPS_EN_PIN) pin is "low"
This is required because the receiver never enters low power mode if microcontroller is in deep-sleep.
Maybe the changing UART_RX levels trigger a wakeup but even with UBX-RXM-PMREQ[12] = 0x00 (all external wakeup sources disabled)
the receivcer remains in aquisition state -> potentially a bug
Workaround: Control the EXTINT pin by the GPS_EN_PIN signal
As mentioned in the M10 operational issues down below, power save won't allow the use of BDS B1C.
CFG-SIGNAL-BDS_B1C_ENA L -> 0
// Ram layer config message:
// b5 62 06 8a 26 00 00 01 00 00 01 00 d0 20 02 02 00 d0 40 05 00 00 00 05 00 d0 30 01 00 08 00 d0 10 01 09 00 d0 10 01 10 00 d0
// 10 01 8b de
// 01 01 00 00 01 00 D0 20 01 02 00 D0 40 00 00 00 00 03 00 D0 40 00 00 00 00 05 00 D0 30 00 00 0D 00 D0 10 01
// BBR layer config message:
// b5 62 06 8a 26 00 00 02 00 00 01 00 d0 20 02 02 00 d0 40 05 00 00 00 05 00 d0 30 01 00 08 00 d0 10 01 09 00 d0 10 01 10 00 d0
// 10 01 8c 03
const uint8_t GPS::_message_VALSET_PM_RAM[] = {0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0xd0, 0x20, 0x02, 0x02, 0x00, 0xd0, 0x40,
0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0xd0, 0x30, 0x01, 0x00, 0x08, 0x00, 0xd0,
0x10, 0x01, 0x09, 0x00, 0xd0, 0x10, 0x01, 0x10, 0x00, 0xd0, 0x10, 0x01};
const uint8_t GPS::_message_VALSET_PM_BBR[] = {0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0xd0, 0x20, 0x02, 0x02, 0x00, 0xd0, 0x40,
0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0xd0, 0x30, 0x01, 0x00, 0x08, 0x00, 0xd0,
0x10, 0x01, 0x09, 0x00, 0xd0, 0x10, 0x01, 0x10, 0x00, 0xd0, 0x10, 0x01};
// 01 02 00 00 01 00 D0 20 01 02 00 D0 40 00 00 00 00 03 00 D0 40 00 00 00 00 05 00 D0 30 00 00 0D 00 D0 10 01
*/
const uint8_t GPS::_message_VALSET_PM_RAM[] = {0x01, 0x01, 0x00, 0x00, 0x0F, 0x00, 0x31, 0x10, 0x00, 0x01, 0x00, 0xD0, 0x20, 0x01,
0x02, 0x00, 0xD0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xD0, 0x40, 0x00, 0x00,
0x00, 0x00, 0x05, 0x00, 0xD0, 0x30, 0x00, 0x00, 0x0D, 0x00, 0xD0, 0x10, 0x01};
const uint8_t GPS::_message_VALSET_PM_BBR[] = {0x01, 0x02, 0x00, 0x00, 0x0F, 0x00, 0x31, 0x10, 0x00, 0x01, 0x00, 0xD0, 0x20, 0x01,
0x02, 0x00, 0xD0, 0x40, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0xD0, 0x40, 0x00, 0x00,
0x00, 0x00, 0x05, 0x00, 0xD0, 0x30, 0x00, 0x00, 0x0D, 0x00, 0xD0, 0x10, 0x01};
/*
CFG-ITFM replaced by 5 valset messages which can be combined into one for RAM and one for BBR
@ -402,23 +406,28 @@ const uint8_t GPS::_message_VALSET_DISABLE_NMEA_BBR[] = {0x00, 0x02, 0x00, 0x00,
// BBR layer config message:
// b5 62 06 8a 09 00 00 02 00 00 07 00 92 20 06 5a 58
// Turn NMEA GSA, GGA, RMC messages on:
// Ram layer config message:
// b5 62 06 8a 13 00 00 01 00 00 c0 00 91 20 01 bb 00 91 20 01 ac 00 91 20 01 e1 3b
// BBR layer config message:
// b5 62 06 8a 13 00 00 02 00 00 c0 00 91 20 01 bb 00 91 20 01 ac 00 91 20 01 e2 4d
// Turn NMEA GGA, RMC messages on:
// Layer config messages:
// RAM:
// b5 62 06 8a 0e 00 00 01 00 00 bb 00 91 20 01 ac 00 91 20 01 6a 8f
// BBR:
// b5 62 06 8a 0e 00 00 02 00 00 bb 00 91 20 01 ac 00 91 20 01 6b 9c
// FLASH:
// b5 62 06 8a 0e 00 00 04 00 00 bb 00 91 20 01 ac 00 91 20 01 6d b6
// Doing this for the FLASH layer isn't really required since we save the config to flash later
const uint8_t GPS::_message_VALSET_DISABLE_TXT_INFO_RAM[] = {0x00, 0x01, 0x00, 0x00, 0x07, 0x00, 0x92, 0x20, 0x03};
const uint8_t GPS::_message_VALSET_DISABLE_TXT_INFO_BBR[] = {0x00, 0x02, 0x00, 0x00, 0x07, 0x00, 0x92, 0x20, 0x03};
const uint8_t GPS::_message_VALSET_ENABLE_NMEA_RAM[] = {0x00, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x91, 0x20, 0x01, 0xbb,
0x00, 0x91, 0x20, 0x01, 0xac, 0x00, 0x91, 0x20, 0x01};
const uint8_t GPS::_message_VALSET_ENABLE_NMEA_BBR[] = {0x00, 0x02, 0x00, 0x00, 0xc0, 0x00, 0x91, 0x20, 0x01, 0xbb,
0x00, 0x91, 0x20, 0x01, 0xac, 0x00, 0x91, 0x20, 0x01};
const uint8_t GPS::_message_VALSET_ENABLE_NMEA_RAM[] = {0x00, 0x01, 0x00, 0x00, 0xbb, 0x00, 0x91,
0x20, 0x01, 0xac, 0x00, 0x91, 0x20, 0x01};
const uint8_t GPS::_message_VALSET_ENABLE_NMEA_BBR[] = {0x00, 0x02, 0x00, 0x00, 0xbb, 0x00, 0x91,
0x20, 0x01, 0xac, 0x00, 0x91, 0x20, 0x01};
const uint8_t GPS::_message_VALSET_DISABLE_SBAS_RAM[] = {0x00, 0x01, 0x00, 0x00, 0x20, 0x00, 0x31,
0x10, 0x00, 0x05, 0x00, 0x31, 0x10, 0x00};
const uint8_t GPS::_message_VALSET_DISABLE_SBAS_BBR[] = {0x00, 0x02, 0x00, 0x00, 0x20, 0x00, 0x31,
0x10, 0x00, 0x05, 0x00, 0x31, 0x10, 0x00};
/*
Operational issues with the M10:

View File

@ -62,12 +62,19 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
return false;
// FIXME - only draw bits have changed (use backbuf similar to the other displays)
const bool flipped = config.display.flip_screen;
for (uint32_t y = 0; y < displayHeight; y++) {
for (uint32_t x = 0; x < displayWidth; x++) {
// get src pixel in the page based ordering the OLED lib uses FIXME, super inefficient
auto b = buffer[x + (y / 8) * displayWidth];
auto isset = b & (1 << (y & 7));
adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
// Handle flip here, rather than with setRotation(),
// Avoids issues when display width is not a multiple of 8
if (flipped)
adafruitDisplay->drawPixel((displayWidth - 1) - x, (displayHeight - 1) - y, isset ? GxEPD_BLACK : GxEPD_WHITE);
else
adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
}
}

View File

@ -534,6 +534,10 @@ void EInkDynamicDisplay::checkBusyAsyncRefresh()
return;
}
// Async refresh appears to have stopped, but wasn't caught by onNotify()
else
pollAsyncRefresh(); // Check (and terminate) the async refresh manually
}
// Hold control while an async refresh runs

View File

@ -109,6 +109,7 @@ class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWo
refreshTypes currentConfig = FULL; // Which refresh type is GxEPD2 currently configured for
// Optional - track ghosting, pixel by pixel
// May 2024: no longer used by any display. Kept for possible future use.
#ifdef EINK_LIMIT_GHOSTING_PX
void countGhostPixels(); // Count any pixels which have moved from black to white since last full-refresh
void checkExcessiveGhosting(); // Check if ghosting exceeds defined limit

4
src/graphics/NeoPixel.h Normal file
View File

@ -0,0 +1,4 @@
#ifdef HAS_NEOPIXEL
#include <Adafruit_NeoPixel.h>
extern Adafruit_NeoPixel pixels;
#endif

View File

@ -0,0 +1,4 @@
struct PointStruct {
int x;
int y;
};

View File

@ -94,6 +94,11 @@ std::vector<MeshModule *> moduleFrames;
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
static char ourId[5];
// vector where symbols (string) are displayed in bottom corner of display.
std::vector<std::string> functionSymbals;
// string displayed in bottom right corner of display. Created from elements in functionSymbals vector
std::string functionSymbalString = "";
#if HAS_GPS
// GeoCoord object for the screen
GeoCoord geoCoord;
@ -260,6 +265,42 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i
#endif
}
// draw overlay in bottom right corner of screen to show when notifications are muted or modifier key is active
static void drawFunctionOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
{
// LOG_DEBUG("Drawing function overlay\n");
if (functionSymbals.begin() != functionSymbals.end()) {
char buf[64];
display->setFont(FONT_SMALL);
snprintf(buf, sizeof(buf), "%s", functionSymbalString.c_str());
display->drawString(SCREEN_WIDTH - display->getStringWidth(buf), SCREEN_HEIGHT - FONT_HEIGHT_SMALL, buf);
}
}
/// Check if the display can render a string (detect special chars; emoji)
static bool haveGlyphs(const char *str)
{
#if defined(OLED_UA) || defined(OLED_RU)
// Don't want to make any assumptions about custom language support
return true;
#endif
// Check each character with the lookup function for the OLED library
// We're not really meant to use this directly..
bool have = true;
for (uint16_t i = 0; i < strlen(str); i++) {
uint8_t result = Screen::customFontTableLookup((uint8_t)str[i]);
// If font doesn't support a character, it is substituted for ¿
if (result == 191 && (uint8_t)str[i] != 191) {
have = false;
break;
}
}
LOG_DEBUG("haveGlyphs=%d\n", have);
return have;
}
#ifdef USE_EINK
/// Used on eink displays while in deep sleep
static void drawDeepSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
@ -284,14 +325,15 @@ static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *sta
display->setTextAlignment(TEXT_ALIGN_LEFT);
const char *pauseText = "Screen Paused";
const char *idText = owner.short_name;
const bool useId = haveGlyphs(idText); // This bool is used to hide the idText box if we can't render the short name
constexpr uint16_t padding = 5;
constexpr uint8_t dividerGap = 1;
constexpr uint8_t imprecision = 5; // How far the box origins can drift from center. Combat burn-in.
// Dimensions
const uint16_t idTextWidth = display->getStringWidth(idText, strlen(idText));
const uint16_t idTextWidth = display->getStringWidth(idText, strlen(idText), true); // "true": handle utf8 chars
const uint16_t pauseTextWidth = display->getStringWidth(pauseText, strlen(pauseText));
const uint16_t boxWidth = padding + idTextWidth + padding + padding + pauseTextWidth + padding;
const uint16_t boxWidth = padding + (useId ? idTextWidth + padding + padding : 0) + pauseTextWidth + padding;
const uint16_t boxHeight = padding + FONT_HEIGHT_SMALL + padding;
// Position
@ -301,7 +343,7 @@ static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *sta
const int16_t boxBottom = boxTop + boxHeight - 1;
const int16_t idTextLeft = boxLeft + padding;
const int16_t idTextTop = boxTop + padding;
const int16_t pauseTextLeft = boxLeft + padding + idTextWidth + padding + padding;
const int16_t pauseTextLeft = boxLeft + (useId ? padding + idTextWidth + padding : 0) + padding;
const int16_t pauseTextTop = boxTop + padding;
const int16_t dividerX = boxLeft + padding + idTextWidth + padding;
const int16_t dividerTop = boxTop + 1 + dividerGap;
@ -314,12 +356,14 @@ static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *sta
display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
// Draw: Text
display->drawString(idTextLeft, idTextTop, idText);
if (useId)
display->drawString(idTextLeft, idTextTop, idText);
display->drawString(pauseTextLeft, pauseTextTop, pauseText);
display->drawString(pauseTextLeft + 1, pauseTextTop, pauseText); // Faux bold
// Draw: divider
display->drawLine(dividerX, dividerTop, dividerX, dividerBottom);
if (useId)
display->drawLine(dividerX, dividerTop, dividerX, dividerBottom);
}
#endif
@ -402,6 +446,536 @@ static bool shouldDrawMessage(const meshtastic_MeshPacket *packet)
return packet->from != 0 && !moduleConfig.store_forward.enabled;
}
// Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage.
static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, const PowerStatus *powerStatus)
{
static const uint8_t powerBar[3] = {0x81, 0xBD, 0xBD};
static const uint8_t lightning[8] = {0xA1, 0xA1, 0xA5, 0xAD, 0xB5, 0xA5, 0x85, 0x85};
// Clear the bar area on the battery image
for (int i = 1; i < 14; i++) {
imgBuffer[i] = 0x81;
}
// If charging, draw a charging indicator
if (powerStatus->getIsCharging()) {
memcpy(imgBuffer + 3, lightning, 8);
// If not charging, Draw power bars
} else {
for (int i = 0; i < 4; i++) {
if (powerStatus->getBatteryChargePercent() >= 25 * i)
memcpy(imgBuffer + 1 + (i * 3), powerBar, 3);
}
}
display->drawFastImage(x, y, 16, 8, imgBuffer);
}
#ifdef T_WATCH_S3
void Screen::drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode, float scale)
{
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
if (digitalMode) {
uint16_t radius = (segmentWidth + (segmentHeight * 2) + 4) / 2;
uint16_t centerX = (x + segmentHeight + 2) + (radius / 2);
uint16_t centerY = (y + segmentHeight + 2) + (radius / 2);
display->drawCircle(centerX, centerY, radius);
display->drawCircle(centerX, centerY, radius + 1);
display->drawLine(centerX, centerY, centerX, centerY - radius + 3);
display->drawLine(centerX, centerY, centerX + radius - 3, centerY);
} else {
uint16_t segmentOneX = x + segmentHeight + 2;
uint16_t segmentOneY = y;
uint16_t segmentTwoX = segmentOneX + segmentWidth + 2;
uint16_t segmentTwoY = segmentOneY + segmentHeight + 2;
uint16_t segmentThreeX = segmentOneX;
uint16_t segmentThreeY = segmentTwoY + segmentWidth + 2;
uint16_t segmentFourX = x;
uint16_t segmentFourY = y + segmentHeight + 2;
drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight);
drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight);
drawHorizontalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight);
drawVerticalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight);
}
}
// Draw a digital clock
void Screen::drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_LEFT);
drawBattery(display, x, y + 7, imgBattery, powerStatus);
if (powerStatus->getHasBattery()) {
String batteryPercent = String(powerStatus->getBatteryChargePercent()) + "%";
display->setFont(FONT_SMALL);
display->drawString(x + 20, y + 2, batteryPercent);
}
if (nimbleBluetooth->isConnected()) {
drawBluetoothConnectedIcon(display, display->getWidth() - 18, y + 2);
}
drawWatchFaceToggleButton(display, display->getWidth() - 36, display->getHeight() - 36, screen->digitalWatchFace, 1);
display->setColor(OLEDDISPLAY_COLOR::WHITE);
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
int hour = hms / SEC_PER_HOUR;
int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
int second = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
hour = hour > 12 ? hour - 12 : hour;
if (hour == 0) {
hour = 12;
}
// hours string
String hourString = String(hour);
// minutes string
String minuteString = minute < 10 ? "0" + String(minute) : String(minute);
String timeString = hourString + ":" + minuteString;
// seconds string
String secondString = second < 10 ? "0" + String(second) : String(second);
float scale = 1.5;
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
// calculate hours:minutes string width
uint16_t timeStringWidth = timeString.length() * 5;
for (uint8_t i = 0; i < timeString.length(); i++) {
String character = String(timeString[i]);
if (character == ":") {
timeStringWidth += segmentHeight;
} else {
timeStringWidth += segmentWidth + (segmentHeight * 2) + 4;
}
}
// calculate seconds string width
uint16_t secondStringWidth = (secondString.length() * 12) + 4;
// sum these to get total string width
uint16_t totalWidth = timeStringWidth + secondStringWidth;
uint16_t hourMinuteTextX = (display->getWidth() / 2) - (totalWidth / 2);
uint16_t startingHourMinuteTextX = hourMinuteTextX;
uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2);
// iterate over characters in hours:minutes string and draw segmented characters
for (uint8_t i = 0; i < timeString.length(); i++) {
String character = String(timeString[i]);
if (character == ":") {
drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale);
hourMinuteTextX += segmentHeight + 6;
} else {
drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character.toInt(), scale);
hourMinuteTextX += segmentWidth + (segmentHeight * 2) + 4;
}
hourMinuteTextX += 5;
}
// draw seconds string
display->setFont(FONT_MEDIUM);
display->drawString(startingHourMinuteTextX + timeStringWidth + 4,
(display->getHeight() - hourMinuteTextY) - FONT_HEIGHT_MEDIUM + 6, secondString);
}
}
void Screen::drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale)
{
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
uint16_t cellHeight = (segmentWidth * 2) + (segmentHeight * 3) + 8;
uint16_t topAndBottomX = x + (4 * scale);
uint16_t quarterCellHeight = cellHeight / 4;
uint16_t topY = y + quarterCellHeight;
uint16_t bottomY = y + (quarterCellHeight * 3);
display->fillRect(topAndBottomX, topY, segmentHeight, segmentHeight);
display->fillRect(topAndBottomX, bottomY, segmentHeight, segmentHeight);
}
void Screen::drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale)
{
// the numbers 0-9, each expressed as an array of seven boolean (0|1) values encoding the on/off state of
// segment {innerIndex + 1}
// e.g., to display the numeral '0', segments 1-6 are on, and segment 7 is off.
uint8_t numbers[10][7] = {
{1, 1, 1, 1, 1, 1, 0}, // 0 Display segment key
{0, 1, 1, 0, 0, 0, 0}, // 1 1
{1, 1, 0, 1, 1, 0, 1}, // 2 ___
{1, 1, 1, 1, 0, 0, 1}, // 3 6 | | 2
{0, 1, 1, 0, 0, 1, 1}, // 4 |_7̲_|
{1, 0, 1, 1, 0, 1, 1}, // 5 5 | | 3
{1, 0, 1, 1, 1, 1, 1}, // 6 |___|
{1, 1, 1, 0, 0, 1, 0}, // 7
{1, 1, 1, 1, 1, 1, 1}, // 8 4
{1, 1, 1, 1, 0, 1, 1}, // 9
};
// the width and height of each segment's central rectangle:
// _____________________
// ⋰| (only this part, |⋱
// ⋰ | not including | ⋱
// ⋱ | the triangles | ⋰
// ⋱| on the ends) |⋰
// ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
uint16_t segmentWidth = SEGMENT_WIDTH * scale;
uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
// segment x and y coordinates
uint16_t segmentOneX = x + segmentHeight + 2;
uint16_t segmentOneY = y;
uint16_t segmentTwoX = segmentOneX + segmentWidth + 2;
uint16_t segmentTwoY = segmentOneY + segmentHeight + 2;
uint16_t segmentThreeX = segmentTwoX;
uint16_t segmentThreeY = segmentTwoY + segmentWidth + 2 + segmentHeight + 2;
uint16_t segmentFourX = segmentOneX;
uint16_t segmentFourY = segmentThreeY + segmentWidth + 2;
uint16_t segmentFiveX = x;
uint16_t segmentFiveY = segmentThreeY;
uint16_t segmentSixX = x;
uint16_t segmentSixY = segmentTwoY;
uint16_t segmentSevenX = segmentOneX;
uint16_t segmentSevenY = segmentTwoY + segmentWidth + 2;
if (numbers[number][0]) {
drawHorizontalSegment(display, segmentOneX, segmentOneY, segmentWidth, segmentHeight);
}
if (numbers[number][1]) {
drawVerticalSegment(display, segmentTwoX, segmentTwoY, segmentWidth, segmentHeight);
}
if (numbers[number][2]) {
drawVerticalSegment(display, segmentThreeX, segmentThreeY, segmentWidth, segmentHeight);
}
if (numbers[number][3]) {
drawHorizontalSegment(display, segmentFourX, segmentFourY, segmentWidth, segmentHeight);
}
if (numbers[number][4]) {
drawVerticalSegment(display, segmentFiveX, segmentFiveY, segmentWidth, segmentHeight);
}
if (numbers[number][5]) {
drawVerticalSegment(display, segmentSixX, segmentSixY, segmentWidth, segmentHeight);
}
if (numbers[number][6]) {
drawHorizontalSegment(display, segmentSevenX, segmentSevenY, segmentWidth, segmentHeight);
}
}
void Screen::drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height)
{
int halfHeight = height / 2;
// draw central rectangle
display->fillRect(x, y, width, height);
// draw end triangles
display->fillTriangle(x, y, x, y + height - 1, x - halfHeight, y + halfHeight);
display->fillTriangle(x + width, y, x + width + halfHeight, y + halfHeight, x + width, y + height - 1);
}
void Screen::drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int height)
{
int halfHeight = height / 2;
// draw central rectangle
display->fillRect(x, y, height, width);
// draw end triangles
display->fillTriangle(x + halfHeight, y - halfHeight, x + height - 1, y, x, y);
display->fillTriangle(x, y + width, x + height - 1, y + width, x + halfHeight, y + width + halfHeight);
}
void Screen::drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y)
{
display->drawFastImage(x, y, 18, 14, bluetoothConnectedIcon);
}
// Draw an analog clock
void Screen::drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
display->setTextAlignment(TEXT_ALIGN_LEFT);
drawBattery(display, x, y + 7, imgBattery, powerStatus);
if (powerStatus->getHasBattery()) {
String batteryPercent = String(powerStatus->getBatteryChargePercent()) + "%";
display->setFont(FONT_SMALL);
display->drawString(x + 20, y + 2, batteryPercent);
}
if (nimbleBluetooth->isConnected()) {
drawBluetoothConnectedIcon(display, display->getWidth() - 18, y + 2);
}
drawWatchFaceToggleButton(display, display->getWidth() - 36, display->getHeight() - 36, screen->digitalWatchFace, 1);
// clock face center coordinates
int16_t centerX = display->getWidth() / 2;
int16_t centerY = display->getHeight() / 2;
// clock face radius
int16_t radius = (display->getWidth() / 2) * 0.8;
// noon (0 deg) coordinates (outermost circle)
int16_t noonX = centerX;
int16_t noonY = centerY - radius;
// second hand radius and y coordinate (outermost circle)
int16_t secondHandNoonY = noonY + 1;
// tick mark outer y coordinate; (first nested circle)
int16_t tickMarkOuterNoonY = secondHandNoonY;
// seconds tick mark inner y coordinate; (second nested circle)
double secondsTickMarkInnerNoonY = (double)noonY + 8;
// hours tick mark inner y coordinate; (third nested circle)
double hoursTickMarkInnerNoonY = (double)noonY + 16;
// minute hand y coordinate
int16_t minuteHandNoonY = secondsTickMarkInnerNoonY + 4;
// hour string y coordinate
int16_t hourStringNoonY = minuteHandNoonY + 18;
// hour hand radius and y coordinate
int16_t hourHandRadius = radius * 0.55;
int16_t hourHandNoonY = centerY - hourHandRadius;
display->setColor(OLEDDISPLAY_COLOR::WHITE);
display->drawCircle(centerX, centerY, radius);
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
// Tear apart hms into h:m:s
int hour = hms / SEC_PER_HOUR;
int minute = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
int second = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
hour = hour > 12 ? hour - 12 : hour;
int16_t degreesPerHour = 30;
int16_t degreesPerMinuteOrSecond = 6;
double hourBaseAngle = hour * degreesPerHour;
double hourAngleOffset = ((double)minute / 60) * degreesPerHour;
double hourAngle = radians(hourBaseAngle + hourAngleOffset);
double minuteBaseAngle = minute * degreesPerMinuteOrSecond;
double minuteAngleOffset = ((double)second / 60) * degreesPerMinuteOrSecond;
double minuteAngle = radians(minuteBaseAngle + minuteAngleOffset);
double secondAngle = radians(second * degreesPerMinuteOrSecond);
double hourX = sin(-hourAngle) * (hourHandNoonY - centerY) + noonX;
double hourY = cos(-hourAngle) * (hourHandNoonY - centerY) + centerY;
double minuteX = sin(-minuteAngle) * (minuteHandNoonY - centerY) + noonX;
double minuteY = cos(-minuteAngle) * (minuteHandNoonY - centerY) + centerY;
double secondX = sin(-secondAngle) * (secondHandNoonY - centerY) + noonX;
double secondY = cos(-secondAngle) * (secondHandNoonY - centerY) + centerY;
display->setFont(FONT_MEDIUM);
// draw minute and hour tick marks and hour numbers
for (uint16_t angle = 0; angle < 360; angle += 6) {
double angleInRadians = radians(angle);
double sineAngleInRadians = sin(-angleInRadians);
double cosineAngleInRadians = cos(-angleInRadians);
double endX = sineAngleInRadians * (tickMarkOuterNoonY - centerY) + noonX;
double endY = cosineAngleInRadians * (tickMarkOuterNoonY - centerY) + centerY;
if (angle % degreesPerHour == 0) {
double startX = sineAngleInRadians * (hoursTickMarkInnerNoonY - centerY) + noonX;
double startY = cosineAngleInRadians * (hoursTickMarkInnerNoonY - centerY) + centerY;
// draw hour tick mark
display->drawLine(startX, startY, endX, endY);
static char buffer[2];
uint8_t hourInt = (angle / 30);
if (hourInt == 0) {
hourInt = 12;
}
// hour number x offset needs to be adjusted for some cases
int8_t hourStringXOffset;
int8_t hourStringYOffset = 13;
switch (hourInt) {
case 3:
hourStringXOffset = 5;
break;
case 9:
hourStringXOffset = 7;
break;
case 10:
case 11:
hourStringXOffset = 8;
break;
case 12:
hourStringXOffset = 13;
break;
default:
hourStringXOffset = 6;
break;
}
double hourStringX = (sineAngleInRadians * (hourStringNoonY - centerY) + noonX) - hourStringXOffset;
double hourStringY = (cosineAngleInRadians * (hourStringNoonY - centerY) + centerY) - hourStringYOffset;
// draw hour number
display->drawStringf(hourStringX, hourStringY, buffer, "%d", hourInt);
}
if (angle % degreesPerMinuteOrSecond == 0) {
double startX = sineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + noonX;
double startY = cosineAngleInRadians * (secondsTickMarkInnerNoonY - centerY) + centerY;
// draw minute tick mark
display->drawLine(startX, startY, endX, endY);
}
}
// draw hour hand
display->drawLine(centerX, centerY, hourX, hourY);
// draw minute hand
display->drawLine(centerX, centerY, minuteX, minuteY);
// draw second hand
display->drawLine(centerX, centerY, secondX, secondY);
}
}
#endif
// Get an absolute time from "seconds ago" info. Returns false if no valid timestamp possible
bool deltaToTimestamp(uint32_t secondsAgo, uint8_t *hours, uint8_t *minutes, int32_t *daysAgo)
{
// Cache the result - avoid frequent recalculation
static uint8_t hoursCached = 0, minutesCached = 0;
static uint32_t daysAgoCached = 0;
static uint32_t secondsAgoCached = 0;
static bool validCached = false;
// Abort: if timezone not set
if (strlen(config.device.tzdef) == 0) {
validCached = false;
return validCached;
}
// Abort: if invalid pointers passed
if (hours == nullptr || minutes == nullptr || daysAgo == nullptr) {
validCached = false;
return validCached;
}
// Abort: if time seems invalid.. (> 6 months ago, probably seen before RTC set)
if (secondsAgo > SEC_PER_DAY * 30UL * 6) {
validCached = false;
return validCached;
}
// If repeated request, don't bother recalculating
if (secondsAgo - secondsAgoCached < 60 && secondsAgoCached != 0) {
if (validCached) {
*hours = hoursCached;
*minutes = minutesCached;
*daysAgo = daysAgoCached;
}
return validCached;
}
// Get local time
uint32_t secondsRTC = getValidTime(RTCQuality::RTCQualityDevice, true); // Get local time
// Abort: if RTC not set
if (!secondsRTC) {
validCached = false;
return validCached;
}
// Get absolute time when last seen
uint32_t secondsSeenAt = secondsRTC - secondsAgo;
// Calculate daysAgo
*daysAgo = (secondsRTC / SEC_PER_DAY) - (secondsSeenAt / SEC_PER_DAY); // How many "midnights" have passed
// Get seconds since midnight
uint32_t hms = (secondsRTC - secondsAgo) % SEC_PER_DAY;
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
// Tear apart hms into hours and minutes
*hours = hms / SEC_PER_HOUR;
*minutes = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
// Cache the result
daysAgoCached = *daysAgo;
hoursCached = *hours;
minutesCached = *minutes;
secondsAgoCached = secondsAgo;
validCached = true;
return validCached;
}
/// Draw the last text message we received
static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
@ -423,22 +997,98 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
display->setColor(BLACK);
}
// For time delta
uint32_t seconds = sinceReceived(&mp);
uint32_t minutes = seconds / 60;
uint32_t hours = minutes / 60;
uint32_t days = hours / 24;
if (config.display.heading_bold) {
display->drawStringf(1 + x, 0 + y, tempBuf, "%s ago from %s",
screen->drawTimeDelta(days, hours, minutes, seconds).c_str(),
(node && node->has_user) ? node->user.short_name : "???");
// For timestamp
uint8_t timestampHours, timestampMinutes;
int32_t daysAgo;
bool useTimestamp = deltaToTimestamp(seconds, &timestampHours, &timestampMinutes, &daysAgo);
// If bold, draw twice, shifting right by one pixel
for (uint8_t xOff = 0; xOff <= (config.display.heading_bold ? 1 : 0); xOff++) {
// Show a timestamp if received today, but longer than 15 minutes ago
if (useTimestamp && minutes >= 15 && daysAgo == 0) {
display->drawStringf(xOff + x, 0 + y, tempBuf, "At %02hu:%02hu from %s", timestampHours, timestampMinutes,
(node && node->has_user) ? node->user.short_name : "???");
}
// Timestamp yesterday (if display is wide enough)
else if (useTimestamp && daysAgo == 1 && display->width() >= 200) {
display->drawStringf(xOff + x, 0 + y, tempBuf, "Yesterday %02hu:%02hu from %s", timestampHours, timestampMinutes,
(node && node->has_user) ? node->user.short_name : "???");
}
// Otherwise, show a time delta
else {
display->drawStringf(xOff + x, 0 + y, tempBuf, "%s ago from %s",
screen->drawTimeDelta(days, hours, minutes, seconds).c_str(),
(node && node->has_user) ? node->user.short_name : "???");
}
}
display->drawStringf(0 + x, 0 + y, tempBuf, "%s ago from %s", screen->drawTimeDelta(days, hours, minutes, seconds).c_str(),
(node && node->has_user) ? node->user.short_name : "???");
display->setColor(WHITE);
#ifndef EXCLUDE_EMOJI
if (strcmp(reinterpret_cast<const char *>(mp.decoded.payload.bytes), u8"\U0001F44D") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height,
thumbup);
} else if (strcmp(reinterpret_cast<const char *>(mp.decoded.payload.bytes), u8"\U0001F44E") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - thumbs_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - thumbs_height) / 2 + 2 + 5, thumbs_width, thumbs_height,
thumbdown);
} else if (strcmp(reinterpret_cast<const char *>(mp.decoded.payload.bytes), u8"") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - question_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - question_height) / 2 + 2 + 5, question_width, question_height,
question);
} else if (strcmp(reinterpret_cast<const char *>(mp.decoded.payload.bytes), u8"‼️") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - bang_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - bang_height) / 2 + 2 + 5,
bang_width, bang_height, bang);
} else if (strcmp(reinterpret_cast<const char *>(mp.decoded.payload.bytes), u8"\U0001F4A9") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - poo_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - poo_height) / 2 + 2 + 5,
poo_width, poo_height, poo);
} else if (strcmp(reinterpret_cast<const char *>(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<const char *>(mp.decoded.payload.bytes), u8"\U0001F44B") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - wave_icon_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - wave_icon_height) / 2 + 2 + 5, wave_icon_width,
wave_icon_height, wave_icon);
} else if (strcmp(reinterpret_cast<const char *>(mp.decoded.payload.bytes), u8"\U0001F920") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - cowboy_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - cowboy_height) / 2 + 2 + 5, cowboy_width, cowboy_height,
cowboy);
} else if (strcmp(reinterpret_cast<const char *>(mp.decoded.payload.bytes), u8"\U0001F42D") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - deadmau5_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - deadmau5_height) / 2 + 2 + 5, deadmau5_width, deadmau5_height,
deadmau5);
} else if (strcmp(reinterpret_cast<const char *>(mp.decoded.payload.bytes), u8"\xE2\x98\x80\xEF\xB8\x8F") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - sun_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - sun_height) / 2 + 2 + 5,
sun_width, sun_height, sun);
} else if (strcmp(reinterpret_cast<const char *>(mp.decoded.payload.bytes), u8"\u2614") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - rain_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - rain_height) / 2 + 2 + 10,
rain_width, rain_height, rain);
} else if (strcmp(reinterpret_cast<const char *>(mp.decoded.payload.bytes), u8"☁️") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - cloud_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - cloud_height) / 2 + 2 + 5, cloud_width, cloud_height, cloud);
} else if (strcmp(reinterpret_cast<const char *>(mp.decoded.payload.bytes), u8"🌫️") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - fog_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - fog_height) / 2 + 2 + 5,
fog_width, fog_height, fog);
} else if (strcmp(reinterpret_cast<const char *>(mp.decoded.payload.bytes), u8"\xf0\x9f\x98\x88") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - devil_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - devil_height) / 2 + 2 + 5, devil_width, devil_height, devil);
} else if (strcmp(reinterpret_cast<const char *>(mp.decoded.payload.bytes), u8"♥️") == 0) {
display->drawXbm(x + (SCREEN_WIDTH - heart_width) / 2,
y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - heart_height) / 2 + 2 + 5, heart_width, heart_height, heart);
} else {
snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes);
display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf);
}
#else
snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes);
display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf);
#endif
}
/// Draw the last waypoint we received
@ -501,28 +1151,6 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char *
}
}
// Draw power bars or a charging indicator on an image of a battery, determined by battery charge voltage or percentage.
static void drawBattery(OLEDDisplay *display, int16_t x, int16_t y, uint8_t *imgBuffer, const PowerStatus *powerStatus)
{
static const uint8_t powerBar[3] = {0x81, 0xBD, 0xBD};
static const uint8_t lightning[8] = {0xA1, 0xA1, 0xA5, 0xAD, 0xB5, 0xA5, 0x85, 0x85};
// Clear the bar area on the battery image
for (int i = 1; i < 14; i++) {
imgBuffer[i] = 0x81;
}
// If charging, draw a charging indicator
if (powerStatus->getIsCharging()) {
memcpy(imgBuffer + 3, lightning, 8);
// If not charging, Draw power bars
} else {
for (int i = 0; i < 4; i++) {
if (powerStatus->getBatteryChargePercent() >= 25 * i)
memcpy(imgBuffer + 1 + (i * 3), powerBar, 3);
}
}
display->drawFastImage(x, y, 16, 8, imgBuffer);
}
// Draw nodes status
static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStatus *nodeStatus)
{
@ -858,23 +1486,42 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
const char *username = node->has_user ? node->user.long_name : "Unknown Name";
static char signalStr[20];
snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100));
// section here to choose whether to display hops away rather than signal strength if more than 0 hops away.
if (node->hops_away > 0) {
snprintf(signalStr, sizeof(signalStr), "Hops Away: %d", node->hops_away);
} else {
snprintf(signalStr, sizeof(signalStr), "Signal: %d%%", clamp((int)((node->snr + 10) * 5), 0, 100));
}
uint32_t agoSecs = sinceLastSeen(node);
static char lastStr[20];
// Use an absolute timestamp in some cases.
// Particularly useful with E-Ink displays. Static UI, fewer refreshes.
uint8_t timestampHours, timestampMinutes;
int32_t daysAgo;
bool useTimestamp = deltaToTimestamp(agoSecs, &timestampHours, &timestampMinutes, &daysAgo);
if (agoSecs < 120) // last 2 mins?
snprintf(lastStr, sizeof(lastStr), "%u seconds ago", agoSecs);
// -- if suitable for timestamp --
else if (useTimestamp && agoSecs < 15 * SECONDS_IN_MINUTE) // Last 15 minutes
snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / SECONDS_IN_MINUTE);
else if (useTimestamp && daysAgo == 0) // Today
snprintf(lastStr, sizeof(lastStr), "Last seen: %02u:%02u", (unsigned int)timestampHours, (unsigned int)timestampMinutes);
else if (useTimestamp && daysAgo == 1) // Yesterday
snprintf(lastStr, sizeof(lastStr), "Seen yesterday");
else if (useTimestamp && daysAgo > 1) // Last six months (capped by deltaToTimestamp method)
snprintf(lastStr, sizeof(lastStr), "%li days ago", (long)daysAgo);
// -- if using time delta instead --
else if (agoSecs < 120 * 60) // last 2 hrs
snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / 60);
else {
// Only show hours ago if it's been less than 6 months. Otherwise, we may have bad
// data.
if ((agoSecs / 60 / 60) < (hours_in_month * 6)) {
snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60);
} else {
snprintf(lastStr, sizeof(lastStr), "unknown age");
}
}
// Only show hours ago if it's been less than 6 months. Otherwise, we may have bad data.
else if ((agoSecs / 60 / 60) < (hours_in_month * 6))
snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60);
else
snprintf(lastStr, sizeof(lastStr), "unknown age");
static char distStr[20];
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
@ -883,7 +1530,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
strncpy(distStr, "? km", sizeof(distStr));
}
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
const char *fields[] = {username, lastStr, signalStr, distStr, NULL};
int16_t compassX = 0, compassY = 0;
// coordinates for the center of the compass/circle
@ -896,9 +1543,13 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
}
bool hasNodeHeading = false;
if (ourNode && hasValidPosition(ourNode)) {
if (ourNode && (hasValidPosition(ourNode) || screen->hasHeading())) {
const meshtastic_PositionLite &op = ourNode->position;
float myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
float myHeading;
if (screen->hasHeading())
myHeading = (screen->getHeading()) * PI / 180; // gotta convert compass degrees to Radians
else
myHeading = estimatedHeading(DegD(op.latitude_i), DegD(op.longitude_i));
drawCompassNorth(display, compassX, compassY, myHeading);
if (hasValidPosition(node)) {
@ -1023,7 +1674,14 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
#if !ARCH_PORTDUINO
dispdev->displayOn();
#endif
#if defined(ST7789_CS) && \
!defined(M5STACK) // set display brightness when turning on screens. Just moved function from TFTDisplay to here.
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
#endif
dispdev->displayOn();
enabled = true;
setInterval(0); // Draw ASAP
runASAP = true;
@ -1194,6 +1852,10 @@ int32_t Screen::runOnce()
return RUN_SAME;
}
if (displayHeight == 0) {
displayHeight = dispdev->getHeight();
}
// Show boot screen for first logo_timeout seconds, then switch to normal operation.
// serialSinceMsec adjusts for additional serial wait time during nRF52 bootup
static bool showingBootScreen = true;
@ -1424,6 +2086,15 @@ void Screen::setFrames()
LOG_DEBUG("showing standard frames\n");
showingNormalScreen = true;
#ifdef USE_EINK
// If user has disabled the screensaver, warn them after boot
static bool warnedScreensaverDisabled = false;
if (config.display.screen_on_secs == 0 && !warnedScreensaverDisabled) {
screen->print("Screensaver disabled\n");
warnedScreensaverDisabled = true;
}
#endif
moduleFrames = MeshModule::GetMeshModulesWithUIFrames();
LOG_DEBUG("Showing %d module frames\n", moduleFrames.size());
#ifdef DEBUG_PORT
@ -1431,7 +2102,7 @@ void Screen::setFrames()
LOG_DEBUG("Total frame count: %d\n", totalFrameCount);
#endif
// We don't show the node info our our node (if we have it yet - we should)
// We don't show the node info of our node (if we have it yet - we should)
size_t numMeshNodes = nodeDB->getNumMeshNodes();
if (numMeshNodes > 0)
numMeshNodes--;
@ -1454,6 +2125,10 @@ void Screen::setFrames()
if (error_code)
normalFrames[numframes++] = drawCriticalFaultFrame;
#ifdef T_WATCH_S3
normalFrames[numframes++] = screen->digitalWatchFace ? &Screen::drawDigitalClockFrame : &Screen::drawAnalogClockFrame;
#endif
// If we have a text message - show it next, unless it's a phone message and we aren't using any special modules
if (devicestate.has_rx_text_message && shouldDrawMessage(&devicestate.rx_text_message)) {
normalFrames[numframes++] = drawTextMessageFrame;
@ -1490,6 +2165,11 @@ void Screen::setFrames()
ui->setFrames(normalFrames, numframes);
ui->enableAllIndicators();
// Add function overlay here. This can show when notifications muted, modifier key is active etc
static OverlayCallback functionOverlay[] = {drawFunctionOverlay};
static const int functionOverlayCount = sizeof(functionOverlay) / sizeof(functionOverlay[0]);
ui->setOverlays(functionOverlay, functionOverlayCount);
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
// just changed)
@ -1573,9 +2253,55 @@ void Screen::blink()
delay(50);
count = count - 1;
}
// The dispdev->setBrightness does not work for t-deck display, it seems to run the setBrightness function in OLEDDisplay.
dispdev->setBrightness(brightness);
}
void Screen::increaseBrightness()
{
brightness = ((brightness + 62) > 254) ? brightness : (brightness + 62);
#if defined(ST7789_CS)
// run the setDisplayBrightness function. This works on t-decks
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
#endif
/* TO DO: add little popup in center of screen saying what brightness level it is set to*/
}
void Screen::decreaseBrightness()
{
brightness = (brightness < 70) ? brightness : (brightness - 62);
#if defined(ST7789_CS)
static_cast<TFTDisplay *>(dispdev)->setDisplayBrightness(brightness);
#endif
/* TO DO: add little popup in center of screen saying what brightness level it is set to*/
}
void Screen::setFunctionSymbal(std::string sym)
{
if (std::find(functionSymbals.begin(), functionSymbals.end(), sym) == functionSymbals.end()) {
functionSymbals.push_back(sym);
functionSymbalString = "";
for (auto symbol : functionSymbals) {
functionSymbalString = symbol + " " + functionSymbalString;
}
setFastFramerate();
}
}
void Screen::removeFunctionSymbal(std::string sym)
{
functionSymbals.erase(std::remove(functionSymbals.begin(), functionSymbals.end(), sym), functionSymbals.end());
functionSymbalString = "";
for (auto symbol : functionSymbals) {
functionSymbalString = symbol + " " + functionSymbalString;
}
setFastFramerate();
}
std::string Screen::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds)
{
std::string uptime;
@ -1983,6 +2709,21 @@ int Screen::handleUIFrameEvent(const UIFrameEvent *event)
int Screen::handleInputEvent(const InputEvent *event)
{
#ifdef T_WATCH_S3
// For the T-Watch, intercept touches to the 'toggle digital/analog watch face' button
uint8_t watchFaceFrame = error_code ? 1 : 0;
if (this->ui->getUiState()->currentFrame == watchFaceFrame && event->touchX >= 204 && event->touchX <= 240 &&
event->touchY >= 204 && event->touchY <= 240) {
screen->digitalWatchFace = !screen->digitalWatchFace;
setFrames();
return 0;
}
#endif
if (showingNormalScreen && moduleFrames.size() == 0) {
// LOG_DEBUG("Screen::handleInputEvent from %s\n", event->source);
if (event->inputEvent == static_cast<char>(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT)) {
@ -1998,4 +2739,4 @@ int Screen::handleInputEvent(const InputEvent *event)
} // namespace graphics
#else
graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}
#endif // HAS_SCREEN
#endif // HAS_SCREEN

View File

@ -48,6 +48,7 @@ class Screen
#include "EInkDisplay2.h"
#include "EInkDynamicDisplay.h"
#include "PointStruct.h"
#include "TFTDisplay.h"
#include "TypedQueue.h"
#include "commands.h"
@ -77,6 +78,10 @@ class Screen
#define EINK_BLACK OLEDDISPLAY_COLOR::WHITE
#define EINK_WHITE OLEDDISPLAY_COLOR::BLACK
// Base segment dimensions for T-Watch segmented display
#define SEGMENT_WIDTH 16
#define SEGMENT_HEIGHT 4
namespace graphics
{
@ -166,9 +171,6 @@ class Screen : public concurrency::OSThread
void showPrevFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_PREV_FRAME}); }
void showNextFrame() { enqueueCmd(ScreenCmd{.cmd = Cmd::SHOW_NEXT_FRAME}); }
// Implementation to Adjust Brightness
uint8_t brightness = BRIGHTNESS_DEFAULT;
/// Starts showing the Bluetooth PIN screen.
//
// Switches over to a static frame showing the Bluetooth pairing screen
@ -202,6 +204,24 @@ class Screen : public concurrency::OSThread
enqueueCmd(cmd);
}
// Function to allow the AccelerometerThread to set the heading if a sensor provides it
// Mutex needed?
void setHeading(long _heading)
{
hasCompass = true;
compassHeading = _heading;
}
bool hasHeading() { return hasCompass; }
long getHeading() { return compassHeading; }
// functions for display brightness
void increaseBrightness();
void decreaseBrightness();
void setFunctionSymbal(std::string sym);
void removeFunctionSymbal(std::string sym);
/// Stops showing the bluetooth PIN screen.
void stopBluetoothPinScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BLUETOOTH_PIN_SCREEN}); }
@ -351,7 +371,7 @@ class Screen : public concurrency::OSThread
bool enqueueCmd(const ScreenCmd &cmd)
{
if (!useDisplay)
return true; // claim success if our display is not in use
return false; // not enqueued if our display is not in use
else {
bool success = cmdQueue.enqueue(cmd, 0);
enabled = true; // handle ASAP (we are the registered reader for cmdQueue, but might have been disabled)
@ -385,6 +405,27 @@ class Screen : public concurrency::OSThread
static void drawDebugInfoWiFiTrampoline(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
#ifdef T_WATCH_S3
static void drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
static void drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
static void drawSegmentedDisplayCharacter(OLEDDisplay *display, int x, int y, uint8_t number, float scale = 1);
static void drawHorizontalSegment(OLEDDisplay *display, int x, int y, int width, int height);
static void drawVerticalSegment(OLEDDisplay *display, int x, int y, int width, int height);
static void drawSegmentedDisplayColon(OLEDDisplay *display, int x, int y, float scale = 1);
static void drawWatchFaceToggleButton(OLEDDisplay *display, int16_t x, int16_t y, bool digitalMode = true, float scale = 1);
static void drawBluetoothConnectedIcon(OLEDDisplay *display, int16_t x, int16_t y);
// Whether we are showing the digital watch face or the analog one
bool digitalWatchFace = true;
#endif
/// Queue of commands to execute in doTask.
TypedQueue<ScreenCmd> cmdQueue;
/// Whether we are using a display
@ -395,6 +436,11 @@ class Screen : public concurrency::OSThread
// Bluetooth PIN screen)
bool showingNormalScreen = false;
// Implementation to Adjust Brightness
uint8_t brightness = BRIGHTNESS_DEFAULT; // H = 254, MH = 192, ML = 130 L = 103
bool hasCompass = false;
float compassHeading;
/// Holds state for debug information
DebugInfo debugInfo;

View File

@ -1,7 +1,6 @@
#include "configuration.h"
#include "main.h"
#if ARCH_PORTDUINO
#include "mesh_bus_spi.h"
#include "platform/portduino/PortduinoGlue.h"
#endif
@ -9,6 +8,12 @@
#define TFT_BACKLIGHT_ON HIGH
#endif
#ifdef GPIO_EXTENDER
#include <SparkFunSX1509.h>
#include <Wire.h>
extern SX1509 gpioExtender;
#endif
#ifndef TFT_MESH
#define TFT_MESH COLOR565(0x67, 0xEA, 0x94)
#endif
@ -112,8 +117,16 @@ class LGFX : public lgfx::LGFX_Device
static LGFX *tft = nullptr;
#elif defined(RAK14014)
#include <RAK14014_FT6336U.h>
#include <TFT_eSPI.h>
TFT_eSPI *tft = nullptr;
FT6336U ft6336u;
static uint8_t _rak14014_touch_int = false; // TP interrupt generation flag.
static void rak14014_tpIntHandle(void)
{
_rak14014_touch_int = true;
}
#elif defined(ST7789_CS)
#include <LovyanGFX.hpp> // Graphics and font library for ST7735 driver chip
@ -340,7 +353,7 @@ static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_LCD *_panel_instance;
lgfx::Mesh_Bus_SPI _bus_instance;
lgfx::Bus_SPI _bus_instance;
lgfx::ITouch *_touch_instance;
@ -357,7 +370,7 @@ class LGFX : public lgfx::LGFX_Device
_panel_instance = new lgfx::Panel_ILI9341;
auto buscfg = _bus_instance.config();
buscfg.spi_mode = 0;
_bus_instance.spi_device(DisplaySPI, settingsStrings[displayspidev]);
buscfg.spi_host = settingsMap[displayspidev];
buscfg.pin_dc = settingsMap[displayDC]; // Set SPI DC pin number (-1 = disable)
@ -383,6 +396,8 @@ class LGFX : public lgfx::LGFX_Device
_touch_instance = new lgfx::Touch_XPT2046;
} else if (settingsMap[touchscreenModule] == stmpe610) {
_touch_instance = new lgfx::Touch_STMPE610;
} else if (settingsMap[touchscreenModule] == ft5x06) {
_touch_instance = new lgfx::Touch_FT5x06;
}
auto touch_cfg = _touch_instance->config();
@ -394,6 +409,11 @@ class LGFX : public lgfx::LGFX_Device
touch_cfg.pin_int = settingsMap[touchscreenIRQ];
touch_cfg.bus_shared = true;
touch_cfg.offset_rotation = 1;
if (settingsMap[touchscreenI2CAddr] != -1) {
touch_cfg.i2c_addr = settingsMap[touchscreenI2CAddr];
} else {
touch_cfg.spi_host = settingsMap[touchscreenspidev];
}
_touch_instance->config(touch_cfg);
_panel_instance->setTouch(_touch_instance);
@ -584,7 +604,7 @@ void TFTDisplay::sendCommand(uint8_t com)
unphone.backlight(true); // using unPhone library
#endif
#ifdef RAK14014
#elif !defined(M5STACK)
#elif !defined(M5STACK) && !defined(ST7789_CS) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function
tft->setBrightness(172);
#endif
break;
@ -628,6 +648,16 @@ void TFTDisplay::sendCommand(uint8_t com)
// Drop all other commands to device (we just update the buffer)
}
void TFTDisplay::setDisplayBrightness(uint8_t _brightness)
{
#ifdef RAK14014
// todo
#else
tft->setBrightness(_brightness);
LOG_DEBUG("Brightness is set to value: %i \n", _brightness);
#endif
}
void TFTDisplay::flipScreenVertically()
{
#if defined(T_WATCH_S3)
@ -639,6 +669,7 @@ void TFTDisplay::flipScreenVertically()
bool TFTDisplay::hasTouch(void)
{
#ifdef RAK14014
return true;
#elif !defined(M5STACK)
return tft->touch() != nullptr;
#else
@ -649,6 +680,15 @@ bool TFTDisplay::hasTouch(void)
bool TFTDisplay::getTouch(int16_t *x, int16_t *y)
{
#ifdef RAK14014
if (_rak14014_touch_int) {
_rak14014_touch_int = false;
/* The X and Y axes have to be switched */
*y = ft6336u.read_touch1_x();
*x = TFT_HEIGHT - ft6336u.read_touch1_y();
return true;
} else {
return false;
}
#elif !defined(M5STACK)
return tft->getTouch(x, y);
#else
@ -698,7 +738,10 @@ bool TFTDisplay::connect()
#elif defined(RAK14014)
tft->setRotation(1);
tft->setSwapBytes(true);
// tft->fillScreen(TFT_BLACK);
// tft->fillScreen(TFT_BLACK);
ft6336u.begin();
pinMode(SCREEN_TOUCH_INT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(SCREEN_TOUCH_INT), rak14014_tpIntHandle, FALLING);
#elif defined(T_DECK) || defined(PICOMPUTER_S3) || defined(CHATTER_2)
tft->setRotation(1); // T-Deck has the TFT in landscape
#elif defined(T_WATCH_S3)
@ -711,4 +754,4 @@ bool TFTDisplay::connect()
return true;
}
#endif
#endif

View File

@ -30,6 +30,9 @@ class TFTDisplay : public OLEDDisplay
static bool hasTouch(void);
static bool getTouch(int16_t *x, int16_t *y);
// Functions for changing display brightness
void setDisplayBrightness(uint8_t);
/**
* shim to make the abstraction happy
*

View File

@ -14,6 +14,12 @@ const uint8_t imgUser[] PROGMEM = {0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3
const uint8_t imgPositionEmpty[] PROGMEM = {0x20, 0x30, 0x28, 0x24, 0x42, 0xFF};
const uint8_t imgPositionSolid[] PROGMEM = {0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF};
#ifdef T_WATCH_S3
const uint8_t bluetoothConnectedIcon[36] PROGMEM = {0xfe, 0x01, 0xff, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0xe3, 0x1f,
0xf3, 0x3f, 0x33, 0x30, 0x33, 0x33, 0x33, 0x33, 0x03, 0x33, 0xff, 0x33,
0xfe, 0x31, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0xf0, 0x3f, 0xe0, 0x1f};
#endif
#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS) || \
ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
@ -31,4 +37,171 @@ const uint8_t imgQuestion[] PROGMEM = {0xbf, 0x41, 0xc0, 0x8b, 0xdb, 0x70, 0xa1,
const uint8_t imgSF[] PROGMEM = {0xd2, 0xb7, 0xad, 0xbb, 0x92, 0x01, 0xfd, 0xfd, 0x15, 0x85, 0xf5};
#endif
#ifndef EXCLUDE_EMOJI
#define thumbs_height 25
#define thumbs_width 25
static unsigned char thumbup[] PROGMEM = {
0x00, 0x1C, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x80, 0x09, 0x00, 0x00,
0xC0, 0x08, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00,
0x0C, 0xCE, 0x7F, 0x00, 0x04, 0x20, 0x80, 0x00, 0x02, 0x20, 0x80, 0x00, 0x02, 0x60, 0xC0, 0x00, 0x01, 0xF8, 0xFF, 0x01,
0x01, 0x08, 0x00, 0x01, 0x01, 0x08, 0x00, 0x01, 0x01, 0xF8, 0xFF, 0x00, 0x01, 0x10, 0x80, 0x00, 0x01, 0x18, 0x80, 0x00,
0x02, 0x30, 0xC0, 0x00, 0x06, 0xE0, 0x3F, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x38, 0x20, 0x10, 0x00, 0xE0, 0xCF, 0x1F, 0x00,
};
static unsigned char thumbdown[] PROGMEM = {
0xE0, 0xCF, 0x1F, 0x00, 0x38, 0x20, 0x10, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x06, 0xE0, 0x3F, 0x00, 0x02, 0x30, 0xC0, 0x00,
0x01, 0x18, 0x80, 0x00, 0x01, 0x10, 0x80, 0x00, 0x01, 0xF8, 0xFF, 0x00, 0x01, 0x08, 0x00, 0x01, 0x01, 0x08, 0x00, 0x01,
0x01, 0xF8, 0xFF, 0x01, 0x02, 0x60, 0xC0, 0x00, 0x02, 0x20, 0x80, 0x00, 0x04, 0x20, 0x80, 0x00, 0x0C, 0xCE, 0x7F, 0x00,
0x18, 0x02, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x20, 0x04, 0x00, 0x00, 0x40, 0x08, 0x00, 0x00, 0xC0, 0x08, 0x00, 0x00,
0x80, 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00,
};
#define question_height 25
#define question_width 25
static unsigned char question[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, 0xFF, 0x01, 0x00, 0xC0, 0xFF, 0x07, 0x00, 0xE0, 0xFF, 0x07, 0x00,
0xE0, 0xC3, 0x0F, 0x00, 0xF0, 0x81, 0x0F, 0x00, 0xF0, 0x01, 0x0F, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x80, 0x0F, 0x00,
0x00, 0xC0, 0x0F, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xF8, 0x01, 0x00, 0x00, 0x7C, 0x00, 0x00,
0x00, 0x3C, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
#define bang_height 30
#define bang_width 30
static unsigned char bang[] PROGMEM = {
0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x0F, 0xFC, 0x3F, 0xFF, 0x07, 0xF8, 0x3F, 0xFF, 0x07, 0xF8, 0x3F,
0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F,
0xFE, 0x03, 0xF0, 0x1F, 0xFE, 0x03, 0xF0, 0x1F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F,
0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x03, 0xF0, 0x0F, 0xFC, 0x01, 0xE0, 0x0F, 0xFC, 0x01, 0xE0, 0x0F,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0xC0, 0x03, 0xFC, 0x03, 0xF0, 0x0F, 0xFE, 0x03, 0xF0, 0x1F,
0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFE, 0x07, 0xF8, 0x1F, 0xFC, 0x03, 0xF0, 0x0F, 0xF8, 0x01, 0xE0, 0x07,
};
#define haha_height 30
#define haha_width 30
static unsigned char haha[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00,
0x00, 0xFC, 0x0F, 0x00, 0x00, 0x1F, 0x3E, 0x00, 0x80, 0x03, 0x70, 0x00, 0xC0, 0x01, 0xE0, 0x00, 0xC0, 0x00, 0xC2, 0x00,
0x60, 0x00, 0x03, 0x00, 0x60, 0x00, 0xC1, 0x1F, 0x60, 0x80, 0x8F, 0x31, 0x30, 0x0E, 0x80, 0x31, 0x30, 0x10, 0x30, 0x1F,
0x30, 0x08, 0x58, 0x00, 0x30, 0x04, 0x6C, 0x03, 0x60, 0x00, 0xF3, 0x01, 0x60, 0xC0, 0xFC, 0x01, 0x80, 0x38, 0xBF, 0x01,
0xE0, 0xC5, 0xDF, 0x00, 0xB0, 0xF9, 0xEF, 0x00, 0x30, 0xF1, 0x73, 0x00, 0xB0, 0x1D, 0x3E, 0x00, 0xF0, 0xFD, 0x0F, 0x00,
0xE0, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
#define wave_icon_height 30
#define wave_icon_width 30
static unsigned char wave_icon[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xC0, 0x00,
0x00, 0x0C, 0x9C, 0x01, 0x80, 0x17, 0x20, 0x01, 0x80, 0x26, 0x46, 0x02, 0x80, 0x44, 0x88, 0x02, 0xC0, 0x89, 0x8A, 0x02,
0x40, 0x93, 0x8B, 0x02, 0x40, 0x26, 0x13, 0x00, 0x80, 0x44, 0x16, 0x00, 0xC0, 0x89, 0x24, 0x00, 0x40, 0x93, 0x60, 0x00,
0x40, 0x26, 0x40, 0x00, 0x80, 0x0C, 0x80, 0x00, 0x00, 0x09, 0x80, 0x00, 0x00, 0x02, 0x80, 0x00, 0x40, 0x06, 0x80, 0x00,
0x50, 0x0C, 0x80, 0x00, 0x50, 0x08, 0x40, 0x00, 0x90, 0x10, 0x20, 0x00, 0xB0, 0x21, 0x10, 0x00, 0x20, 0x47, 0x18, 0x00,
0x40, 0x80, 0x0F, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
#define cowboy_height 30
#define cowboy_width 30
static unsigned char cowboy[] PROGMEM = {
0x00, 0xF0, 0x03, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x00, 0xFE, 0x1F, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x3C, 0xFE, 0x1F, 0x0F,
0xFE, 0xFE, 0xDF, 0x1F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F,
0x3E, 0xC0, 0x00, 0x1F, 0x1E, 0x00, 0x00, 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x08, 0x0E, 0x1C, 0x04, 0x00, 0x0E, 0x1C, 0x00,
0x04, 0x0E, 0x1C, 0x08, 0x04, 0x0E, 0x1C, 0x08, 0x04, 0x04, 0x08, 0x08, 0x04, 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x08,
0x8C, 0x07, 0x70, 0x0C, 0x88, 0xFC, 0x4F, 0x04, 0x88, 0x01, 0x40, 0x04, 0x90, 0xFF, 0x7F, 0x02, 0x30, 0x03, 0x30, 0x03,
0x60, 0x0E, 0x9C, 0x01, 0xC0, 0xF8, 0xC7, 0x00, 0x80, 0x01, 0x60, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00, 0xF8, 0x07, 0x00,
};
#define deadmau5_height 30
#define deadmau5_width 60
static unsigned char deadmau5[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x07, 0x00,
0x00, 0xFC, 0x03, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x80, 0xFF, 0x0F, 0x00, 0xC0, 0xFF, 0xFF, 0x00, 0xE0, 0xFF, 0x3F, 0x00,
0xE0, 0xFF, 0xFF, 0x01, 0xF0, 0xFF, 0x7F, 0x00, 0xF0, 0xFF, 0xFF, 0x03, 0xF8, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x07,
0xFC, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0xFE, 0xFF, 0xFF, 0x00,
0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xE0, 0xFF, 0x3F, 0xFC,
0x0F, 0xFF, 0x7F, 0x00, 0xC0, 0xFF, 0x1F, 0xF8, 0x0F, 0xFC, 0x3F, 0x00, 0x80, 0xFF, 0x0F, 0xF8, 0x1F, 0xFC, 0x1F, 0x00,
0x00, 0xFF, 0x0F, 0xFC, 0x3F, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x1F, 0xFF, 0xFF, 0xFE, 0x01, 0x00, 0x00, 0x00, 0xFC, 0xFF,
0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00,
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0xC0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x80, 0x07, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
#define sun_width 30
#define sun_height 30
static unsigned char sun[] PROGMEM = {
0x00, 0xC0, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x30, 0xC0, 0x00, 0x03,
0x70, 0x00, 0x80, 0x03, 0xF0, 0x00, 0xC0, 0x03, 0xF0, 0xF8, 0xC7, 0x03, 0xE0, 0xFC, 0xCF, 0x01, 0x00, 0xFE, 0x1F, 0x00,
0x00, 0xFF, 0x3F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x8E, 0xFF, 0x7F, 0x1C, 0x9F, 0xFF, 0x7F, 0x3E,
0x9F, 0xFF, 0x7F, 0x3E, 0x8E, 0xFF, 0x7F, 0x1C, 0x80, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0x3F, 0x00,
0x00, 0xFE, 0x1F, 0x00, 0x00, 0xFC, 0x0F, 0x00, 0xC0, 0xF9, 0xE7, 0x00, 0xE0, 0x01, 0xE0, 0x01, 0xF0, 0x01, 0xE0, 0x03,
0xF0, 0xC0, 0xC0, 0x03, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0xC0, 0x00, 0x00,
};
#define rain_width 30
#define rain_height 30
static unsigned char rain[] PROGMEM = {
0xC0, 0x0F, 0xC0, 0x00, 0x40, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x03, 0x38, 0x00,
0x00, 0x0E, 0x0C, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00, 0x20,
0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x30, 0x02, 0x00,
0x00, 0x10, 0x06, 0x00, 0x00, 0x08, 0xFC, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x01, 0x80, 0x00, 0x01, 0x00,
0xC0, 0xC0, 0x81, 0x03, 0xA0, 0x60, 0xC1, 0x03, 0x90, 0x20, 0x41, 0x01, 0xF0, 0xE0, 0xC0, 0x01, 0x60, 0x4C,
0x98, 0x00, 0x00, 0x0E, 0x1C, 0x00, 0x00, 0x0B, 0x12, 0x00, 0x00, 0x09, 0x1A, 0x00, 0x00, 0x06, 0x0E, 0x00,
};
#define cloud_height 30
#define cloud_width 30
static unsigned char cloud[] PROGMEM = {
0x00, 0x80, 0x07, 0x00, 0x00, 0xE0, 0x1F, 0x00, 0x00, 0x70, 0x30, 0x00, 0x00, 0x10, 0x60, 0x00, 0x80, 0x1F, 0x40, 0x00,
0xC0, 0x0F, 0xC0, 0x00, 0xC0, 0x00, 0x80, 0x00, 0x60, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x00, 0x20, 0x00, 0x80, 0x01,
0x20, 0x00, 0x00, 0x07, 0x38, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x08, 0x06, 0x00, 0x00, 0x18, 0x02, 0x00, 0x00, 0x10,
0x02, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20,
0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x30, 0x03, 0x00, 0x00, 0x10,
0x02, 0x00, 0x00, 0x10, 0x06, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x0C, 0xFC, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x03,
};
#define fog_height 25
#define fog_width 25
static unsigned char fog[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x3C, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0x87, 0xC7, 0xC3, 0x01, 0x03, 0xFE, 0x80, 0x01,
0x00, 0x38, 0x00, 0x00, 0xFC, 0x00, 0x7E, 0x00, 0xFF, 0x83, 0xFF, 0x01, 0x03, 0xFF, 0x81, 0x01, 0x00, 0x7C, 0x00, 0x00,
0xF8, 0x00, 0x3E, 0x00, 0xFE, 0x01, 0xFF, 0x00, 0x87, 0xC7, 0xC3, 0x01, 0x03, 0xFE, 0x80, 0x01, 0x00, 0x38, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
#define devil_height 30
#define devil_width 30
static unsigned char devil[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x10, 0x03, 0xC0, 0x01, 0x38, 0x07, 0x7C, 0x0F, 0x38, 0x1F, 0x03, 0x30, 0x1E,
0xFE, 0x01, 0xE0, 0x1F, 0x7E, 0x00, 0x80, 0x1F, 0x3C, 0x00, 0x00, 0x0F, 0x1C, 0x00, 0x00, 0x0E, 0x18, 0x00, 0x00, 0x06,
0x08, 0x00, 0x00, 0x04, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x0E, 0x1C, 0x0C,
0x0C, 0x18, 0x06, 0x0C, 0x0C, 0x1C, 0x06, 0x0C, 0x0C, 0x1C, 0x0E, 0x0C, 0x0C, 0x1C, 0x0E, 0x0C, 0x0C, 0x0C, 0x06, 0x0C,
0x08, 0x00, 0x00, 0x06, 0x18, 0x02, 0x10, 0x06, 0x10, 0x0C, 0x0C, 0x03, 0x30, 0xF8, 0x07, 0x03, 0x60, 0xE0, 0x80, 0x01,
0xC0, 0x00, 0xC0, 0x00, 0x80, 0x01, 0x70, 0x00, 0x00, 0x06, 0x1C, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
};
#define heart_height 30
#define heart_width 30
static unsigned char heart[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0xF0, 0x00, 0xF8, 0x0F, 0xFC, 0x07, 0xFC, 0x1F, 0x06, 0x0E, 0xFE, 0x3F, 0x03, 0x18,
0xFE, 0xFF, 0x7F, 0x10, 0xFF, 0xFF, 0xFF, 0x31, 0xFF, 0xFF, 0xFF, 0x33, 0xFF, 0xFF, 0xFF, 0x37, 0xFF, 0xFF, 0xFF, 0x37,
0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x3F, 0xFE, 0xFF, 0xFF, 0x1F, 0xFE, 0xFF, 0xFF, 0x1F,
0xFC, 0xFF, 0xFF, 0x0F, 0xFC, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF, 0x07, 0xF0, 0xFF, 0xFF, 0x03, 0xF0, 0xFF, 0xFF, 0x03,
0xE0, 0xFF, 0xFF, 0x01, 0xC0, 0xFF, 0xFF, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0x3F, 0x00, 0x00, 0xFE, 0x1F, 0x00,
0x00, 0xFC, 0x0F, 0x00, 0x00, 0xF8, 0x07, 0x00, 0x00, 0xF0, 0x03, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x80, 0x00, 0x00,
};
#define poo_width 30
#define poo_height 30
static unsigned char poo[] PROGMEM = {
0x00, 0x1C, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0xEC, 0x01, 0x00, 0x00, 0x8C, 0x07, 0x00, 0x00, 0x0C, 0x06, 0x00,
0x00, 0x24, 0x0C, 0x00, 0x00, 0x34, 0x08, 0x00, 0x00, 0x1F, 0x08, 0x00, 0xC0, 0x0F, 0x08, 0x00, 0xC0, 0x00, 0x3C, 0x00,
0x60, 0x00, 0x7C, 0x00, 0x60, 0x00, 0xC6, 0x00, 0x20, 0x00, 0xCB, 0x00, 0xA0, 0xC7, 0xFF, 0x00, 0xE0, 0x7F, 0xF7, 0x00,
0xF0, 0x18, 0xE3, 0x03, 0x78, 0x18, 0x41, 0x03, 0x6C, 0x9B, 0x5D, 0x06, 0x64, 0x9B, 0x5D, 0x04, 0x44, 0x1A, 0x41, 0x04,
0x4C, 0xD8, 0x63, 0x06, 0xF8, 0xFC, 0x36, 0x06, 0xFE, 0x0F, 0x9C, 0x1F, 0x07, 0x03, 0xC0, 0x30, 0x03, 0x00, 0x78, 0x20,
0x01, 0x00, 0x1F, 0x20, 0x03, 0xE0, 0x03, 0x20, 0x07, 0x7E, 0x04, 0x30, 0xFE, 0x0F, 0xFC, 0x1F, 0xF0, 0x00, 0xF0, 0x0F,
};
#endif
#include "img/icon.xbm"

View File

@ -1,188 +0,0 @@
// This code has been copied from LovyanGFX to make the SPI device selectable for touchscreens.
// Ideally this could eventually be an inherited class from BUS_SPI,
// but currently too many internal objects are set private.
#include "configuration.h"
#if ARCH_PORTDUINO
#include "lgfx/v1/misc/pixelcopy.hpp"
#include "main.h"
#include "mesh_bus_spi.h"
#include <Arduino.h>
#include <SPI.h>
namespace lgfx
{
inline namespace v1
{
//----------------------------------------------------------------------------
void Mesh_Bus_SPI::config(const config_t &config)
{
_cfg = config;
if (_cfg.pin_dc >= 0) {
pinMode(_cfg.pin_dc, pin_mode_t::output);
gpio_hi(_cfg.pin_dc);
}
}
bool Mesh_Bus_SPI::init(void)
{
dc_h();
pinMode(_cfg.pin_dc, pin_mode_t::output);
if (SPIName != "")
PrivateSPI->begin(SPIName.c_str());
else
PrivateSPI->begin();
return true;
}
void Mesh_Bus_SPI::release(void)
{
PrivateSPI->end();
}
void Mesh_Bus_SPI::spi_device(HardwareSPI *newSPI, std::string newSPIName)
{
PrivateSPI = newSPI;
SPIName = newSPIName;
}
void Mesh_Bus_SPI::beginTransaction(void)
{
dc_h();
SPISettings setting(_cfg.freq_write, MSBFIRST, _cfg.spi_mode);
PrivateSPI->beginTransaction(setting);
}
void Mesh_Bus_SPI::endTransaction(void)
{
PrivateSPI->endTransaction();
dc_h();
}
void Mesh_Bus_SPI::beginRead(void)
{
PrivateSPI->endTransaction();
// SPISettings setting(_cfg.freq_read, BitOrder::MSBFIRST, _cfg.spi_mode, false);
SPISettings setting(_cfg.freq_read, MSBFIRST, _cfg.spi_mode);
PrivateSPI->beginTransaction(setting);
}
void Mesh_Bus_SPI::endRead(void)
{
PrivateSPI->endTransaction();
beginTransaction();
}
void Mesh_Bus_SPI::wait(void) {}
bool Mesh_Bus_SPI::busy(void) const
{
return false;
}
bool Mesh_Bus_SPI::writeCommand(uint32_t data, uint_fast8_t bit_length)
{
dc_l();
PrivateSPI->transfer((uint8_t *)&data, bit_length >> 3);
dc_h();
return true;
}
void Mesh_Bus_SPI::writeData(uint32_t data, uint_fast8_t bit_length)
{
PrivateSPI->transfer((uint8_t *)&data, bit_length >> 3);
}
void Mesh_Bus_SPI::writeDataRepeat(uint32_t data, uint_fast8_t bit_length, uint32_t length)
{
const uint8_t dst_bytes = bit_length >> 3;
uint32_t limit = (dst_bytes == 3) ? 12 : 16;
auto buf = _flip_buffer.getBuffer(512);
size_t fillpos = 0;
reinterpret_cast<uint32_t *>(buf)[0] = data;
fillpos += dst_bytes;
uint32_t len;
do {
len = ((length - 1) % limit) + 1;
if (limit <= 64)
limit <<= 1;
while (fillpos < len * dst_bytes) {
memcpy(&buf[fillpos], buf, fillpos);
fillpos += fillpos;
}
PrivateSPI->transfer(buf, len * dst_bytes);
} while (length -= len);
}
void Mesh_Bus_SPI::writePixels(pixelcopy_t *param, uint32_t length)
{
const uint8_t dst_bytes = param->dst_bits >> 3;
uint32_t limit = (dst_bytes == 3) ? 12 : 16;
uint32_t len;
do {
len = ((length - 1) % limit) + 1;
if (limit <= 32)
limit <<= 1;
auto buf = _flip_buffer.getBuffer(len * dst_bytes);
param->fp_copy(buf, 0, len, param);
PrivateSPI->transfer(buf, len * dst_bytes);
} while (length -= len);
}
void Mesh_Bus_SPI::writeBytes(const uint8_t *data, uint32_t length, bool dc, bool use_dma)
{
if (dc)
dc_h();
else
dc_l();
PrivateSPI->transfer(const_cast<uint8_t *>(data), length);
if (!dc)
dc_h();
}
uint32_t Mesh_Bus_SPI::readData(uint_fast8_t bit_length)
{
uint32_t res = 0;
bit_length >>= 3;
if (!bit_length)
return res;
int idx = 0;
do {
res |= PrivateSPI->transfer(0) << idx;
idx += 8;
} while (--bit_length);
return res;
}
bool Mesh_Bus_SPI::readBytes(uint8_t *dst, uint32_t length, bool use_dma)
{
do {
dst[0] = PrivateSPI->transfer(0);
++dst;
} while (--length);
return true;
}
void Mesh_Bus_SPI::readPixels(void *dst, pixelcopy_t *param, uint32_t length)
{
uint32_t bytes = param->src_bits >> 3;
uint32_t dstindex = 0;
uint32_t len = 4;
uint8_t buf[24];
param->src_data = buf;
do {
if (len > length)
len = length;
readBytes((uint8_t *)buf, len * bytes, true);
param->src_x = 0;
dstindex = param->fp_copy(dst, dstindex, dstindex + len, param);
length -= len;
} while (length);
}
} // namespace v1
} // namespace lgfx
#endif

View File

@ -1,100 +0,0 @@
#if ARCH_PORTDUINO
/*----------------------------------------------------------------------------/
Lovyan GFX - Graphics library for embedded devices.
Original Source:
https://github.com/lovyan03/LovyanGFX/
Licence:
[FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
Author:
[lovyan03](https://twitter.com/lovyan03)
Contributors:
[ciniml](https://github.com/ciniml)
[mongonta0716](https://github.com/mongonta0716)
[tobozo](https://github.com/tobozo)
/----------------------------------------------------------------------------*/
#pragma once
#include <stdint.h>
#include "lgfx/v1/Bus.hpp"
#include "lgfx/v1/platforms/common.hpp"
namespace lgfx
{
inline namespace v1
{
//----------------------------------------------------------------------------
class Mesh_Bus_SPI : public IBus
{
public:
struct config_t {
uint32_t freq_write = 16000000;
uint32_t freq_read = 8000000;
// bool spi_3wire = true;
// bool use_lock = true;
int16_t pin_sclk = -1;
int16_t pin_miso = -1;
int16_t pin_mosi = -1;
int16_t pin_dc = -1;
uint8_t spi_mode = 0;
};
const config_t &config(void) const { return _cfg; }
void config(const config_t &config);
bus_type_t busType(void) const override { return bus_type_t::bus_spi; }
bool init(void) override;
void release(void) override;
void spi_device(HardwareSPI *newSPI, std::string newSPIName);
void beginTransaction(void) override;
void endTransaction(void) override;
void wait(void) override;
bool busy(void) const override;
bool writeCommand(uint32_t data, uint_fast8_t bit_length) override;
void writeData(uint32_t data, uint_fast8_t bit_length) override;
void writeDataRepeat(uint32_t data, uint_fast8_t bit_length, uint32_t count) override;
void writePixels(pixelcopy_t *param, uint32_t length) override;
void writeBytes(const uint8_t *data, uint32_t length, bool dc, bool use_dma) override;
void initDMA(void) {}
void flush(void) {}
void addDMAQueue(const uint8_t *data, uint32_t length) override { writeBytes(data, length, true, true); }
void execDMAQueue(void) {}
uint8_t *getDMABuffer(uint32_t length) override { return _flip_buffer.getBuffer(length); }
void beginRead(void) override;
void endRead(void) override;
uint32_t readData(uint_fast8_t bit_length) override;
bool readBytes(uint8_t *dst, uint32_t length, bool use_dma) override;
void readPixels(void *dst, pixelcopy_t *param, uint32_t length) override;
private:
HardwareSPI *PrivateSPI;
std::string SPIName;
__attribute__((always_inline)) inline void dc_h(void) { gpio_hi(_cfg.pin_dc); }
__attribute__((always_inline)) inline void dc_l(void) { gpio_lo(_cfg.pin_dc); }
config_t _cfg;
FlipBuffer _flip_buffer;
bool _need_wait;
uint32_t _mask_reg_dc;
uint32_t _last_apb_freq = -1;
uint32_t _clkdiv_write;
uint32_t _clkdiv_read;
volatile uint32_t *_gpio_reg_dc_h;
volatile uint32_t *_gpio_reg_dc_l;
};
//----------------------------------------------------------------------------
} // namespace v1
} // namespace lgfx
#endif

View File

@ -1,7 +1,7 @@
#include "InputBroker.h"
#include "PowerFSM.h" // needed for event trigger
InputBroker *inputBroker;
InputBroker *inputBroker = nullptr;
InputBroker::InputBroker(){};

View File

@ -8,6 +8,8 @@ typedef struct _InputEvent {
const char *source;
char inputEvent;
char kbchar;
uint16_t touchX;
uint16_t touchY;
} InputEvent;
class InputBroker : public Observable<const InputEvent *>
{

View File

@ -155,6 +155,9 @@ int32_t LinuxInput::runOnce()
case KEY_ENTER: // Enter
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
break;
case KEY_POWER:
system("poweroff");
break;
default: // all other keys
if (keymap[code]) {
e.inputEvent = ANYKEY;

View File

@ -3,6 +3,7 @@
#include "InputBroker.h"
#include "concurrency/OSThread.h"
#include "mesh/NodeDB.h"
#include "time.h"
typedef struct _TouchEvent {
const char *source;

View File

@ -49,6 +49,10 @@ void TouchScreenImpl1::onEvent(const TouchEvent &event)
{
InputEvent e;
e.source = event.source;
e.touchX = event.x;
e.touchY = event.y;
switch (event.touchEvent) {
case TOUCH_ACTION_LEFT: {
e.inputEvent = static_cast<char>(meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT);

View File

@ -1,5 +1,7 @@
#include "cardKbI2cImpl.h"
#include "InputBroker.h"
#include "detect/ScanI2CTwoWire.h"
#include "main.h"
CardKbI2cImpl *cardKbI2cImpl;
@ -7,10 +9,60 @@ CardKbI2cImpl::CardKbI2cImpl() : KbI2cBase("cardKB") {}
void CardKbI2cImpl::init()
{
<<<<<<< HEAD
if ((cardkb_found.address == 0x00) || (kb_model == 0x12)) {
=======
if (kb_model == 0x12) {
disable();
return;
}
#ifndef ARCH_PORTDUINO
if (cardkb_found.address == 0x00) {
LOG_DEBUG("Rescanning for I2C keyboard\n");
uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR};
uint8_t i2caddr_asize = 3;
auto i2cScanner = std::unique_ptr<ScanI2CTwoWire>(new ScanI2CTwoWire());
#if defined(I2C_SDA1)
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE1, i2caddr_scan, i2caddr_asize);
#endif
i2cScanner->scanPort(ScanI2C::I2CPort::WIRE, i2caddr_scan, i2caddr_asize);
auto kb_info = i2cScanner->firstKeyboard();
if (kb_info.type != ScanI2C::DeviceType::NONE) {
cardkb_found = kb_info.address;
switch (kb_info.type) {
case ScanI2C::DeviceType::RAK14004:
kb_model = 0x02;
break;
case ScanI2C::DeviceType::CARDKB:
kb_model = 0x00;
break;
case ScanI2C::DeviceType::TDECKKB:
// assign an arbitrary value to distinguish from other models
kb_model = 0x10;
break;
case ScanI2C::DeviceType::BBQ10KB:
// assign an arbitrary value to distinguish from other models
kb_model = 0x11;
break;
default:
// use this as default since it's also just zero
LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00\n", kb_info.type);
kb_model = 0x00;
}
}
if (cardkb_found.address == 0x00) {
disable();
return;
}
}
#else
if (cardkb_found.address == 0x00) {
>>>>>>> 96bcc781ee31f68e5eb7b778306525c630435bdd
disable();
return;
}
#endif
inputBroker->registerSource(this);
}

View File

@ -1,6 +1,5 @@
#pragma once
#include "kbI2cBase.h"
#include "main.h"
/**
* @brief The idea behind this class to have static methods for the event handlers.

View File

@ -1,7 +1,7 @@
#include "kbI2cBase.h"
#include "configuration.h"
#include "detect/ScanI2C.h"
#include "detect/ScanI2CTwoWire.h"
extern ScanI2C::DeviceAddress cardkb_found;
extern uint8_t kb_model;
@ -30,11 +30,6 @@ uint8_t read_from_14004(TwoWire *i2cBus, uint8_t reg, uint8_t *data, uint8_t len
int32_t KbI2cBase::runOnce()
{
if (cardkb_found.address == 0x00) {
// Input device is not detected.
return INT32_MAX;
}
if (!i2cBus) {
switch (cardkb_found.port) {
case ScanI2C::WIRE1:
@ -138,6 +133,9 @@ int32_t KbI2cBase::runOnce()
break;
case 0x13: // Code scanner says the SYM key is 0x13
is_sym = !is_sym;
e.inputEvent = ANYKEY;
e.kbchar =
is_sym ? 0xf1 : 0xf2; // send 0xf1 to tell CannedMessages to display that the modifier key is active
break;
case 0x0a: // apparently Enter on Q10 is a line feed instead of carriage return
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
@ -193,6 +191,75 @@ int32_t KbI2cBase::runOnce()
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
e.source = this->_originName;
switch (c) {
case 0x71: // This is the button q. If modifier and q pressed, it cancels the input
if (is_sym) {
is_sym = false;
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL;
} else {
e.inputEvent = ANYKEY;
e.kbchar = c;
}
break;
case 0x74: // letter t. if modifier and t pressed call 'tab'
if (is_sym) {
is_sym = false;
e.inputEvent = ANYKEY;
e.kbchar = 0x09; // TAB Scancode
} else {
e.inputEvent = ANYKEY;
e.kbchar = c;
}
break;
case 0x6d: // letter m. Modifier makes it mute notifications
if (is_sym) {
is_sym = false;
e.inputEvent = ANYKEY;
e.kbchar = 0xac; // mute notifications
} else {
e.inputEvent = ANYKEY;
e.kbchar = c;
}
break;
case 0x6f: // letter o(+). Modifier makes screen increase in brightness
if (is_sym) {
is_sym = false;
e.inputEvent = ANYKEY;
e.kbchar = 0x11; // Increase Brightness code
} else {
e.inputEvent = ANYKEY;
e.kbchar = c;
}
break;
case 0x69: // letter i(-). Modifier makes screen decrease in brightness
if (is_sym) {
is_sym = false;
e.inputEvent = ANYKEY;
e.kbchar = 0x12; // Decrease Brightness code
} else {
e.inputEvent = ANYKEY;
e.kbchar = c;
}
break;
case 0x20: // Space. Send network ping like double press does
if (is_sym) {
is_sym = false;
e.inputEvent = ANYKEY;
e.kbchar = 0xaf; // (fn + space)
} else {
e.inputEvent = ANYKEY;
e.kbchar = c;
}
break;
case 0x67: // letter g. toggle gps
if (is_sym) {
is_sym = false;
e.inputEvent = ANYKEY;
e.kbchar = 0x9e;
} else {
e.inputEvent = ANYKEY;
e.kbchar = c;
}
break;
case 0x1b: // ESC
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL;
break;
@ -216,6 +283,12 @@ int32_t KbI2cBase::runOnce()
e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT;
e.kbchar = 0xb7;
break;
case 0xc: // Modifier key: 0xc is alt+c (Other options could be: 0xea = shift+mic button or 0x4 shift+$(speaker))
// toggle moddifiers button.
is_sym = !is_sym;
e.inputEvent = ANYKEY;
e.kbchar = is_sym ? 0xf1 : 0xf2; // send 0xf1 to tell CannedMessages to display that the modifier key is active
break;
case 0x90: // fn+r
case 0x91: // fn+t
case 0x9b: // fn+s
@ -239,6 +312,7 @@ int32_t KbI2cBase::runOnce()
}
e.inputEvent = ANYKEY;
e.kbchar = c;
is_sym = false;
break;
}

View File

@ -41,13 +41,14 @@
#endif
#if !MESHTASTIC_EXCLUDE_BLUETOOTH
#include "nimble/NimbleBluetooth.h"
NimbleBluetooth *nimbleBluetooth;
NimbleBluetooth *nimbleBluetooth = nullptr;
#endif
#endif
#ifdef ARCH_NRF52
#include "NRF52Bluetooth.h"
NRF52Bluetooth *nrf52Bluetooth;
NRF52Bluetooth *nrf52Bluetooth = nullptr;
;
#endif
#if HAS_WIFI
@ -65,6 +66,8 @@ NRF52Bluetooth *nrf52Bluetooth;
#endif
#include "LLCC68Interface.h"
#include "LR1110Interface.h"
#include "LR1120Interface.h"
#include "RF95Interface.h"
#include "SX1262Interface.h"
#include "SX1268Interface.h"
@ -92,22 +95,23 @@ NRF52Bluetooth *nrf52Bluetooth;
#include "ButtonThread.h"
#endif
#include "AmbientLightingThread.h"
#include "PowerFSMThread.h"
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "AccelerometerThread.h"
#include "AmbientLightingThread.h"
AccelerometerThread *accelerometerThread = nullptr;
#endif
#ifdef HAS_I2S
#include "AudioThread.h"
AudioThread *audioThread;
AudioThread *audioThread = nullptr;
#endif
using namespace concurrency;
// We always create a screen object, but we only init it if we find the hardware
graphics::Screen *screen;
graphics::Screen *screen = nullptr;
// Global power status
meshtastic::PowerStatus *powerStatus = new meshtastic::PowerStatus();
@ -197,7 +201,6 @@ uint32_t timeLastPowered = 0;
static Periodic *ledPeriodic;
static OSThread *powerFSMthread;
static OSThread *accelerometerThread;
static OSThread *ambientLightingThread;
SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0);
@ -342,7 +345,7 @@ void setup()
Wire.begin(I2C_SDA, I2C_SCL);
#elif defined(ARCH_PORTDUINO)
if (settingsStrings[i2cdev] != "") {
LOG_INFO("Using %s as I2C device.\n", settingsStrings[i2cdev]);
LOG_INFO("Using %s as I2C device.\n", settingsStrings[i2cdev].c_str());
Wire.begin(settingsStrings[i2cdev].c_str());
} else {
LOG_INFO("No I2C device configured, skipping.\n");
@ -360,18 +363,11 @@ void setup()
delay(1);
#endif
#ifdef RAK4630
#ifdef PIN_3V3_EN
// We need to enable 3.3V periphery in order to scan it
pinMode(PIN_3V3_EN, OUTPUT);
digitalWrite(PIN_3V3_EN, HIGH);
#endif
#ifdef AQ_SET_PIN
// RAK-12039 set pin for Air quality sensor
pinMode(AQ_SET_PIN, OUTPUT);
digitalWrite(AQ_SET_PIN, HIGH);
#endif
#endif
#ifdef T_DECK
// enable keyboard
@ -426,10 +422,6 @@ void setup()
auto i2cCount = i2cScanner->countDevices();
if (i2cCount == 0) {
LOG_INFO("No I2C devices found\n");
Wire.end();
#ifdef I2C_SDA1
Wire1.end();
#endif
} else {
LOG_INFO("%i I2C devices found\n", i2cCount);
}
@ -541,6 +533,14 @@ void setup()
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::PMSA0031, meshtastic_TelemetrySensorType_PMSA003I)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::RCWL9620, meshtastic_TelemetrySensorType_RCWL9620)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::VEML7700, meshtastic_TelemetrySensorType_VEML7700)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::TSL2591, meshtastic_TelemetrySensorType_TSL25911FN)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::OPT3001, meshtastic_TelemetrySensorType_OPT3001)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::MLX90632, meshtastic_TelemetrySensorType_MLX90632)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::SHT4X, meshtastic_TelemetrySensorType_SHT4X)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::AHT10, meshtastic_TelemetrySensorType_AHT10)
SCANNER_TO_SENSORS_MAP(ScanI2C::DeviceType::DFROBOT_LARK, meshtastic_TelemetrySensorType_DFROBOT_LARK)
i2cScanner.reset();
@ -548,10 +548,6 @@ void setup()
setupSDCard();
#endif
#ifdef RAK4630
// scanEInkDevice();
#endif
// LED init
#ifdef LED_PIN
@ -607,7 +603,7 @@ void setup()
screen_model = meshtastic_Config_DisplayConfig_OledType_OLED_SH1107; // keep dimension of 128x64
#endif
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
if (acc_info.type != ScanI2C::DeviceType::NONE) {
config.display.wake_on_tap_or_motion = true;
moduleConfig.external_notification.enabled = true;
@ -615,7 +611,9 @@ void setup()
}
#endif
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
#if defined(HAS_NEOPIXEL) || defined(UNPHONE) || defined(RGBLED_RED)
ambientLightingThread = new AmbientLightingThread(ScanI2C::DeviceType::NONE);
#elif !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
if (rgb_found.type != ScanI2C::DeviceType::NONE) {
ambientLightingThread = new AmbientLightingThread(rgb_found.type);
}
@ -694,6 +692,12 @@ void setup()
// Now that the mesh service is created, create any modules
setupModules();
#ifdef LED_PIN
// Turn LED off after boot, if heartbeat by config
if (config.device.led_heartbeat_disabled)
digitalWrite(LED_PIN, LOW ^ LED_INVERTED);
#endif
// Do this after service.init (because that clears error_code)
#ifdef HAS_PMU
if (!pmu_found)
@ -730,7 +734,8 @@ void setup()
if (settingsMap[use_sx1262]) {
if (!rIf) {
LOG_DEBUG("Attempting to activate sx1262 radio on SPI port %s\n", settingsStrings[spidev].c_str());
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings);
LockingArduinoHal *RadioLibHAL =
new LockingArduinoHal(SPI, spiSettings, (settingsMap[ch341Quirk] ? settingsMap[busy] : RADIOLIB_NC));
rIf = new SX1262Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
@ -744,7 +749,8 @@ void setup()
} else if (settingsMap[use_rf95]) {
if (!rIf) {
LOG_DEBUG("Attempting to activate rf95 radio on SPI port %s\n", settingsStrings[spidev].c_str());
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings);
LockingArduinoHal *RadioLibHAL =
new LockingArduinoHal(SPI, spiSettings, (settingsMap[ch341Quirk] ? settingsMap[busy] : RADIOLIB_NC));
rIf = new RF95Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
@ -759,7 +765,7 @@ void setup()
} else if (settingsMap[use_sx1280]) {
if (!rIf) {
LOG_DEBUG("Attempting to activate sx1280 radio on SPI port %s\n", settingsStrings[spidev].c_str());
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(*LoraSPI, spiSettings);
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
rIf = new SX1280Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
@ -771,6 +777,21 @@ void setup()
LOG_INFO("SX1280 Radio init succeeded, using SX1280 radio\n");
}
}
} else if (settingsMap[use_sx1268]) {
if (!rIf) {
LOG_DEBUG("Attempting to activate sx1268 radio on SPI port %s\n", settingsStrings[spidev].c_str());
LockingArduinoHal *RadioLibHAL = new LockingArduinoHal(SPI, spiSettings);
rIf = new SX1268Interface((LockingArduinoHal *)RadioLibHAL, settingsMap[cs], settingsMap[irq], settingsMap[reset],
settingsMap[busy]);
if (!rIf->init()) {
LOG_ERROR("Failed to find SX1268 radio\n");
delete rIf;
rIf = NULL;
exit(EXIT_FAILURE);
} else {
LOG_INFO("SX1268 Radio init succeeded, using SX1268 radio\n");
}
}
}
#elif defined(HW_SPI1_DEVICE)
@ -864,6 +885,32 @@ void setup()
}
#endif
#if defined(USE_LR1110)
if (!rIf) {
rIf = new LR1110Interface(RadioLibHAL, LR1110_SPI_NSS_PIN, LR1110_IRQ_PIN, LR1110_NRESER_PIN, LR1110_BUSY_PIN);
if (!rIf->init()) {
LOG_WARN("Failed to find LR1110 radio\n");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("LR1110 Radio init succeeded, using LR1110 radio\n");
}
}
#endif
#if defined(USE_LR1120)
if (!rIf) {
rIf = new LR1120Interface(RadioLibHAL, LR1120_SPI_NSS_PIN, LR1120_IRQ_PIN, LR1120_NRESER_PIN, LR1120_BUSY_PIN);
if (!rIf->init()) {
LOG_WARN("Failed to find LR1120 radio\n");
delete rIf;
rIf = NULL;
} else {
LOG_INFO("LR1120 Radio init succeeded, using LR1120 radio\n");
}
}
#endif
#if defined(USE_SX1280)
if (!rIf) {
rIf = new SX1280Interface(RadioLibHAL, SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY);
@ -1009,4 +1056,4 @@ void loop()
mainDelay.delay(delayMsec);
}
// if (didWake) LOG_DEBUG("wake!\n");
}
}

View File

@ -53,13 +53,18 @@ extern Adafruit_DRV2605 drv;
extern AudioThread *audioThread;
#endif
// Global Screen singleton.
extern graphics::Screen *screen;
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "AccelerometerThread.h"
extern AccelerometerThread *accelerometerThread;
#endif
extern bool isVibrating;
extern int TCPPort; // set by Portduino
// Global Screen singleton.
extern graphics::Screen *screen;
// extern Observable<meshtastic::PowerStatus> newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class
// extern meshtastic::PowerStatus *powerStatus;

View File

@ -22,6 +22,8 @@ uint32_t MemGet::getFreeHeap()
return ESP.getFreeHeap();
#elif defined(ARCH_NRF52)
return dbgHeapFree();
#elif defined(ARCH_RP2040)
return rp2040.getFreeHeap();
#else
// this platform does not have heap management function implemented
return UINT32_MAX;
@ -38,6 +40,8 @@ uint32_t MemGet::getHeapSize()
return ESP.getHeapSize();
#elif defined(ARCH_NRF52)
return dbgHeapTotal();
#elif defined(ARCH_RP2040)
return rp2040.getTotalHeap();
#else
// this platform does not have heap management function implemented
return UINT32_MAX;

Some files were not shown because too many files have changed in this diff Show More