diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml
index 45930a94f..7b97e1753 100644
--- a/.github/actions/setup-base/action.yml
+++ b/.github/actions/setup-base/action.yml
@@ -16,6 +16,19 @@ runs:
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
+
- name: Setup Python
uses: actions/setup-python@v4
with:
diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml
new file mode 100644
index 000000000..7a25892bc
--- /dev/null
+++ b/.github/workflows/build_raspbian.yml
@@ -0,0 +1,45 @@
+name: Build Raspbian
+
+on: workflow_call
+
+permissions:
+ contents: write
+ packages: write
+
+jobs:
+ build-raspbian:
+ runs-on: [self-hosted, linux, ARM64]
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+ with:
+ submodules: recursive
+ ref: ${{github.event.pull_request.head.ref}}
+ repository: ${{github.event.pull_request.head.repo.full_name}}
+
+ - name: 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@v3
+ with:
+ name: firmware-raspbian-${{ steps.version.outputs.version }}.zip
+ path: |
+ release/meshtasticd_linux_aarch64
+ bin/config-dist.yaml
diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index 145a75c2d..5c1cf4c21 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -64,9 +64,10 @@ jobs:
- 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-v1
+ - board: heltec-ht62-esp32c3-sx1262
- board: heltec-v2_0
- board: heltec-v2_1
- board: tbeam0_7
@@ -78,6 +79,7 @@ jobs:
- board: m5stack-core
- board: m5stack-coreink
- board: nano-g1-explorer
+ - board: chatter2
uses: ./.github/workflows/build_esp32.yml
with:
board: ${{ matrix.board }}
@@ -90,6 +92,7 @@ jobs:
- board: heltec-v3
- board: heltec-wsl-v3
- board: heltec-wireless-tracker
+ - board: heltec-wireless-tracker-V1-0
- board: heltec-wireless-paper
- board: tbeam-s3-core
- board: tlora-t3s3-v1
@@ -103,13 +106,13 @@ jobs:
build-nrf52:
strategy:
fail-fast: false
- max-parallel: 2
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
@@ -120,15 +123,26 @@ jobs:
build-rpi2040:
strategy:
fail-fast: false
- max-parallel: 2
matrix:
include:
- board: pico
+ - board: picow
- board: rak11310
+ - board: senselora_rp2040
+ - board: rp2040-lora
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:
@@ -202,9 +216,20 @@ jobs:
repository: ${{github.event.pull_request.head.repo.full_name}}
gather-artifacts:
+ permissions:
+ contents: write
+ pull-requests: write
runs-on: ubuntu-latest
needs:
- [build-esp32, build-esp32-s3, build-nrf52, build-native, build-rpi2040]
+ [
+ build-esp32,
+ build-esp32-s3,
+ build-nrf52,
+ build-raspbian,
+ build-native,
+ build-rpi2040,
+ package-raspbian,
+ ]
steps:
- name: Checkout code
uses: actions/checkout@v3
@@ -216,12 +241,15 @@ jobs:
with:
path: ./
+ - name: Display structure of downloaded files
+ run: ls -R
+
- name: Get release version string
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
- name: Move files up
- run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat
+ run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.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
- name: Repackage in single firmware zip
uses: actions/upload-artifact@v3
@@ -233,6 +261,8 @@ jobs:
./firmware-*-ota.zip
./device-*.sh
./device-*.bat
+ ./meshtasticd_linux_arm64
+ ./config-dist.yaml
retention-days: 90
- uses: actions/download-artifact@v3
@@ -262,14 +292,13 @@ jobs:
- name: Create request artifacts
continue-on-error: true # FIXME: Why are we getting 502, but things still work?
if: ${{ github.event_name == 'pull_request_target' || github.event_name == 'pull_request' }}
- uses: gavv/pull-request-artifacts@v1.1.0
+ uses: gavv/pull-request-artifacts@v2.1.0
with:
commit: ${{ (github.event.pull_request_target || github.event.pull_request).head.sha }}
repo-token: ${{ secrets.GITHUB_TOKEN }}
artifacts-token: ${{ secrets.ARTIFACTS_TOKEN }}
artifacts-repo: meshtastic/artifacts
artifacts-branch: device
- artifacts-dir: pr
artifacts: ./firmware-${{ steps.version.outputs.version }}.zip
release-artifacts:
@@ -294,6 +323,13 @@ jobs:
name: firmware-${{ steps.version.outputs.version }}
path: ./output
+ - uses: actions/download-artifact@v3
+ with:
+ name: artifact-deb
+
+ - name: Display structure of downloaded files
+ run: ls -R
+
- name: Device scripts permissions
run: |
chmod +x ./output/device-install.sh
@@ -347,6 +383,16 @@ jobs:
asset_name: debug-elfs-${{ steps.version.outputs.version }}.zip
asset_content_type: application/zip
+ - name: Add raspbian .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_name: meshtasticd_${{ steps.version.outputs.version }}_arm64.deb
+ asset_content_type: application/vnd.debian.binary-package
+
- name: Bump version.properties
run: >-
bin/bump_version.py
diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml
new file mode 100644
index 000000000..2f9a99e58
--- /dev/null
+++ b/.github/workflows/package_raspbian.yml
@@ -0,0 +1,62 @@
+name: Package Raspbian
+
+on:
+ workflow_call:
+ workflow_dispatch:
+
+permissions:
+ contents: write
+ packages: write
+
+jobs:
+ build-raspbian:
+ uses: ./.github/workflows/build_raspbian.yml
+
+ package-raspbian:
+ runs-on: ubuntu-latest
+ needs: build-raspbian
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+ with:
+ submodules: recursive
+ ref: ${{github.event.pull_request.head.ref}}
+ repository: ${{github.event.pull_request.head.repo.full_name}}
+
+ - name: Get release version string
+ run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+ id: version
+
+ - name: Download artifacts
+ uses: actions/download-artifact@v3
+ with:
+ name: firmware-raspbian-${{ steps.version.outputs.version }}.zip
+
+ - name: Display structure of downloaded files
+ run: ls -R
+
+ - name: build .debpkg
+ run: |
+ mkdir -p .debpkg/usr/sbin
+ mkdir -p .debpkg/etc/meshtasticd
+ mkdir -p .debpkg/usr/lib/systemd/system/
+ cp release/meshtasticd_linux_aarch64 .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
+
+ - uses: jiro4989/build-deb-action@v3
+ with:
+ package: meshtasticd
+ package_root: .debpkg
+ maintainer: Jonathan Bennett
+ version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.*
+ arch: arm64
+ depends: libyaml-cpp0.7
+ desc: Native Linux Meshtastic binary.
+
+ - uses: actions/upload-artifact@v3
+ with:
+ name: artifact-deb
+ path: |
+ ./*.deb
diff --git a/.trunk/configs/.shellcheckrc b/.trunk/configs/.shellcheckrc
index 8c7b1ada8..b2e8a14cc 100644
--- a/.trunk/configs/.shellcheckrc
+++ b/.trunk/configs/.shellcheckrc
@@ -1,7 +1,10 @@
enable=all
source-path=SCRIPTDIR
disable=SC2154
+disable=SC2248
+disable=SC2250
# If you're having issues with shellcheck following source, disable the errors via:
# disable=SC1090
# disable=SC1091
+#
\ No newline at end of file
diff --git a/.trunk/configs/.yamllint.yaml b/.trunk/configs/.yamllint.yaml
index 4d444662d..790846156 100644
--- a/.trunk/configs/.yamllint.yaml
+++ b/.trunk/configs/.yamllint.yaml
@@ -3,7 +3,7 @@ rules:
required: only-when-needed
extra-allowed: ["{|}"]
empty-values:
- forbid-in-block-mappings: true
+ forbid-in-block-mappings: false
forbid-in-flow-mappings: true
key-duplicates: {}
octal-values:
diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index 9c1dcf707..81a35f8f1 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -1,25 +1,24 @@
version: 0.1
cli:
- version: 1.17.0
+ version: 1.17.2
plugins:
sources:
- id: trunk
- ref: v1.2.5
+ ref: v1.3.0
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- bandit@1.7.5
- - checkov@2.5.0
- - terrascan@1.18.3
- - trivy@0.45.1
- - trufflehog@3.59.0
+ - checkov@3.1.9
+ - terrascan@1.18.5
+ - trivy@0.47.0
+ #- trufflehog@3.63.2-rc0
- taplo@0.8.1
- - ruff@0.0.292
- - yamllint@1.32.0
+ - ruff@0.1.6
- isort@5.12.0
- markdownlint@0.37.0
- - oxipng@8.0.0
- - svgo@3.0.2
+ - oxipng@9.0.0
+ - svgo@3.0.5
- actionlint@1.6.26
- flake8@6.1.0
- hadolint@2.12.0
@@ -27,18 +26,9 @@ lint:
- shellcheck@0.9.0
- black@23.9.1
- git-diff-check
- - gitleaks@8.18.0
+ - gitleaks@8.18.1
- clang-format@16.0.3
- - prettier@3.0.3
- disabled:
- - taplo@0.8.1
- - shellcheck@0.9.0
- - shfmt@3.6.0
- - oxipng@8.0.0
- - actionlint@1.6.22
- - markdownlint@0.37.0
- - hadolint@2.12.0
- - svgo@3.0.2
+ - prettier@3.1.0
runtimes:
enabled:
- python@3.10.8
diff --git a/Dockerfile b/Dockerfile
index 8e3cd2154..21e42ad87 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -12,7 +12,7 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Install build deps
USER root
RUN apt-get update && \
- apt-get -y install wget python3 g++ zip python3-venv git vim ca-certificates
+ apt-get -y install wget python3 g++ zip python3-venv git vim ca-certificates libgpiod-dev libyaml-cpp-dev libbluetooth-dev
# create a non-priveleged user & group
RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh
@@ -27,15 +27,15 @@ RUN wget https://raw.githubusercontent.com/platformio/platformio-core-installer/
source ~/.platformio/penv/bin/activate && \
./bin/build-native.sh
-FROM frolvlad/alpine-glibc
+FROM frolvlad/alpine-glibc:glibc-2.31
RUN apk --update add --no-cache g++ shadow && \
groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh
-COPY --from=builder /tmp/firmware/release/meshtasticd_linux_amd64 /home/mesh/
+COPY --from=builder /tmp/firmware/release/meshtasticd_linux_x86_64 /home/mesh/
USER mesh
WORKDIR /home/mesh
-CMD sh -cx "./meshtasticd_linux_amd64 --hwid '$RANDOM'"
+CMD sh -cx "./meshtasticd_linux_x86_64 --hwid '${HWID:-$RANDOM}'"
-HEALTHCHECK NONE
+HEALTHCHECK NONE
\ No newline at end of file
diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini
index aa31db13e..bf84dd939 100644
--- a/arch/esp32/esp32.ini
+++ b/arch/esp32/esp32.ini
@@ -31,6 +31,9 @@ build_flags =
-DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=5120
-DESP_OPENSSL_SUPPRESS_LEGACY_WARNING
-DSERIAL_BUFFER_SIZE=4096
+ -DLIBPAX_ARDUINO
+ -DLIBPAX_WIFI
+ -DLIBPAX_BLE
;-DDEBUG_HEAP
lib_deps =
@@ -39,7 +42,7 @@ lib_deps =
${environmental_base.lib_deps}
https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2
h2zero/NimBLE-Arduino@^1.4.1
- jgromes/RadioLib@^6.1.0
+ https://github.com/dbSuS/libpax.git#7bcd3fcab75037505be9b122ab2b24cc5176b587
https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6
https://github.com/meshtastic/ESP32_Codec2.git#633326c78ac251c059ab3a8c430fcdf25b41672f
@@ -57,4 +60,4 @@ lib_ignore =
; customize the partition table
; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
-board_build.partitions = partition-table.csv
+board_build.partitions = partition-table.csv
\ No newline at end of file
diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini
index d1826a96a..04ca89a54 100644
--- a/arch/nrf52/nrf52.ini
+++ b/arch/nrf52/nrf52.ini
@@ -11,11 +11,10 @@ build_flags =
-Isrc/platform/nrf52
build_src_filter =
- ${arduino_base.build_src_filter} - - - - - - - -
+ ${arduino_base.build_src_filter} - - - - - - - - -
lib_deps=
${arduino_base.lib_deps}
- jgromes/RadioLib@^6.1.0
lib_ignore =
BluetoothOTA
\ No newline at end of file
diff --git a/arch/portduino/portduino.ini b/arch/portduino/portduino.ini
index b39974853..0dcc9afc2 100644
--- a/arch/portduino/portduino.ini
+++ b/arch/portduino/portduino.ini
@@ -1,6 +1,6 @@
; The Portduino based sim environment on top of any host OS, all hardware will be simulated
[portduino_base]
-platform = https://github.com/meshtastic/platform-native.git#489ff929dca0bb768256ba2de45f95815111490f
+platform = https://github.com/meshtastic/platform-native.git#a28dd5a9ccd5c48a9bede46037855ff83915d74b
framework = arduino
build_src_filter =
@@ -10,6 +10,7 @@ build_src_filter =
-
-
-
+ -
-
-
-
@@ -22,9 +23,14 @@ lib_deps =
${env.lib_deps}
${networking_base.lib_deps}
rweather/Crypto@^0.4.0
- jgromes/RadioLib@6.1.0
+ lovyan03/LovyanGFX@^1.1.12
build_flags =
${arduino_base.build_flags}
-fPIC
- -Isrc/platform/portduino
\ No newline at end of file
+ -Isrc/platform/portduino
+ -DRADIOLIB_EEPROM_UNSUPPORTED
+ -DPORTDUINO_LINUX_HARDWARE
+ -lbluetooth
+ -lgpiod
+ -lyaml-cpp
\ No newline at end of file
diff --git a/arch/rp2040/rp2040.ini b/arch/rp2040/rp2040.ini
index b6ac4f171..48fe0dae6 100644
--- a/arch/rp2040/rp2040.ini
+++ b/arch/rp2040/rp2040.ini
@@ -1,8 +1,8 @@
; Common settings for rp2040 Processor based targets
[rp2040_base]
-platform = https://github.com/maxgerhardt/platform-raspberrypi.git#0c33219f53faa035e188925ea1324f472e8b93d2
+platform = https://github.com/maxgerhardt/platform-raspberrypi.git#612de5399d68b359053f1307ed223d400aea975c
extends = arduino_base
-platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#3.2.2
+platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git#3.6.2
board_build.core = earlephilhower
board_build.filesystem_size = 0.5m
@@ -12,7 +12,7 @@ build_flags =
-D__PLAT_RP2040__
# -D _POSIX_THREADS
build_src_filter =
- ${arduino_base.build_src_filter} - - - - - - - -
+ ${arduino_base.build_src_filter} - - - - - - - -
lib_ignore =
BluetoothOTA
@@ -20,5 +20,4 @@ lib_ignore =
lib_deps =
${arduino_base.lib_deps}
${environmental_base.lib_deps}
- jgromes/RadioLib@^6.1.0
- https://github.com/kokke/tiny-AES-c.git#f06ac37fc31dfdaca2e0d9bec83f90d5663c319b
\ No newline at end of file
+ rweather/Crypto
\ No newline at end of file
diff --git a/arch/stm32/stm32wl5e.ini b/arch/stm32/stm32wl5e.ini
index 524edd6b9..4483ff526 100644
--- a/arch/stm32/stm32wl5e.ini
+++ b/arch/stm32/stm32wl5e.ini
@@ -13,17 +13,16 @@ build_flags =
-DVECT_TAB_OFFSET=0x08000000
build_src_filter =
- ${arduino_base.build_src_filter} - - - - - - - - - - - -
+ ${arduino_base.build_src_filter} - - - - - - - - - - - - -
board_upload.offset_address = 0x08000000
upload_protocol = stlink
lib_deps =
${env.lib_deps}
- jgromes/RadioLib@^6.1.0
https://github.com/kokke/tiny-AES-c.git#f06ac37fc31dfdaca2e0d9bec83f90d5663c319b
https://github.com/littlefs-project/littlefs.git#v2.5.1
https://github.com/stm32duino/STM32FreeRTOS.git#10.3.1
lib_ignore =
- https://github.com/mathertel/OneButton#2.1.0
\ No newline at end of file
+ mathertel/OneButton
\ No newline at end of file
diff --git a/bin/Meshtastic_nRF52_factory_erase.uf2 b/bin/Meshtastic_nRF52_factory_erase.uf2
deleted file mode 100644
index fffff13ec..000000000
Binary files a/bin/Meshtastic_nRF52_factory_erase.uf2 and /dev/null differ
diff --git a/bin/Meshtastic_nRF52_factory_erase_v2.uf2 b/bin/Meshtastic_nRF52_factory_erase_v2.uf2
new file mode 100644
index 000000000..8a83bc8ec
Binary files /dev/null and b/bin/Meshtastic_nRF52_factory_erase_v2.uf2 differ
diff --git a/bin/build-native.sh b/bin/build-native.sh
index 8bc262860..7e9fcb632 100755
--- a/bin/build-native.sh
+++ b/bin/build-native.sh
@@ -2,8 +2,8 @@
set -e
-VERSION=`bin/buildinfo.py long`
-SHORT_VERSION=`bin/buildinfo.py short`
+VERSION=$(bin/buildinfo.py long)
+SHORT_VERSION=$(bin/buildinfo.py short)
OUTDIR=release/
@@ -13,11 +13,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
-
+platformio pkg update
pio run --environment native
-cp .pio/build/native/program $OUTDIR/meshtasticd_linux_amd64
-
+cp .pio/build/native/program "$OUTDIR/meshtasticd_linux_$(arch)"
cp bin/device-install.* $OUTDIR
cp bin/device-update.* $OUTDIR
-
diff --git a/bin/check-all.sh b/bin/check-all.sh
index 1475ac3ee..d1b50a8aa 100755
--- a/bin/check-all.sh
+++ b/bin/check-all.sh
@@ -13,7 +13,7 @@ if [[ $# -gt 0 ]]; then
# can override which environment by passing arg
BOARDS="$@"
else
- BOARDS="tlora-v2 tlora-v1 tlora_v1_3 tlora-v2-1-1.6 tbeam heltec-v1 heltec-v2.0 heltec-v2.1 tbeam0.7 meshtastic-diy-v1 rak4631 rak4631_eink rak11200 t-echo pca10059_diy_eink"
+ BOARDS="tlora-v2 tlora-v1 tlora_v1_3 tlora-v2-1-1.6 tbeam heltec-v2.0 heltec-v2.1 tbeam0.7 meshtastic-diy-v1 rak4631 rak4631_eink rak11200 t-echo canaryone pca10059_diy_eink"
fi
echo "BOARDS:${BOARDS}"
diff --git a/bin/check-dependencies.sh b/bin/check-dependencies.sh
index 27372487f..4aa0fccc0 100644
--- a/bin/check-dependencies.sh
+++ b/bin/check-dependencies.sh
@@ -5,17 +5,17 @@
set -e
if [[ $# -gt 0 ]]; then
- # can override which environment by passing arg
- BOARDS="$@"
+ # can override which environment by passing arg
+ BOARDS="$@"
else
- BOARDS="rak4631 rak4631_eink t-echo pca10059_diy_eink pico rak11200 tlora-v2 tlora-v1 tlora_v1_3 tlora-v2-1-1.6 tbeam heltec-v1 heltec-v2.0 heltec-v2.1 tbeam0.7 meshtastic-diy-v1 nano-g1 station-g1 m5stack-core m5stack-coreink tbeam-s3-core"
+ BOARDS="rak4631 rak4631_eink t-echo canaryone pca10059_diy_eink pico rak11200 tlora-v2 tlora-v1 tlora_v1_3 tlora-v2-1-1.6 tbeam heltec-v2.0 heltec-v2.1 tbeam0.7 meshtastic-diy-v1 nano-g1 station-g1 m5stack-core m5stack-coreink tbeam-s3-core"
fi
echo "BOARDS:${BOARDS}"
CHECK=""
for BOARD in $BOARDS; do
- CHECK="${CHECK} -e ${BOARD}"
+ CHECK="${CHECK} -e ${BOARD}"
done
echo $CHECK
diff --git a/bin/config-dist.yaml b/bin/config-dist.yaml
new file mode 100644
index 000000000..b5b105e4c
--- /dev/null
+++ b/bin/config-dist.yaml
@@ -0,0 +1,119 @@
+### Define your devices here using Broadcom pin numbering
+### Uncomment the block that corresponds to your hardware
+---
+Lora:
+# Module: sx1262 # Waveshare SX126X XXXM
+# DIO2_AS_RF_SWITCH: true
+# CS: 21
+# IRQ: 16
+# Busy: 20
+# Reset: 18
+
+# Module: sx1262 # Waveshare SX1302 LISTEN ONLY AT THIS TIME!
+# CS: 7
+# IRQ: 17
+# Reset: 22
+
+# Module: sx1262 # pinedio
+# CS: 0
+# IRQ: 10
+# Busy: 11
+# spidev: spidev0.1
+
+# Module: RF95 # Adafruit RFM9x
+# Reset: 25
+# CS: 7
+# IRQ: 22
+# Busy: 23
+
+# Module: RF95 # Elecrow Lora RFM95 IOT https://www.elecrow.com/lora-rfm95-iot-board-for-rpi.html
+# Reset: 22
+# CS: 7
+# IRQ: 25
+
+# Module: sx1280 # SX1280
+# CS: 21
+# IRQ: 16
+# Busy: 20
+# Reset: 18
+
+# DIO3_TCXO_VOLTAGE: true # the Waveshare Core1262 and others are known to need this setting
+
+# TXen: x # TX and RX enable pins
+# RXen: x
+
+### Set gpio chip to use in /dev/. Defaults to 0.
+### Notably the Raspberry Pi 5 puts the GPIO header on gpiochip4
+# gpiochip: 4
+
+### Specify the SPI device to use in /dev/. Defaults to spidev0.0
+### Some devices, like the pinedio, may require spidev0.1 as a workaround.
+# spidev: spidev0.0
+
+### Define GPIO buttons here:
+
+GPIO:
+# User: 6
+
+### Define GPS
+
+GPS:
+# SerialPath: /dev/ttyS0
+
+### Specify I2C device, or leave blank for none
+
+I2C:
+# I2CDevice: /dev/i2c-1
+
+### Set up SPI displays here. Note that I2C displays are generally auto-detected.
+
+Display:
+
+### Waveshare 2.8inch RPi LCD
+# Panel: ST7789
+# CS: 8
+# DC: 22 # Data/Command pin
+# Backlight: 18
+# Width: 240
+# Height: 320
+# Reset: 27
+# Rotate: true
+# Invert: true
+
+### Waveshare 1.44inch LCD HAT
+# Panel: ST7735S
+# CS: 8 #Chip Select
+# DC: 25 # Data/Command pin
+# Backlight: 24
+# Width: 128
+# Height: 128
+# Reset: 27
+# OffsetX: 0
+# OffsetY: 0
+
+### Adafruit PiTFT 2.8 TFT+Touchscreen
+# Panel: ILI9341
+# CS: 8
+# DC: 25
+# Backlight: 2
+# Width: 320
+# Height: 240
+
+Touchscreen:
+# Module: STMPE610
+# CS: 7
+# IRQ: 24
+
+# Module: XPT2046
+# CS: 7
+# IRQ: 17
+
+### Configure device for direct keyboard input
+
+Input:
+# KeyboardDevice: /dev/input/event0
+
+###
+
+Logging:
+ LogLevel: info # debug, info, warn, error
diff --git a/bin/exception_decoder.py b/bin/exception_decoder.py
index 8ef66b9bb..ec94ce20e 100755
--- a/bin/exception_decoder.py
+++ b/bin/exception_decoder.py
@@ -11,19 +11,22 @@ Meshtastic notes:
* version that's checked into meshtastic repo is based on: https://github.com/me21/EspArduinoExceptionDecoder
which adds in ESP32 Backtrace decoding.
* this also updates the defaults to use ESP32, instead of ESP8266 and defaults to the built firmware.bin
+* also updated the toolchain name, which will be set according to the platform
To use, copy the "Backtrace: 0x...." line to a file, e.g., backtrace.txt, then run:
$ bin/exception_decoder.py backtrace.txt
+For a platform other than ESP32, use the -p option, e.g.:
+$ bin/exception_decoder.py -p ESP32S3 backtrace.txt
+To specify a specific .elf file, use the -e option, e.g.:
+$ bin/exception_decoder.py -e firmware.elf backtrace.txt
"""
import argparse
+import os
import re
import subprocess
-from collections import namedtuple
-
import sys
-
-import os
+from collections import namedtuple
EXCEPTIONS = [
"Illegal instruction",
@@ -55,24 +58,39 @@ EXCEPTIONS = [
"LoadStorePrivilege: A load or store referenced a virtual address at a ring level less than CRING",
"reserved",
"LoadProhibited: A load referenced a page mapped with an attribute that does not permit loads",
- "StoreProhibited: A store referenced a page mapped with an attribute that does not permit stores"
+ "StoreProhibited: A store referenced a page mapped with an attribute that does not permit stores",
]
PLATFORMS = {
- "ESP8266": "lx106",
- "ESP32": "esp32"
+ "ESP8266": "xtensa-lx106",
+ "ESP32": "xtensa-esp32",
+ "ESP32S3": "xtensa-esp32s3",
+ "ESP32C3": "riscv32-esp",
+}
+TOOLS = {
+ "ESP8266": "xtensa",
+ "ESP32": "xtensa-esp32",
+ "ESP32S3": "xtensa-esp32s3",
+ "ESP32C3": "riscv32-esp",
}
-BACKTRACE_REGEX = re.compile(r"(?:\s+(0x40[0-2](?:\d|[a-f]|[A-F]){5}):0x(?:\d|[a-f]|[A-F]){8})\b")
+BACKTRACE_REGEX = re.compile(
+ r"(?:\s+(0x40[0-2](?:\d|[a-f]|[A-F]){5}):0x(?:\d|[a-f]|[A-F]){8})\b"
+)
EXCEPTION_REGEX = re.compile("^Exception \\((?P[0-9]*)\\):$")
-COUNTER_REGEX = re.compile('^epc1=(?P0x[0-9a-f]+) epc2=(?P0x[0-9a-f]+) epc3=(?P0x[0-9a-f]+) '
- 'excvaddr=(?P0x[0-9a-f]+) depc=(?P0x[0-9a-f]+)$')
+COUNTER_REGEX = re.compile(
+ "^epc1=(?P0x[0-9a-f]+) epc2=(?P0x[0-9a-f]+) epc3=(?P0x[0-9a-f]+) "
+ "excvaddr=(?P0x[0-9a-f]+) depc=(?P0x[0-9a-f]+)$"
+)
CTX_REGEX = re.compile("^ctx: (?P.+)$")
-POINTER_REGEX = re.compile('^sp: (?P[0-9a-f]+) end: (?P[0-9a-f]+) offset: (?P[0-9a-f]+)$')
-STACK_BEGIN = '>>>stack>>>'
-STACK_END = '<<[0-9a-f]+) end: (?P[0-9a-f]+) offset: (?P[0-9a-f]+)$"
+)
+STACK_BEGIN = ">>>stack>>>"
+STACK_END = "<<[0-9a-f]+):\W+(?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+)(\W.*)?$')
+ "^(?P[0-9a-f]+):\W+(?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+)(\W.*)?$"
+)
StackLine = namedtuple("StackLine", ["offset", "content"])
@@ -96,15 +114,18 @@ class ExceptionDataParser(object):
self.stack = []
def _parse_backtrace(self, line):
- if line.startswith('Backtrace:'):
- self.stack = [StackLine(offset=0, content=(addr,)) for addr in BACKTRACE_REGEX.findall(line)]
+ if line.startswith("Backtrace:"):
+ self.stack = [
+ StackLine(offset=0, content=(addr,))
+ for addr in BACKTRACE_REGEX.findall(line)
+ ]
return None
return self._parse_backtrace
def _parse_exception(self, line):
match = EXCEPTION_REGEX.match(line)
if match is not None:
- self.exception = int(match.group('exc'))
+ self.exception = int(match.group("exc"))
return self._parse_counters
return self._parse_exception
@@ -144,14 +165,22 @@ class ExceptionDataParser(object):
if line != STACK_END:
match = STACK_REGEX.match(line)
if match is not None:
- self.stack.append(StackLine(offset=match.group("off"),
- content=(match.group("c1"), match.group("c2"), match.group("c3"),
- match.group("c4"))))
+ self.stack.append(
+ StackLine(
+ offset=match.group("off"),
+ content=(
+ match.group("c1"),
+ match.group("c2"),
+ match.group("c3"),
+ match.group("c4"),
+ ),
+ )
+ )
return self._parse_stack_line
return None
def parse_file(self, file, platform, stack_only=False):
- if platform == 'ESP32':
+ if platform != "ESP8266":
func = self._parse_backtrace
else:
func = self._parse_exception
@@ -175,7 +204,9 @@ class AddressResolver(object):
self._address_map = {}
def _lookup(self, addresses):
- cmd = [self._tool, "-aipfC", "-e", self._elf] + [addr for addr in addresses if addr is not None]
+ cmd = [self._tool, "-aipfC", "-e", self._elf] + [
+ addr for addr in addresses if addr is not None
+ ]
if sys.version_info[0] < 3:
output = subprocess.check_output(cmd)
@@ -190,19 +221,27 @@ class AddressResolver(object):
match = line_regex.match(line)
if match is None:
- if last is not None and line.startswith('(inlined by)'):
- line = line [12:].strip()
- self._address_map[last] += ("\n \-> inlined by: " + line)
+ if last is not None and line.startswith("(inlined by)"):
+ line = line[12:].strip()
+ self._address_map[last] += "\n \-> inlined by: " + line
continue
- if match.group("result") == '?? ??:0':
+ if match.group("result") == "?? ??:0":
continue
self._address_map[match.group("addr")] = match.group("result")
last = match.group("addr")
def fill(self, parser):
- addresses = [parser.epc1, parser.epc2, parser.epc3, parser.excvaddr, parser.sp, parser.end, parser.offset]
+ addresses = [
+ parser.epc1,
+ parser.epc2,
+ parser.epc3,
+ parser.excvaddr,
+ parser.sp,
+ parser.end,
+ parser.offset,
+ ]
for line in parser.stack:
addresses.extend(line.content)
@@ -257,8 +296,10 @@ def print_stack(lines, resolver):
def print_result(parser, resolver, platform, full=True, stack_only=False):
- if platform == 'ESP8266' and not stack_only:
- print('Exception: {} ({})'.format(parser.exception, EXCEPTIONS[parser.exception]))
+ if platform == "ESP8266" and not stack_only:
+ print(
+ "Exception: {} ({})".format(parser.exception, EXCEPTIONS[parser.exception])
+ )
print("")
print_addr("epc1", parser.epc1, resolver)
@@ -285,15 +326,33 @@ def print_result(parser, resolver, platform, full=True, stack_only=False):
def parse_args():
parser = argparse.ArgumentParser(description="decode ESP Stacktraces.")
- parser.add_argument("-p", "--platform", help="The platform to decode from", choices=PLATFORMS.keys(),
- default="ESP32")
- parser.add_argument("-t", "--tool", help="Path to the xtensa toolchain",
- default="~/.platformio/packages/toolchain-xtensa32/")
- parser.add_argument("-e", "--elf", help="path to elf file",
- default=".pio/build/esp32/firmware.elf")
- parser.add_argument("-f", "--full", help="Print full stack dump", action="store_true")
- parser.add_argument("-s", "--stack_only", help="Decode only a stractrace", action="store_true")
- parser.add_argument("file", help="The file to read the exception data from ('-' for STDIN)", default="-")
+ parser.add_argument(
+ "-p",
+ "--platform",
+ help="The platform to decode from",
+ choices=PLATFORMS.keys(),
+ default="ESP32",
+ )
+ parser.add_argument(
+ "-t",
+ "--tool",
+ help="Path to the toolchain (without specific platform)",
+ default="~/.platformio/packages/toolchain-",
+ )
+ parser.add_argument(
+ "-e", "--elf", help="path to elf file", default=".pio/build/tbeam/firmware.elf"
+ )
+ parser.add_argument(
+ "-f", "--full", help="Print full stack dump", action="store_true"
+ )
+ parser.add_argument(
+ "-s", "--stack_only", help="Decode only a stractrace", action="store_true"
+ )
+ parser.add_argument(
+ "file",
+ help="The file to read the exception data from ('-' for STDIN)",
+ default="-",
+ )
return parser.parse_args()
@@ -309,10 +368,12 @@ if __name__ == "__main__":
sys.exit(1)
file = open(args.file, "r")
- addr2line = os.path.join(os.path.abspath(os.path.expanduser(args.tool)),
- "bin/xtensa-" + PLATFORMS[args.platform] + "-elf-addr2line")
- if os.name == 'nt':
- addr2line += '.exe'
+ addr2line = os.path.join(
+ os.path.abspath(os.path.expanduser(args.tool + TOOLS[args.platform])),
+ "bin/" + PLATFORMS[args.platform] + "-elf-addr2line",
+ )
+ if os.name == "nt":
+ addr2line += ".exe"
if not os.path.exists(addr2line):
print("ERROR: addr2line not found (" + addr2line + ")")
diff --git a/bin/lilygo_techo_bootloader-0.6.1.zip b/bin/lilygo_techo_bootloader-0.6.1.zip
new file mode 100644
index 000000000..34bd169f6
Binary files /dev/null and b/bin/lilygo_techo_bootloader-0.6.1.zip differ
diff --git a/bin/meshtasticd.service b/bin/meshtasticd.service
new file mode 100644
index 000000000..f15fdc871
--- /dev/null
+++ b/bin/meshtasticd.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=Meshtastic Native Daemon
+After=network-online.target
+
+[Service]
+User=root
+Group=root
+Type=simple
+ExecStart=/usr/sbin/meshtasticd
+
+[Install]
+WantedBy=multi-user.target
\ No newline at end of file
diff --git a/bin/native-install.sh b/bin/native-install.sh
new file mode 100755
index 000000000..cc6d968f9
--- /dev/null
+++ b/bin/native-install.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+cp "release/meshtasticd_linux_$(arch)" /usr/sbin/meshtasticd
+mkdir /etc/meshtasticd
+if [[ -f "/etc/meshtasticd/config.yaml" ]]; then
+ cp bin/config-dist.yaml /etc/meshtasticd/config-upgrade.yaml
+else
+ cp bin/config-dist.yaml /etc/meshtasticd/config.yaml
+fi
+cp bin/meshtasticd.service /usr/lib/systemd/system/meshtasticd.service
diff --git a/bin/update-lilygo_techo_bootloader-0.6.1_nosd.uf2 b/bin/update-lilygo_techo_bootloader-0.6.1_nosd.uf2
new file mode 100644
index 000000000..ea293e357
Binary files /dev/null and b/bin/update-lilygo_techo_bootloader-0.6.1_nosd.uf2 differ
diff --git a/boards/canaryone.json b/boards/canaryone.json
new file mode 100644
index 000000000..d8f966a47
--- /dev/null
+++ b/boards/canaryone.json
@@ -0,0 +1,49 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "nrf52840_s140_v6.ld"
+ },
+ "core": "nRF5",
+ "cpu": "cortex-m4",
+ "extra_flags": "-DARDUINO_NRF52840_CANARY -DNRF52840_XXAA",
+ "f_cpu": "64000000L",
+ "hwids": [["0x239A", "0x4405"]],
+ "usb_product": "CanaryOne",
+ "mcu": "nrf52840",
+ "variant": "canaryone",
+ "variants_dir": "variants",
+ "bsp": {
+ "name": "adafruit"
+ },
+ "softdevice": {
+ "sd_flags": "-DS140",
+ "sd_name": "s140",
+ "sd_version": "6.1.1",
+ "sd_fwid": "0x00B6"
+ },
+ "bootloader": {
+ "settings_addr": "0xFF000"
+ }
+ },
+ "connectivity": ["bluetooth"],
+ "debug": {
+ "jlink_device": "nRF52840_xxAA",
+ "onboard_tools": ["jlink"],
+ "svd_path": "nrf52840.svd",
+ "openocd_target": "nrf52840-mdk-rs"
+ },
+ "frameworks": ["arduino"],
+ "name": "Canary (Adafruit BSP)",
+ "upload": {
+ "maximum_ram_size": 248832,
+ "maximum_size": 815104,
+ "speed": 115200,
+ "protocol": "nrfutil",
+ "protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
+ "use_1200bps_touch": true,
+ "require_upload_port": true,
+ "wait_for_upload_port": true
+ },
+ "url": "https://canaryradio.io/",
+ "vendor": "Canary Radio Company"
+}
diff --git a/boards/eink0.1.json b/boards/eink0.1.json
index 28862c5d4..cb636ea3f 100644
--- a/boards/eink0.1.json
+++ b/boards/eink0.1.json
@@ -29,7 +29,8 @@
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
- "svd_path": "nrf52840.svd"
+ "svd_path": "nrf52840.svd",
+ "openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "TTGO eink (Adafruit BSP)",
diff --git a/boards/esp32-s3-pico.json b/boards/esp32-s3-pico.json
new file mode 100644
index 000000000..8f8c6fdb7
--- /dev/null
+++ b/boards/esp32-s3-pico.json
@@ -0,0 +1,40 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "esp32s3_out.ld",
+ "partitions": "default_16MB.csv"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-DARDUINO_ESP32S3_DEV",
+ "-DARDUINO_USB_MODE=1",
+ "-DARDUINO_RUNNING_CORE=1",
+ "-DARDUINO_EVENT_RUNNING_CORE=1"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "80000000L",
+ "flash_mode": "qio",
+ "hwids": [["0x303A", "0x1001"]],
+ "mcu": "esp32s3",
+ "variant": "esp32s3"
+ },
+ "connectivity": ["wifi", "bluetooth", "lora"],
+ "debug": {
+ "default_tool": "esp-builtin",
+ "onboard_tools": ["esp-builtin"],
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": ["arduino", "espidf"],
+ "name": "Waveshare ESP32-S3-Pico (16 MB FLASH, 2 MB PSRAM)",
+ "upload": {
+ "flash_size": "16MB",
+ "maximum_ram_size": 327680,
+ "maximum_size": 16777216,
+ "use_1200bps_touch": true,
+ "wait_for_upload_port": true,
+ "require_upload_port": true,
+ "speed": 921600
+ },
+ "url": "https://www.waveshare.com/esp32-s3-pico.htm",
+ "vendor": "Waveshare"
+}
diff --git a/boards/lora-relay-v1.json b/boards/lora-relay-v1.json
index ca4e2f0ab..b390b8404 100644
--- a/boards/lora-relay-v1.json
+++ b/boards/lora-relay-v1.json
@@ -29,7 +29,8 @@
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
- "svd_path": "nrf52840.svd"
+ "svd_path": "nrf52840.svd",
+ "openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "Meshtastic Lora Relay V1 (Adafruit BSP)",
diff --git a/boards/lora-relay-v2.json b/boards/lora-relay-v2.json
index 2cca8ae9a..52b775e58 100644
--- a/boards/lora-relay-v2.json
+++ b/boards/lora-relay-v2.json
@@ -29,7 +29,8 @@
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
- "svd_path": "nrf52840.svd"
+ "svd_path": "nrf52840.svd",
+ "openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "Meshtastic Lora Relay V1 (Adafruit BSP)",
diff --git a/boards/lora_isp4520.json b/boards/lora_isp4520.json
index 971512b28..8125aa666 100644
--- a/boards/lora_isp4520.json
+++ b/boards/lora_isp4520.json
@@ -22,7 +22,8 @@
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52832_xxAA",
- "svd_path": "nrf52.svd"
+ "svd_path": "nrf52.svd",
+ "openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "lora ISP4520",
diff --git a/boards/nordic_pca10059.json b/boards/nordic_pca10059.json
index 214c2851d..b99e3c763 100644
--- a/boards/nordic_pca10059.json
+++ b/boards/nordic_pca10059.json
@@ -32,7 +32,8 @@
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
- "svd_path": "nrf52840.svd"
+ "svd_path": "nrf52840.svd",
+ "openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "nRF52840 Dongle",
diff --git a/boards/nrf52840_dk.json b/boards/nrf52840_dk.json
index 8d07575bf..8a16e854f 100644
--- a/boards/nrf52840_dk.json
+++ b/boards/nrf52840_dk.json
@@ -29,7 +29,8 @@
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
- "svd_path": "nrf52840.svd"
+ "svd_path": "nrf52840.svd",
+ "openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "A modified NRF52840-DK devboard (Adafruit BSP)",
diff --git a/boards/nrf52840_dk_modified.json b/boards/nrf52840_dk_modified.json
index 0462c55f8..2932cb4b9 100644
--- a/boards/nrf52840_dk_modified.json
+++ b/boards/nrf52840_dk_modified.json
@@ -29,7 +29,8 @@
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
- "svd_path": "nrf52840.svd"
+ "svd_path": "nrf52840.svd",
+ "openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "A modified NRF52840-DK devboard (Adafruit BSP)",
diff --git a/boards/ppr.json b/boards/ppr.json
index 5283fdc4e..15e3025c0 100644
--- a/boards/ppr.json
+++ b/boards/ppr.json
@@ -29,7 +29,8 @@
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
- "svd_path": "nrf52840.svd"
+ "svd_path": "nrf52840.svd",
+ "openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "Meshtastic PPR (Adafruit BSP)",
diff --git a/boards/ppr1.json b/boards/ppr1.json
index 4c0528245..35bf7d1e4 100644
--- a/boards/ppr1.json
+++ b/boards/ppr1.json
@@ -29,7 +29,8 @@
"debug": {
"jlink_device": "nRF52833_xxAA",
"onboard_tools": ["jlink"],
- "svd_path": "nrf52833.svd"
+ "svd_path": "nrf52833.svd",
+ "openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "Meshtastic PPR1 (Adafruit BSP)",
diff --git a/boards/t-echo.json b/boards/t-echo.json
index 957ba01e3..fcfc8c50b 100644
--- a/boards/t-echo.json
+++ b/boards/t-echo.json
@@ -9,6 +9,7 @@
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x4405"],
+ ["0x239A", "0x0029"],
["0x239A", "0x002A"]
],
"usb_product": "TTGO_eink",
@@ -32,7 +33,8 @@
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
- "svd_path": "nrf52840.svd"
+ "svd_path": "nrf52840.svd",
+ "openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "TTGO eink (Adafruit BSP)",
diff --git a/boards/t-watch-s3.json b/boards/t-watch-s3.json
index 080389f39..e6e363305 100644
--- a/boards/t-watch-s3.json
+++ b/boards/t-watch-s3.json
@@ -16,7 +16,10 @@
"f_cpu": "240000000L",
"f_flash": "80000000L",
"flash_mode": "qio",
- "hwids": [["0x303A", "0x1001"]],
+ "hwids": [
+ ["0x303A", "0x1001"],
+ ["0x303A", "0x0002"]
+ ],
"mcu": "esp32s3",
"variant": "t-watch-s3"
},
diff --git a/boards/wiscore_rak4600.json b/boards/wiscore_rak4600.json
index 9969ef26e..0dec90a79 100644
--- a/boards/wiscore_rak4600.json
+++ b/boards/wiscore_rak4600.json
@@ -32,7 +32,8 @@
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52832_xxAA",
- "svd_path": "nrf52.svd"
+ "svd_path": "nrf52.svd",
+ "openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino", "zephyr"],
"name": "Adafruit Bluefruit nRF52832 Feather",
diff --git a/boards/wiscore_rak4631.json b/boards/wiscore_rak4631.json
index 149492688..6dec3f7cb 100644
--- a/boards/wiscore_rak4631.json
+++ b/boards/wiscore_rak4631.json
@@ -32,7 +32,8 @@
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
- "svd_path": "nrf52840.svd"
+ "svd_path": "nrf52840.svd",
+ "openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "WisCore RAK4631 Board",
diff --git a/boards/xiao_ble_sense.json b/boards/xiao_ble_sense.json
index 09a28c25d..8e0212b3e 100644
--- a/boards/xiao_ble_sense.json
+++ b/boards/xiao_ble_sense.json
@@ -31,7 +31,8 @@
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
- "svd_path": "nrf52840.svd"
+ "svd_path": "nrf52840.svd",
+ "openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "Seeed Xiao BLE Sense",
diff --git a/platformio.ini b/platformio.ini
index a6ad6f873..0033b6e46 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -2,7 +2,7 @@
; https://docs.platformio.org/page/projectconf.html
[platformio]
-;default_envs = tbeam
+default_envs = tbeam
;default_envs = pico
;default_envs = tbeam-s3-core
;default_envs = tbeam0.7
@@ -10,13 +10,16 @@
;default_envs = heltec-v2_0
;default_envs = heltec-v2_1
;default_envs = heltec-wireless-tracker
+;default_envs = chatter2
;default_envs = tlora-v1
;default_envs = tlora_v1_3
;default_envs = tlora-v2
;default_envs = tlora-v2-1-1_6
+;default_envs = tlora-v2-1-1_6-tcxo
;default_envs = tlora-t3s3-v1
;default_envs = lora-relay-v1 # nrf board
;default_envs = t-echo
+;default_envs = canaryone
;default_envs = nrf52840dk-geeksville
;default_envs = native # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
;default_envs = nano-g1
@@ -26,7 +29,8 @@
;default_envs = meshtastic-dr-dev
;default_envs = m5stack-coreink
;default_envs = rak4631
-default_envs = wio-e5
+;default_envs = rak10701
+;default_envs = wio-e5
extra_configs =
arch/*/*.ini
@@ -50,9 +54,11 @@ build_flags = -Wno-missing-field-initializers
-DRADIOLIB_EXCLUDE_NRF24
-DRADIOLIB_EXCLUDE_RF69
-DRADIOLIB_EXCLUDE_SX1231
+ -DRADIOLIB_EXCLUDE_SX1233
-DRADIOLIB_EXCLUDE_SI443X
-DRADIOLIB_EXCLUDE_RFM2X
-DRADIOLIB_EXCLUDE_AFSK
+ -DRADIOLIB_EXCLUDE_BELL
-DRADIOLIB_EXCLUDE_HELLSCHREIBER
-DRADIOLIB_EXCLUDE_MORSE
-DRADIOLIB_EXCLUDE_RTTY
@@ -67,10 +73,11 @@ build_flags = -Wno-missing-field-initializers
monitor_speed = 115200
lib_deps =
- https://github.com/meshtastic/esp8266-oled-ssd1306.git#b38094e03dfa964fbc0e799bc374e91a605c1223 ; ESP8266_SSD1306
- https://github.com/mathertel/OneButton#2.1.0 ; OneButton library for non-blocking button debounce
+ jgromes/RadioLib@^6.4.0
+ https://github.com/meshtastic/esp8266-oled-ssd1306.git#ee628ee6c9588d4c56c9e3da35f0fc9448ad54a8 ; ESP8266_SSD1306
+ mathertel/OneButton@^2.5.0 ; OneButton library for non-blocking button debounce
https://github.com/meshtastic/arduino-fsm.git#7db3702bf0cfe97b783d6c72595e3f38e0b19159
- https://github.com/meshtastic/TinyGPSPlus.git#076e8d2c8fb702d9be5b08c55b93ff76f8af7e61
+ https://github.com/meshtastic/TinyGPSPlus.git#2044b2c51e91ab4cd8cc93b15e40658cd808dd06
https://github.com/meshtastic/ArduinoThread.git#72921ac222eed6f526ba1682023cee290d9aa1b3
nanopb/Nanopb@^0.4.7
erriez/ErriezCRC32@^1.0.1
@@ -92,7 +99,7 @@ lib_deps =
end2endzone/NonBlockingRTTTL@^1.3.0
https://github.com/meshtastic/SparkFun_ATECCX08a_Arduino_Library.git#5cf62b36c6f30bc72a07bdb2c11fc9a22d1e31da
-build_flags = ${env.build_flags} -Os -DRADIOLIB_SPI_PARANOID=0
+build_flags = ${env.build_flags} -Os
build_src_filter = ${env.build_src_filter} -
; Common libs for communicating over TCP/IP networks such as MQTT
@@ -113,6 +120,7 @@ lib_deps =
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
@@ -121,4 +129,4 @@ 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/BMA423_Library@^0.0.1
\ No newline at end of file
+ https://github.com/lewisxhe/BMA423_Library@^0.0.1
diff --git a/protobufs b/protobufs
index 6290ee0f6..24edea644 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 6290ee0f6aa15939ee582c3c59bc7a048cc0478f
+Subproject commit 24edea64429de4474c00d09990ef4c496614dc5d
diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h
index da5695368..744f0ad64 100644
--- a/src/AccelerometerThread.h
+++ b/src/AccelerometerThread.h
@@ -42,7 +42,7 @@ namespace concurrency
class AccelerometerThread : public concurrency::OSThread
{
public:
- AccelerometerThread(ScanI2C::DeviceType type = ScanI2C::DeviceType::NONE) : OSThread("AccelerometerThread")
+ explicit AccelerometerThread(ScanI2C::DeviceType type) : OSThread("AccelerometerThread")
{
if (accelerometer_found.port == ScanI2C::I2CPort::NO_I2C) {
LOG_DEBUG("AccelerometerThread disabling due to no sensors found\n");
diff --git a/src/AmbientLightingThread.h b/src/AmbientLightingThread.h
index 0dd0fdf4a..98ccedde4 100644
--- a/src/AmbientLightingThread.h
+++ b/src/AmbientLightingThread.h
@@ -10,7 +10,7 @@ namespace concurrency
class AmbientLightingThread : public concurrency::OSThread
{
public:
- AmbientLightingThread(ScanI2C::DeviceType type) : OSThread("AmbientLightingThread")
+ explicit AmbientLightingThread(ScanI2C::DeviceType type) : OSThread("AmbientLightingThread")
{
// Uncomment to test module
// moduleConfig.ambient_lighting.led_state = true;
diff --git a/src/AudioThread.h b/src/AudioThread.h
new file mode 100644
index 000000000..c9f253440
--- /dev/null
+++ b/src/AudioThread.h
@@ -0,0 +1,77 @@
+#pragma once
+#include "PowerFSM.h"
+#include "concurrency/OSThread.h"
+#include "configuration.h"
+#include "main.h"
+#include "sleep.h"
+
+#ifdef HAS_I2S
+#include
+#include
+#include
+#include
+
+#define AUDIO_THREAD_INTERVAL_MS 100
+
+class AudioThread : public concurrency::OSThread
+{
+ public:
+ AudioThread() : OSThread("AudioThread") { initOutput(); }
+
+ void beginRttl(const void *data, uint32_t len)
+ {
+ setCPUFast(true);
+ rtttlFile = new AudioFileSourcePROGMEM(data, len);
+ i2sRtttl = new AudioGeneratorRTTTL();
+ i2sRtttl->begin(rtttlFile, audioOut);
+ }
+
+ bool isPlaying()
+ {
+ if (i2sRtttl != nullptr) {
+ return i2sRtttl->isRunning() && i2sRtttl->loop();
+ }
+ return false;
+ }
+
+ void stop()
+ {
+ if (i2sRtttl != nullptr) {
+ i2sRtttl->stop();
+ delete i2sRtttl;
+ i2sRtttl = nullptr;
+ }
+ if (rtttlFile != nullptr) {
+ delete rtttlFile;
+ rtttlFile = nullptr;
+ }
+
+ setCPUFast(false);
+ }
+
+ protected:
+ int32_t runOnce() override
+ {
+ canSleep = true; // Assume we should not keep the board awake
+
+ // if (i2sRtttl != nullptr && i2sRtttl->isRunning()) {
+ // i2sRtttl->loop();
+ // }
+ return AUDIO_THREAD_INTERVAL_MS;
+ }
+
+ private:
+ void initOutput()
+ {
+ audioOut = new AudioOutputI2S(1, AudioOutputI2S::EXTERNAL_I2S);
+ audioOut->SetPinout(DAC_I2S_BCK, DAC_I2S_WS, DAC_I2S_DOUT);
+ audioOut->SetGain(0.2);
+ };
+
+ AudioGeneratorRTTTL *i2sRtttl = nullptr;
+ AudioOutputI2S *audioOut;
+
+ AudioFileSourcePROGMEM *rtttlFile;
+};
+
+#endif
diff --git a/src/ButtonThread.cpp b/src/ButtonThread.cpp
new file mode 100644
index 000000000..84d433285
--- /dev/null
+++ b/src/ButtonThread.cpp
@@ -0,0 +1,219 @@
+#include "ButtonThread.h"
+#include "GPS.h"
+#include "MeshService.h"
+#include "PowerFSM.h"
+#include "RadioLibInterface.h"
+#include "buzz.h"
+#include "graphics/Screen.h"
+#include "main.h"
+#include "modules/ExternalNotificationModule.h"
+#include "power.h"
+#ifdef ARCH_PORTDUINO
+#include "platform/portduino/PortduinoGlue.h"
+#endif
+
+#define DEBUG_BUTTONS 0
+#if DEBUG_BUTTONS
+#define LOG_BUTTON(...) LOG_DEBUG(__VA_ARGS__)
+#else
+#define LOG_BUTTON(...)
+#endif
+
+using namespace concurrency;
+
+volatile ButtonThread::ButtonEventType ButtonThread::btnEvent = ButtonThread::BUTTON_EVENT_NONE;
+
+ButtonThread::ButtonThread() : OSThread("Button")
+{
+#if defined(ARCH_PORTDUINO) || defined(BUTTON_PIN)
+#if defined(ARCH_PORTDUINO)
+ if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) {
+ userButton = OneButton(settingsMap[user], true, true);
+ LOG_DEBUG("Using GPIO%02d for button\n", settingsMap[user]);
+ }
+#elif defined(BUTTON_PIN)
+ int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN;
+ this->userButton = OneButton(pin, true, true);
+ LOG_DEBUG("Using GPIO%02d for button\n", pin);
+#endif
+
+#ifdef INPUT_PULLUP_SENSE
+ // Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
+ pinMode(pin, INPUT_PULLUP_SENSE);
+#endif
+ userButton.attachClick(userButtonPressed);
+ userButton.setClickMs(250);
+ userButton.setPressMs(c_longPressTime);
+ userButton.setDebounceMs(1);
+ userButton.attachDoubleClick(userButtonDoublePressed);
+ userButton.attachMultiClick(userButtonMultiPressed);
+#ifndef T_DECK // T-Deck immediately wakes up after shutdown, so disable this function
+ userButton.attachLongPressStart(userButtonPressedLongStart);
+ userButton.attachLongPressStop(userButtonPressedLongStop);
+#endif
+#if defined(ARCH_PORTDUINO)
+ if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC)
+ wakeOnIrq(settingsMap[user], FALLING);
+#else
+ static OneButton *pBtn = &userButton; // only one instance of ButtonThread is created, so static is safe
+ attachInterrupt(
+ pin,
+ []() {
+ BaseType_t higherWake = 0;
+ mainDelay.interruptFromISR(&higherWake);
+ pBtn->tick();
+ },
+ CHANGE);
+#endif
+#endif
+#ifdef BUTTON_PIN_ALT
+ userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
+#ifdef INPUT_PULLUP_SENSE
+ // Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
+ pinMode(BUTTON_PIN_ALT, INPUT_PULLUP_SENSE);
+#endif
+ userButtonAlt.attachClick(userButtonPressed);
+ userButtonAlt.setClickMs(250);
+ userButtonAlt.setPressMs(c_longPressTime);
+ userButtonAlt.setDebounceMs(1);
+ userButtonAlt.attachDoubleClick(userButtonDoublePressed);
+ userButtonAlt.attachLongPressStart(userButtonPressedLongStart);
+ userButtonAlt.attachLongPressStop(userButtonPressedLongStop);
+ wakeOnIrq(BUTTON_PIN_ALT, FALLING);
+#endif
+
+#ifdef BUTTON_PIN_TOUCH
+ userButtonTouch = OneButton(BUTTON_PIN_TOUCH, true, true);
+ userButtonTouch.attachClick(touchPressed);
+ wakeOnIrq(BUTTON_PIN_TOUCH, FALLING);
+#endif
+}
+
+int32_t ButtonThread::runOnce()
+{
+ // If the button is pressed we suppress CPU sleep until release
+ canSleep = true; // Assume we should not keep the board awake
+
+#if defined(BUTTON_PIN)
+ userButton.tick();
+ canSleep &= userButton.isIdle();
+#elif defined(ARCH_PORTDUINO)
+ if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) {
+ userButton.tick();
+ canSleep &= userButton.isIdle();
+ }
+#endif
+#ifdef BUTTON_PIN_ALT
+ userButtonAlt.tick();
+ canSleep &= userButtonAlt.isIdle();
+#endif
+#ifdef BUTTON_PIN_TOUCH
+ userButtonTouch.tick();
+ canSleep &= userButtonTouch.isIdle();
+#endif
+
+ if (btnEvent != BUTTON_EVENT_NONE) {
+ switch (btnEvent) {
+ case BUTTON_EVENT_PRESSED: {
+ LOG_BUTTON("press!\n");
+#ifdef BUTTON_PIN
+ if (((config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN) !=
+ moduleConfig.canned_message.inputbroker_pin_press) ||
+ !(moduleConfig.canned_message.updown1_enabled || moduleConfig.canned_message.rotary1_enabled) ||
+ !moduleConfig.canned_message.enabled) {
+ powerFSM.trigger(EVENT_PRESS);
+ }
+#endif
+#if defined(ARCH_PORTDUINO)
+ if ((settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) &&
+ (settingsMap[user] != moduleConfig.canned_message.inputbroker_pin_press) ||
+ !moduleConfig.canned_message.enabled) {
+ powerFSM.trigger(EVENT_PRESS);
+ }
+#endif
+ break;
+ }
+
+ case BUTTON_EVENT_DOUBLE_PRESSED: {
+ LOG_BUTTON("Double press!\n");
+#if defined(USE_EINK) && defined(PIN_EINK_EN)
+ digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
+#endif
+ service.refreshLocalMeshNode();
+ service.sendNetworkPing(NODENUM_BROADCAST, true);
+ if (screen)
+ screen->print("Sent ad-hoc ping\n");
+ break;
+ }
+
+ case BUTTON_EVENT_MULTI_PRESSED: {
+ LOG_BUTTON("Multi press!\n");
+ if (!config.device.disable_triple_click && (gps != nullptr)) {
+ gps->toggleGpsMode();
+ if (screen)
+ screen->forceDisplay();
+ }
+ break;
+ }
+
+ case BUTTON_EVENT_LONG_PRESSED: {
+ LOG_BUTTON("Long press!\n");
+ powerFSM.trigger(EVENT_PRESS);
+ if (screen)
+ screen->startShutdownScreen();
+ playBeep();
+ break;
+ }
+
+ // Do actual shutdown when button released, otherwise the button release
+ // may wake the board immediatedly.
+ case BUTTON_EVENT_LONG_RELEASED: {
+ LOG_INFO("Shutdown from long press\n");
+ playShutdownMelody();
+ delay(3000);
+ power->shutdown();
+ break;
+ }
+ case BUTTON_EVENT_TOUCH_PRESSED: {
+ LOG_BUTTON("Touch press!\n");
+ if (screen)
+ screen->forceDisplay();
+ break;
+ }
+ default:
+ break;
+ }
+ btnEvent = BUTTON_EVENT_NONE;
+ }
+
+ return 50;
+}
+
+/**
+ * Watch a GPIO and if we get an IRQ, wake the main thread.
+ * Use to add wake on button press
+ */
+void ButtonThread::wakeOnIrq(int irq, int mode)
+{
+ attachInterrupt(
+ irq,
+ [] {
+ BaseType_t higherWake = 0;
+ mainDelay.interruptFromISR(&higherWake);
+ },
+ FALLING);
+}
+
+void ButtonThread::userButtonPressedLongStart()
+{
+ if (millis() > c_holdOffTime) {
+ btnEvent = BUTTON_EVENT_LONG_PRESSED;
+ }
+}
+
+void ButtonThread::userButtonPressedLongStop()
+{
+ if (millis() > c_holdOffTime) {
+ btnEvent = BUTTON_EVENT_LONG_RELEASED;
+ }
+}
diff --git a/src/ButtonThread.h b/src/ButtonThread.h
index a8a89e82e..554c1f0c4 100644
--- a/src/ButtonThread.h
+++ b/src/ButtonThread.h
@@ -1,33 +1,29 @@
-#include "PowerFSM.h"
-#include "RadioLibInterface.h"
-#include "buzz.h"
+#pragma once
+
+#include "OneButton.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
-#include "graphics/Screen.h"
-#include "main.h"
-#include "power.h"
-#include
-
-namespace concurrency
-{
-/**
- * Watch a GPIO and if we get an IRQ, wake the main thread.
- * Use to add wake on button press
- */
-void wakeOnIrq(int irq, int mode)
-{
- attachInterrupt(
- irq,
- [] {
- BaseType_t higherWake = 0;
- mainDelay.interruptFromISR(&higherWake);
- },
- FALLING);
-}
class ButtonThread : public concurrency::OSThread
{
-// Prepare for button presses
+ public:
+ static const uint32_t c_longPressTime = 5000; // shutdown after 5s
+ static const uint32_t c_holdOffTime = 30000; // hold off 30s after boot
+
+ enum ButtonEventType {
+ BUTTON_EVENT_NONE,
+ BUTTON_EVENT_PRESSED,
+ BUTTON_EVENT_DOUBLE_PRESSED,
+ BUTTON_EVENT_MULTI_PRESSED,
+ BUTTON_EVENT_LONG_PRESSED,
+ BUTTON_EVENT_LONG_RELEASED,
+ BUTTON_EVENT_TOUCH_PRESSED
+ };
+
+ ButtonThread();
+ int32_t runOnce() override;
+
+ private:
#ifdef BUTTON_PIN
OneButton userButton;
#endif
@@ -37,166 +33,20 @@ class ButtonThread : public concurrency::OSThread
#ifdef BUTTON_PIN_TOUCH
OneButton userButtonTouch;
#endif
- static bool shutdown_on_long_stop;
-
- public:
- static uint32_t longPressTime;
-
- // callback returns the period for the next callback invocation (or 0 if we should no longer be called)
- ButtonThread() : OSThread("Button")
- {
-#ifdef BUTTON_PIN
- userButton = OneButton(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, true, true);
-#ifdef INPUT_PULLUP_SENSE
- // Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
- pinMode(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, INPUT_PULLUP_SENSE);
-#endif
- userButton.attachClick(userButtonPressed);
- userButton.setClickMs(300);
- userButton.attachDuringLongPress(userButtonPressedLong);
- userButton.attachDoubleClick(userButtonDoublePressed);
- userButton.attachMultiClick(userButtonMultiPressed);
- userButton.attachLongPressStart(userButtonPressedLongStart);
- userButton.attachLongPressStop(userButtonPressedLongStop);
- wakeOnIrq(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN, FALLING);
-#endif
-#ifdef BUTTON_PIN_ALT
- userButtonAlt = OneButton(BUTTON_PIN_ALT, true, true);
-#ifdef INPUT_PULLUP_SENSE
- // Some platforms (nrf52) have a SENSE variant which allows wake from sleep - override what OneButton did
- pinMode(BUTTON_PIN_ALT, INPUT_PULLUP_SENSE);
-#endif
- userButtonAlt.attachClick(userButtonPressed);
- userButtonAlt.attachDuringLongPress(userButtonPressedLong);
- userButtonAlt.attachDoubleClick(userButtonDoublePressed);
- userButtonAlt.attachLongPressStart(userButtonPressedLongStart);
- userButtonAlt.attachLongPressStop(userButtonPressedLongStop);
- wakeOnIrq(BUTTON_PIN_ALT, FALLING);
+#if defined(ARCH_PORTDUINO)
+ OneButton userButton;
#endif
-#ifdef BUTTON_PIN_TOUCH
- userButtonTouch = OneButton(BUTTON_PIN_TOUCH, true, true);
- userButtonTouch.attachClick(touchPressed);
- wakeOnIrq(BUTTON_PIN_TOUCH, FALLING);
-#endif
- }
+ // set during IRQ
+ static volatile ButtonEventType btnEvent;
- protected:
- /// If the button is pressed we suppress CPU sleep until release
- int32_t runOnce() override
- {
- canSleep = true; // Assume we should not keep the board awake
+ static void wakeOnIrq(int irq, int mode);
-#ifdef BUTTON_PIN
- userButton.tick();
- canSleep &= userButton.isIdle();
-#endif
-#ifdef BUTTON_PIN_ALT
- userButtonAlt.tick();
- canSleep &= userButtonAlt.isIdle();
-#endif
-#ifdef BUTTON_PIN_TOUCH
- userButtonTouch.tick();
- canSleep &= userButtonTouch.isIdle();
-#endif
- // if (!canSleep) LOG_DEBUG("Suppressing sleep!\n");
- // else LOG_DEBUG("sleep ok\n");
-
- return 50;
- }
-
- private:
- static void touchPressed()
- {
- screen->forceDisplay();
- LOG_DEBUG("touch press!\n");
- }
-
- static void userButtonPressed()
- {
- // LOG_DEBUG("press!\n");
-#ifdef BUTTON_PIN
- if (((config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN) !=
- moduleConfig.canned_message.inputbroker_pin_press) ||
- !moduleConfig.canned_message.enabled) {
- powerFSM.trigger(EVENT_PRESS);
- }
-#endif
- }
- static void userButtonPressedLong()
- {
- // LOG_DEBUG("Long press!\n");
- // If user button is held down for 5 seconds, shutdown the device.
- if ((millis() - longPressTime > 5000) && (longPressTime > 0)) {
-#if defined(ARCH_NRF52) || defined(ARCH_ESP32)
- // Do actual shutdown when button released, otherwise the button release
- // may wake the board immediatedly.
- if ((!shutdown_on_long_stop) && (millis() > 30 * 1000)) {
- screen->startShutdownScreen();
- LOG_INFO("Shutdown from long press");
- playBeep();
-#ifdef PIN_LED1
- ledOff(PIN_LED1);
-#endif
-#ifdef PIN_LED2
- ledOff(PIN_LED2);
-#endif
-#ifdef PIN_LED3
- ledOff(PIN_LED3);
-#endif
- shutdown_on_long_stop = true;
- }
-#endif
- } else {
- // LOG_DEBUG("Long press %u\n", (millis() - longPressTime));
- }
- }
-
- static void userButtonDoublePressed()
- {
-#if defined(USE_EINK) && defined(PIN_EINK_EN)
- digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
-#endif
- screen->print("Sent ad-hoc ping\n");
- service.refreshLocalMeshNode();
- service.sendNetworkPing(NODENUM_BROADCAST, true);
- }
-
- static void userButtonMultiPressed()
- {
- if (!config.device.disable_triple_click && (gps != nullptr)) {
- config.position.gps_enabled = !(config.position.gps_enabled);
- if (config.position.gps_enabled) {
- LOG_DEBUG("Flag set to true to restore power\n");
- gps->enable();
-
- } else {
- LOG_DEBUG("Flag set to false for gps power\n");
- gps->disable();
- }
- }
- }
-
- static void userButtonPressedLongStart()
- {
- if (millis() > 30 * 1000) {
- LOG_DEBUG("Long press start!\n");
- longPressTime = millis();
- }
- }
-
- static void userButtonPressedLongStop()
- {
- if (millis() > 30 * 1000) {
- LOG_DEBUG("Long press stop!\n");
- longPressTime = 0;
- if (shutdown_on_long_stop) {
- playShutdownMelody();
- delay(3000);
- power->shutdown();
- }
- }
- }
+ // IRQ callbacks
+ static void touchPressed() { btnEvent = BUTTON_EVENT_TOUCH_PRESSED; }
+ static void userButtonPressed() { btnEvent = BUTTON_EVENT_PRESSED; }
+ static void userButtonDoublePressed() { btnEvent = BUTTON_EVENT_DOUBLE_PRESSED; }
+ static void userButtonMultiPressed() { btnEvent = BUTTON_EVENT_MULTI_PRESSED; }
+ static void userButtonPressedLongStart();
+ static void userButtonPressedLongStop();
};
-
-} // namespace concurrency
\ No newline at end of file
diff --git a/src/DebugConfiguration.h b/src/DebugConfiguration.h
index 59ce043bc..f0686b811 100644
--- a/src/DebugConfiguration.h
+++ b/src/DebugConfiguration.h
@@ -28,7 +28,7 @@
#define DEBUG_PORT (*console) // Serial debug port
#ifdef USE_SEGGER
-#define DEBUG_PORT
+// #undef DEBUG_PORT
#define LOG_DEBUG(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#define LOG_INFO(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#define LOG_WARN(...) SEGGER_RTT_printf(0, __VA_ARGS__)
diff --git a/src/Observer.h b/src/Observer.h
index 555dcd1e9..6e1ec44c8 100644
--- a/src/Observer.h
+++ b/src/Observer.h
@@ -10,12 +10,12 @@ template class Observable;
*/
template class Observer
{
- std::list *> observed;
+ std::list *> observables;
public:
virtual ~Observer();
- /// Stop watching the obserable
+ /// Stop watching the observable
void unobserve(Observable *o);
/// Start watching a specified observable
@@ -86,21 +86,21 @@ template class Observable
template Observer::~Observer()
{
- for (typename std::list *>::const_iterator iterator = observed.begin(); iterator != observed.end();
+ for (typename std::list *>::const_iterator iterator = observables.begin(); iterator != observables.end();
++iterator) {
(*iterator)->removeObserver(this);
}
- observed.clear();
+ observables.clear();
}
template void Observer::unobserve(Observable *o)
{
o->removeObserver(this);
- observed.remove(o);
+ observables.remove(o);
}
template void Observer::observe(Observable *o)
{
- observed.push_back(o);
+ observables.push_back(o);
o->addObserver(this);
-}
+}
\ No newline at end of file
diff --git a/src/Power.cpp b/src/Power.cpp
index 72bb38181..8e44ddb98 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -19,6 +19,11 @@
#include "meshUtils.h"
#include "sleep.h"
+// Working USB detection for powered/charging states on the RAK platform
+#ifdef NRF_APM
+#include "nrfx_power.h"
+#endif
+
#ifdef DEBUG_HEAP_MQTT
#include "mqtt/MQTT.h"
#include "target_specific.h"
@@ -52,6 +57,7 @@ static const adc_atten_t atten = ADC_ATTENUATION;
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
INA260Sensor ina260Sensor;
INA219Sensor ina219Sensor;
+INA3221Sensor ina3221Sensor;
#endif
#ifdef HAS_PMU
@@ -121,8 +127,6 @@ class AnalogBatteryLevel : public HasBatteryLevel
{
/**
* Battery state of charge, from 0 to 100 or -1 for unknown
- *
- * FIXME - use a lipo lookup table, the current % full is super wrong
*/
virtual int getBatteryPercent() override
{
@@ -131,13 +135,32 @@ class AnalogBatteryLevel : public HasBatteryLevel
if (v < noBatVolt)
return -1; // If voltage is super low assume no battery installed
-#ifdef ARCH_ESP32
+#ifdef NO_BATTERY_LEVEL_ON_CHARGE
// This does not work on a RAK4631 with battery connected
if (v > chargingVolt)
return 0; // While charging we can't report % full on the battery
#endif
-
- return clamp((int)(100 * (v - emptyVolt) / (fullVolt - emptyVolt)), 0, 100);
+ /**
+ * @brief Battery voltage lookup table interpolation to obtain a more
+ * precise percentage rather than the old proportional one.
+ * @author Gabriele Russo
+ * @date 06/02/2024
+ */
+ float battery_SOC = 0.0;
+ uint16_t voltage = v / NUM_CELLS; // single cell voltage (average)
+ for (int i = 0; i < NUM_OCV_POINTS; i++) {
+ if (OCV[i] <= voltage) {
+ if (i == 0) {
+ battery_SOC = 100.0; // 100% full
+ } else {
+ // interpolate between OCV[i] and OCV[i-1]
+ battery_SOC = (float)100.0 / (NUM_OCV_POINTS - 1.0) *
+ (NUM_OCV_POINTS - 1.0 - i + ((float)voltage - OCV[i]) / (OCV[i - 1] - OCV[i]));
+ }
+ break;
+ }
+ }
+ return clamp((int)(battery_SOC), 0, 100);
}
/**
@@ -158,7 +181,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
#endif
#ifndef BATTERY_SENSE_SAMPLES
-#define BATTERY_SENSE_SAMPLES 30
+#define BATTERY_SENSE_SAMPLES \
+ 15 // Set the number of samples, it has an effect of increasing sensitivity in complex electromagnetic environment.
#endif
#ifdef BATTERY_PIN
@@ -170,66 +194,110 @@ class AnalogBatteryLevel : public HasBatteryLevel
if (millis() - last_read_time_ms > min_read_interval) {
last_read_time_ms = millis();
- // Set the number of samples, it has an effect of increasing sensitivity, especially in complex electromagnetic
- // environment.
uint32_t raw = 0;
-#ifdef ARCH_ESP32
-#ifndef BAT_MEASURE_ADC_UNIT // ADC1
-#ifdef ADC_CTRL
- if (heltec_version == 5) {
- pinMode(ADC_CTRL, OUTPUT);
- digitalWrite(ADC_CTRL, HIGH);
- delay(10);
- }
-#endif
- for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
- raw += adc1_get_raw(adc_channel);
- }
-#ifdef ADC_CTRL
- if (heltec_version == 5) {
- digitalWrite(ADC_CTRL, LOW);
- }
-#endif
-#else // ADC2
- int32_t adc_buf = 0;
- for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
- // ADC2 wifi bug workaround, see
- // https://github.com/espressif/arduino-esp32/issues/102
- WRITE_PERI_REG(SENS_SAR_READ_CTRL2_REG, RTC_reg_b);
- SET_PERI_REG_MASK(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_DATA_INV);
- adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf);
- raw += adc_buf;
- }
-#endif // BAT_MEASURE_ADC_UNIT
-#else // !ARCH_ESP32
+ float scaled = 0;
+
+#ifdef ARCH_ESP32 // ADC block for espressif platforms
+ raw = espAdcRead();
+ scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
+ scaled *= operativeAdcMultiplier;
+#else // block for all other platforms
for (uint32_t i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
raw += analogRead(BATTERY_PIN);
}
-#endif
raw = raw / BATTERY_SENSE_SAMPLES;
- float scaled;
-#ifdef ARCH_ESP32
- scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
- scaled *= operativeAdcMultiplier;
-#else
-#ifndef VBAT_RAW_TO_SCALED
- scaled = 1000.0 * operativeAdcMultiplier * (AREF_VOLTAGE / 1024.0) * raw;
-#else
- scaled = VBAT_RAW_TO_SCALED(raw); // defined in variant.h
-#endif // VBAT RAW TO SCALED
-#endif // ARCH_ESP32
- // LOG_DEBUG("battery gpio %d raw val=%u scaled=%u\n", BATTERY_PIN, raw, (uint32_t)(scaled));
-
- last_read_value = scaled;
- return scaled;
- } else {
- return last_read_value;
+ scaled = operativeAdcMultiplier * ((1000 * AREF_VOLTAGE) / pow(2, BATTERY_SENSE_RESOLUTION_BITS)) * raw;
+#endif
+ 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));
}
-#else
- return 0;
+ return last_read_value;
#endif // BATTERY_PIN
+ return 0;
}
+#if defined(ARCH_ESP32) && !defined(HAS_PMU) && defined(BATTERY_PIN)
+ /**
+ * ESP32 specific function for getting calibrated ADC reads
+ */
+ uint32_t espAdcRead()
+ {
+
+ uint32_t raw = 0;
+ uint8_t raw_c = 0; // raw reading counter
+
+#ifndef BAT_MEASURE_ADC_UNIT // ADC1
+#ifdef ADC_CTRL // enable adc voltage divider when we need to read
+ pinMode(ADC_CTRL, OUTPUT);
+ digitalWrite(ADC_CTRL, ADC_CTRL_ENABLED);
+ delay(10);
+#endif
+ for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
+ int val_ = adc1_get_raw(adc_channel);
+ if (val_ >= 0) { // save only valid readings
+ raw += val_;
+ raw_c++;
+ }
+ // delayMicroseconds(100);
+ }
+#ifdef ADC_CTRL // disable adc voltage divider when we need to read
+ digitalWrite(ADC_CTRL, !ADC_CTRL_ENABLED);
+#endif
+#else // ADC2
+#ifdef ADC_CTRL
+#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0)
+ pinMode(ADC_CTRL, OUTPUT);
+ digitalWrite(ADC_CTRL, LOW); // ACTIVE LOW
+ delay(10);
+#endif
+#endif // End ADC_CTRL
+
+#ifdef CONFIG_IDF_TARGET_ESP32S3 // ESP32S3
+ // ADC2 wifi bug workaround not required, breaks compile
+ // On ESP32S3, ADC2 can take turns with Wifi (?)
+
+ int32_t adc_buf;
+ esp_err_t read_result;
+
+ // Multiple samples
+ for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
+ adc_buf = 0;
+ read_result = -1;
+
+ read_result = adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf);
+ if (read_result == ESP_OK) {
+ raw += adc_buf;
+ raw_c++; // Count valid samples
+ } else {
+ LOG_DEBUG("An attempt to sample ADC2 failed\n");
+ }
+ }
+
+#else // Other ESP32
+ int32_t adc_buf = 0;
+ for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
+ // ADC2 wifi bug workaround, see
+ // https://github.com/espressif/arduino-esp32/issues/102
+ WRITE_PERI_REG(SENS_SAR_READ_CTRL2_REG, RTC_reg_b);
+ SET_PERI_REG_MASK(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_DATA_INV);
+ adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf);
+ raw += adc_buf;
+ raw_c++;
+ }
+#endif // BAT_MEASURE_ADC_UNIT
+
+#ifdef ADC_CTRL
+#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0)
+ digitalWrite(ADC_CTRL, HIGH);
+#endif
+#endif // End ADC_CTRL
+
+#endif // End BAT_MEASURE_ADC_UNIT
+ return (raw / (raw_c < 1 ? 1 : raw_c));
+ }
+#endif
+
/**
* return true if there is a battery installed in this unit
*/
@@ -260,22 +328,14 @@ class AnalogBatteryLevel : public HasBatteryLevel
/// If we see a battery voltage higher than physics allows - assume charger is pumping
/// in power
-#ifndef BAT_FULLVOLT
-#define BAT_FULLVOLT 4200
-#endif
-#ifndef BAT_EMPTYVOLT
-#define BAT_EMPTYVOLT 3270
-#endif
-#ifndef BAT_CHARGINGVOLT
-#define BAT_CHARGINGVOLT 4210
-#endif
-#ifndef BAT_NOBATVOLT
-#define BAT_NOBATVOLT 2230
-#endif
-
- /// For heltecs with no battery connected, the measured voltage is 2204, so raising to 2230 from 2100
- const float fullVolt = BAT_FULLVOLT, emptyVolt = BAT_EMPTYVOLT, chargingVolt = BAT_CHARGINGVOLT, noBatVolt = BAT_NOBATVOLT;
- float last_read_value = 0.0;
+ /// For heltecs with no battery connected, the measured voltage is 2204, so
+ // need to be higher than that, in this case is 2500mV (3000-500)
+ const uint16_t OCV[NUM_OCV_POINTS] = {OCV_ARRAY};
+ const float chargingVolt = (OCV[0] + 10) * NUM_CELLS;
+ 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.
+ 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)
@@ -286,6 +346,9 @@ class AnalogBatteryLevel : public HasBatteryLevel
} else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA260].first ==
config.power.device_battery_ina_address) {
return ina260Sensor.getBusVoltageMv();
+ } else if (nodeTelemetrySensorsMap[meshtastic_TelemetrySensorType_INA3221].first ==
+ config.power.device_battery_ina_address) {
+ return ina3221Sensor.getBusVoltageMv();
}
return 0;
}
@@ -349,8 +412,11 @@ bool Power::analogInit()
adc1_config_channel_atten(adc_channel, atten);
#else // ADC2
adc2_config_channel_atten(adc_channel, atten);
+#ifndef CONFIG_IDF_TARGET_ESP32S3
// ADC2 wifi bug workaround
+ // Not required with ESP32S3, breaks compile
RTC_reg_b = READ_PERI_REG(SENS_SAR_READ_CTRL2_REG);
+#endif
#endif
// calibrate ADC
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_characs);
@@ -359,13 +425,16 @@ bool Power::analogInit()
LOG_INFO("ADCmod: ADC characterization based on Two Point values stored in eFuse\n");
} else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
LOG_INFO("ADCmod: ADC characterization based on reference voltage stored in eFuse\n");
- } else {
+ }
+#ifdef CONFIG_IDF_TARGET_ESP32S3
+ // ESP32S3
+ else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) {
+ LOG_INFO("ADCmod: ADC Characterization based on Two Point values and fitting curve coefficients stored in eFuse\n");
+ }
+#endif
+ else {
LOG_INFO("ADCmod: ADC characterization based on default reference voltage\n");
}
-#if defined(HELTEC_V3) || defined(HELTEC_WSL_V3)
- pinMode(37, OUTPUT); // needed for P channel mosfet to work
- digitalWrite(37, LOW);
-#endif
#endif // ARCH_ESP32
#ifdef ARCH_NRF52
@@ -374,11 +443,12 @@ bool Power::analogInit()
#else
analogReference(AR_INTERNAL); // 3.6V
#endif
- analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS); // Default of 12 is not very linear. Recommended to use 10 or 11
- // depending on needed resolution.
-
#endif // ARCH_NRF52
+#ifndef ARCH_ESP32
+ analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
+#endif
+
batteryLevel = &analogLevel;
return true;
#else
@@ -393,11 +463,8 @@ bool Power::analogInit()
*/
bool Power::setup()
{
- bool found = axpChipInit();
+ bool found = axpChipInit() || analogInit();
- if (!found) {
- found = analogInit();
- }
enabled = found;
low_voltage_counter = 0;
@@ -426,7 +493,7 @@ void Power::shutdown()
ledOff(PIN_LED2);
#endif
#ifdef PIN_LED3
- ledOff(PIN_LED2);
+ ledOff(PIN_LED3);
#endif
doDeepSleep(DELAY_FOREVER, false);
#endif
@@ -448,18 +515,33 @@ void Power::readPowerStatus()
batteryChargePercent = batteryLevel->getBatteryPercent();
} else {
// If the AXP192 returns a percentage less than 0, the feature is either not supported or there is an error
- // In that case, we compute an estimate of the charge percent based on maximum and minimum voltages defined in
- // power.h
- batteryChargePercent =
- clamp((int)(((batteryVoltageMv - BAT_MILLIVOLTS_EMPTY) * 1e2) / (BAT_MILLIVOLTS_FULL - BAT_MILLIVOLTS_EMPTY)),
- 0, 100);
+ // In that case, we compute an estimate of the charge percent based on open circuite voltage table defined
+ // in power.h
+ batteryChargePercent = clamp((int)(((batteryVoltageMv - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS)) * 1e2) /
+ ((OCV[0] * NUM_CELLS) - (OCV[NUM_OCV_POINTS - 1] * NUM_CELLS))),
+ 0, 100);
}
}
+ OptionalBool NRF_USB = OptFalse;
+
+#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.
+
+ 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;
+ }
+#endif
// Notify any status instances that are observing us
- const PowerStatus powerStatus2 =
- PowerStatus(hasBattery ? OptTrue : OptFalse, batteryLevel->isVbusIn() ? OptTrue : OptFalse,
- batteryLevel->isCharging() ? OptTrue : OptFalse, batteryVoltageMv, batteryChargePercent);
+ const PowerStatus powerStatus2 = PowerStatus(
+ hasBattery ? OptTrue : OptFalse, batteryLevel->isVbusIn() || NRF_USB == OptTrue ? OptTrue : OptFalse,
+ batteryLevel->isCharging() || NRF_USB == OptTrue ? OptTrue : OptFalse, batteryVoltageMv, batteryChargePercent);
LOG_DEBUG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d\n", powerStatus2.getHasUSB(),
powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent());
newStatus.notifyObservers(&powerStatus2);
@@ -502,10 +584,11 @@ void Power::readPowerStatus()
#endif
- // If we have a battery at all and it is less than 10% full, force deep sleep if we have more than 10 low readings in
- // a row
+ // If we have a battery at all and it is less than 0%, force deep sleep if we have more than 10 low readings in
+ // a row. NOTE: min LiIon/LiPo voltage is 2.0 to 2.5V, current OCV min is set to 3100 that is large enough.
+ //
if (powerStatus2.getHasBattery() && !powerStatus2.getHasUSB()) {
- if (batteryLevel->getBattVoltage() < MIN_BAT_MILLIVOLTS) {
+ if (batteryLevel->getBattVoltage() < OCV[NUM_OCV_POINTS - 1]) {
low_voltage_counter++;
LOG_DEBUG("Low voltage counter: %d/10\n", low_voltage_counter);
if (low_voltage_counter > 10) {
@@ -873,4 +956,4 @@ bool Power::axpChipInit()
#else
return false;
#endif
-}
\ No newline at end of file
+}
diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp
index c64599ce6..bac3899bb 100644
--- a/src/PowerFSM.cpp
+++ b/src/PowerFSM.cpp
@@ -246,6 +246,7 @@ void PowerFSM_setup()
{
bool isRouter = (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ? 1 : 0);
bool isTrackerOrSensor = config.device.role == meshtastic_Config_DeviceConfig_Role_TRACKER ||
+ config.device.role == meshtastic_Config_DeviceConfig_Role_TAK_TRACKER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_SENSOR;
bool hasPower = isPowered();
diff --git a/src/PowerFSMThread.h b/src/PowerFSMThread.h
index 541522f43..584c955aa 100644
--- a/src/PowerFSMThread.h
+++ b/src/PowerFSMThread.h
@@ -21,7 +21,7 @@ class PowerFSMThread : public OSThread
/// If we are in power state we force the CPU to wake every 10ms to check for serial characters (we don't yet wake
/// cpu for serial rx - FIXME)
- const auto state = powerFSM.getState();
+ const State *state = powerFSM.getState();
canSleep = (state != &statePOWER) && (state != &stateSERIAL);
if (powerStatus->getHasUSB()) {
@@ -33,7 +33,7 @@ class PowerFSMThread : public OSThread
powerFSM.trigger(EVENT_SHUTDOWN);
}
- return 10;
+ return 100;
}
};
diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp
index 2d73c7c9b..d3f39c377 100644
--- a/src/RedirectablePrint.cpp
+++ b/src/RedirectablePrint.cpp
@@ -10,6 +10,10 @@
#include
#include
+#ifdef ARCH_PORTDUINO
+#include "platform/portduino/PortduinoGlue.h"
+#endif
+
/**
* A printer that doesn't go anywhere
*/
@@ -68,7 +72,15 @@ size_t RedirectablePrint::vprintf(const char *format, va_list arg)
size_t RedirectablePrint::log(const char *logLevel, const char *format, ...)
{
- if (moduleConfig.serial.override_console_serial_port && strcmp(logLevel, "DEBUG") == 0) {
+#ifdef ARCH_PORTDUINO
+ if (settingsMap[logoutputlevel] < level_debug && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0)
+ return 0;
+ else if (settingsMap[logoutputlevel] < level_info && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_INFO) == 0)
+ return 0;
+ else if (settingsMap[logoutputlevel] < level_warn && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_WARN) == 0)
+ return 0;
+#endif
+ if (moduleConfig.serial.override_console_serial_port && strcmp(logLevel, MESHTASTIC_LOG_LEVEL_DEBUG) == 0) {
return 0;
}
size_t r = 0;
@@ -99,10 +111,17 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...)
int hour = hms / SEC_PER_HOUR;
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
-
+#ifdef ARCH_PORTDUINO
+ r += ::printf("%s | %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000);
+#else
r += printf("%s | %02d:%02d:%02d %u ", logLevel, hour, min, sec, millis() / 1000);
+#endif
} else
+#ifdef ARCH_PORTDUINO
+ r += ::printf("%s | ??:??:?? %u ", logLevel, millis() / 1000);
+#else
r += printf("%s | ??:??:?? %u ", logLevel, millis() / 1000);
+#endif
auto thread = concurrency::OSThread::currentThread;
if (thread) {
diff --git a/src/configuration.h b/src/configuration.h
index 2640b1572..d8b0dba5f 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -57,8 +57,8 @@ along with this program. If not, see .
#define REQUIRE_RADIO true // If true, we will fail to start if the radio is not found
/// Convert a preprocessor name into a quoted string
-#define xstr(s) str(s)
-#define str(s) #s
+#define xstr(s) ystr(s)
+#define ystr(s) #s
/// Convert a preprocessor name into a quoted string and if that string is empty use "unset"
#define optstr(s) (xstr(s)[0] ? xstr(s) : "unset")
@@ -111,6 +111,7 @@ along with this program. If not, see .
#define MCP9808_ADDR 0x18
#define INA_ADDR 0x40
#define INA_ADDR_ALTERNATE 0x41
+#define INA3221_ADDR 0x42
#define QMC6310_ADDR 0x1C
#define QMI8658_ADDR 0x6B
#define QMC5883L_ADDR 0x1E
@@ -141,8 +142,9 @@ along with this program. If not, see .
// -----------------------------------------------------------------------------
// GPS
// -----------------------------------------------------------------------------
-
+#ifndef GPS_BAUDRATE
#define GPS_BAUDRATE 9600
+#endif
#ifndef GPS_THREAD_INTERVAL
#define GPS_THREAD_INTERVAL 200
@@ -160,6 +162,17 @@ along with this program. If not, see .
/* Step #3: mop up with disabled values for HAS_ options not handled by the above two */
+// -----------------------------------------------------------------------------
+// GPS
+// -----------------------------------------------------------------------------
+
+#ifndef GPS_BAUDRATE
+#define GPS_BAUDRATE 9600
+#endif
+#ifndef GPS_THREAD_INTERVAL
+#define GPS_THREAD_INTERVAL 100
+#endif
+
#ifndef HAS_WIFI
#define HAS_WIFI 0
#endif
@@ -187,6 +200,9 @@ along with this program. If not, see .
#ifndef HAS_TELEMETRY
#define HAS_TELEMETRY 0
#endif
+#ifndef HAS_SENSOR
+#define HAS_SENSOR 0
+#endif
#ifndef HAS_RADIO
#define HAS_RADIO 0
#endif
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index 15d9b1342..2b4b8a735 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -25,6 +25,7 @@ class ScanI2C
BMP_280,
INA260,
INA219,
+ INA3221,
MCP9808,
SHT31,
SHTC3,
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index ced1e34dd..990fb36ea 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -2,7 +2,9 @@
#include "concurrency/LockGuard.h"
#include "configuration.h"
-
+#if defined(ARCH_PORTDUINO)
+#include "linux/LinuxHardwareI2C.h"
+#endif
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
#include "main.h" // atecc
#endif
@@ -162,7 +164,14 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
for (addr.address = 1; addr.address < 127; addr.address++) {
i2cBus->beginTransmission(addr.address);
+#ifdef ARCH_PORTDUINO
+ if (i2cBus->read() != -1)
+ err = 0;
+ else
+ err = 2;
+#else
err = i2cBus->endTransmission();
+#endif
type = NONE;
if (err == 0) {
LOG_DEBUG("I2C device found at address 0x%x\n", addr.address);
@@ -251,7 +260,10 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
type = INA219;
}
break;
-
+ case INA3221_ADDR:
+ LOG_INFO("INA3221 sensor found at address 0x%x\n", (uint8_t)addr.address);
+ type = INA3221;
+ break;
case MCP9808_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x07), 2);
if (registerValue == 0x0400) {
diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index ce5b18af8..08ef116b2 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -7,14 +7,16 @@
#include "ubx.h"
#ifdef ARCH_PORTDUINO
+#include "PortduinoGlue.h"
#include "meshUtils.h"
+#include
#endif
#ifndef GPS_RESET_MODE
#define GPS_RESET_MODE HIGH
#endif
-#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32)
+#if defined(NRF52840_XXAA) || defined(NRF52833_XXAA) || defined(ARCH_ESP32) || defined(ARCH_PORTDUINO)
HardwareSerial *GPS::_serial_gps = &Serial1;
#else
HardwareSerial *GPS::_serial_gps = NULL;
@@ -249,17 +251,23 @@ int GPS::getACK(uint8_t *buffer, uint16_t size, uint8_t requestedClass, uint8_t
bool GPS::setup()
{
int msglen = 0;
- bool isProblematicGPS = false;
if (!didSerialInit) {
#if !defined(GPS_UC6580)
-#ifdef HAS_PMU
- // The T-Beam 1.2 has issues with the GPS
- if (HW_VENDOR == meshtastic_HardwareModel_TBEAM && PMU->getChipModel() == XPOWERS_AXP2101) {
- gnssModel = GNSS_MODEL_UBLOX;
- isProblematicGPS = true;
+
+#if defined(RAK4630) && defined(PIN_3V3_EN)
+ // If we are using the RAK4630 and we have no other peripherals on the I2C bus or module interest in 3V3_S,
+ // then we can safely set en_gpio turn off power to 3V3 (IO2) to hard sleep the GPS
+ if (rtc_found.port == ScanI2C::DeviceType::NONE && rgb_found.type == ScanI2C::DeviceType::NONE &&
+ accelerometer_found.port == ScanI2C::DeviceType::NONE && !moduleConfig.detection_sensor.enabled &&
+ !moduleConfig.telemetry.air_quality_enabled && !moduleConfig.telemetry.environment_measurement_enabled &&
+ config.power.device_battery_ina_address == 0 && en_gpio == 0) {
+ LOG_DEBUG("Since no problematic peripherals or interested modules were found, setting power save GPS_EN to pin %i\n",
+ PIN_3V3_EN);
+ en_gpio = PIN_3V3_EN;
}
#endif
+
if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
LOG_DEBUG("Probing for GPS at %d \n", serialSpeeds[speedSelect]);
gnssModel = probe(serialSpeeds[speedSelect]);
@@ -277,7 +285,7 @@ bool GPS::setup()
gnssModel = GNSS_MODEL_UNKNOWN;
}
#else
- gnssModel = GNSS_MODEL_UC6850;
+ gnssModel = GNSS_MODEL_UC6580;
#endif
if (gnssModel == GNSS_MODEL_MTK) {
@@ -295,136 +303,292 @@ bool GPS::setup()
// Switch to Vehicle Mode, since SoftRF enables Aviation < 2g
_serial_gps->write("$PCAS11,3*1E\r\n");
delay(250);
- } else if (gnssModel == GNSS_MODEL_UC6850) {
-
- // use GPS + GLONASS
- _serial_gps->write("$CFGSYS,h15\r\n");
+ } else if (gnssModel == GNSS_MODEL_UC6580) {
+ // The Unicore UC6580 can use a lot of sat systems, enable it to
+ // use GPS L1 & L5 + BDS B1I & B2a + GLONASS L1 + GALILEO E1 & E5a + SBAS
+ // This will reset the receiver, so wait a bit afterwards
+ // The paranoid will wait for the OK*04 confirmation response after each command.
+ _serial_gps->write("$CFGSYS,h25155\r\n");
+ delay(750);
+ // Must be done after the CFGSYS command
+ // Turn off GSV messages, we don't really care about which and where the sats are, maybe someday.
+ _serial_gps->write("$CFGMSG,0,3,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);
+ _serial_gps->write("$CFGMSG,6,1,0\r\n");
+ delay(250);
+
} else if (gnssModel == GNSS_MODEL_UBLOX) {
// Configure GNSS system to GPS+SBAS+GLONASS (Module may restart after this command)
// We need set it because by default it is GPS only, and we want to use GLONASS too
// Also we need SBAS for better accuracy and extra features
// ToDo: Dynamic configure GNSS systems depending of LoRa region
- if (strncmp(info.hwVersion, "00040007", 8) !=
- 0) { // The original ublox 6 is GPS only and doesn't support the UBX-CFG-GNSS message
- if (strncmp(info.hwVersion, "00070000", 8) == 0) { // Max7 seems to only support GPS *or* GLONASS
- LOG_DEBUG("Setting GPS+SBAS\n");
- msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS_7), _message_GNSS_7);
- _serial_gps->write(UBXscratch, msglen);
- } else {
- msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS), _message_GNSS);
- _serial_gps->write(UBXscratch, msglen);
- }
+ if (strncmp(info.hwVersion, "000A0000", 8) != 0) {
+ if (strncmp(info.hwVersion, "00040007", 8) != 0) {
+ // The original ublox Neo-6 is GPS only and doesn't support the UBX-CFG-GNSS message
+ // Max7 seems to only support GPS *or* GLONASS
+ // Neo-7 is supposed to support GPS *and* GLONASS but NAKs the CFG-GNSS command to do it
+ // So treat all the u-blox 7 series as GPS only
+ // M8 can support 3 constallations at once so turn on GPS, GLONASS and Galileo (or BeiDou)
- if (getACK(0x06, 0x3e, 800) == GNSS_RESPONSE_NAK) {
- // It's not critical if the module doesn't acknowledge this configuration.
- LOG_INFO("Unable to reconfigure GNSS - defaults maintained. Is this module GPS-only?\n");
- } else {
if (strncmp(info.hwVersion, "00070000", 8) == 0) {
- LOG_INFO("GNSS configured for GPS+SBAS. Pause for 0.75s before sending next command.\n");
+ LOG_DEBUG("Setting GPS+SBAS\n");
+ msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS_7), _message_GNSS_7);
+ _serial_gps->write(UBXscratch, msglen);
} else {
- LOG_INFO("GNSS configured for GPS+SBAS+GLONASS. Pause for 0.75s before sending next command.\n");
+ msglen = makeUBXPacket(0x06, 0x3e, sizeof(_message_GNSS_8), _message_GNSS_8);
+ _serial_gps->write(UBXscratch, msglen);
}
- // Documentation say, we need wait atleast 0.5s after reconfiguration of GNSS module, before sending next
- // commands
- delay(750);
- }
- }
- msglen = makeUBXPacket(0x06, 0x39, sizeof(_message_JAM), _message_JAM);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x39, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable interference resistance.\n");
- }
-
- msglen = makeUBXPacket(0x06, 0x23, sizeof(_message_NAVX5), _message_NAVX5);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x23, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to configure extra settings.\n");
- }
-
- // ublox-M10S can be compatible with UBLOX traditional protocol, so the following sentence settings are also valid
-
- msglen = makeUBXPacket(0x06, 0x08, sizeof(_message_1HZ), _message_1HZ);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x08, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to set GPS update rate.\n");
- }
-
- msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_GGL), _message_GGL);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x01, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to disable NMEA GGL.\n");
- }
-
- msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_GSA), _message_GSA);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x01, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to Enable NMEA GSA.\n");
- }
-
- msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_GSV), _message_GSV);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x01, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to disable NMEA GSV.\n");
- }
-
- msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_VTG), _message_VTG);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x01, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to disable NMEA VTG.\n");
- }
-
- msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_RMC), _message_RMC);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x01, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable NMEA RMC.\n");
- }
-
- msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_GGA), _message_GGA);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x01, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable NMEA GGA.\n");
- }
-
- if (uBloxProtocolVersion >= 18) {
- msglen = makeUBXPacket(0x06, 0x86, sizeof(_message_PMS), _message_PMS);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x86, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable powersaving for GPS.\n");
- }
- } else {
- if (!(isProblematicGPS)) {
- if (strncmp(info.hwVersion, "00040007", 8) == 0) { // This PSM mode has only been tested on this hardware
- msglen = makeUBXPacket(0x06, 0x11, 0x2, _message_CFG_RXM_PSM);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x11, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable powersaving mode for GPS.\n");
- }
- msglen = makeUBXPacket(0x06, 0x3B, 44, _message_CFG_PM2);
- _serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x3B, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable powersaving details for GPS.\n");
- }
+ if (getACK(0x06, 0x3e, 800) == GNSS_RESPONSE_NAK) {
+ // It's not critical if the module doesn't acknowledge this configuration.
+ LOG_INFO("Unable to reconfigure GNSS - defaults maintained. Is this module GPS-only?\n");
} else {
+ if (strncmp(info.hwVersion, "00070000", 8) == 0) {
+ LOG_INFO("GNSS configured for GPS+SBAS. Pause for 0.75s before sending next command.\n");
+ } else {
+ LOG_INFO(
+ "GNSS configured for GPS+SBAS+GLONASS+Galileo. Pause for 0.75s before sending next command.\n");
+ }
+ // Documentation say, we need wait atleast 0.5s after reconfiguration of GNSS module, before sending next
+ // commands for the M8 it tends to be more... 1 sec should be enough ;>)
+ delay(1000);
+ }
+ }
+ // Disable Text Info messages
+ msglen = makeUBXPacket(0x06, 0x02, sizeof(_message_DISABLE_TXT_INFO), _message_DISABLE_TXT_INFO);
+ clearBuffer();
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x02, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable text info messages.\n");
+ }
+ // ToDo add M10 tests for below
+ if (strncmp(info.hwVersion, "00080000", 8) == 0) {
+ msglen = makeUBXPacket(0x06, 0x39, sizeof(_message_JAM_8), _message_JAM_8);
+ clearBuffer();
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x39, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable interference resistance.\n");
+ }
+
+ msglen = makeUBXPacket(0x06, 0x23, sizeof(_message_NAVX5_8), _message_NAVX5_8);
+ clearBuffer();
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x23, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to configure NAVX5_8 settings.\n");
+ }
+ } else {
+ msglen = makeUBXPacket(0x06, 0x39, sizeof(_message_JAM_6_7), _message_JAM_6_7);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x39, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable interference resistance.\n");
+ }
+
+ msglen = makeUBXPacket(0x06, 0x23, sizeof(_message_NAVX5), _message_NAVX5);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x23, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to configure NAVX5 settings.\n");
+ }
+ }
+ // Turn off unwanted NMEA messages, set update rate
+
+ msglen = makeUBXPacket(0x06, 0x08, sizeof(_message_1HZ), _message_1HZ);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x08, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to set GPS update rate.\n");
+ }
+
+ msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_GLL), _message_GLL);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable NMEA GLL.\n");
+ }
+
+ msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_GSA), _message_GSA);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to Enable NMEA GSA.\n");
+ }
+
+ msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_GSV), _message_GSV);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable NMEA GSV.\n");
+ }
+
+ msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_VTG), _message_VTG);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable NMEA VTG.\n");
+ }
+
+ msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_RMC), _message_RMC);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable NMEA RMC.\n");
+ }
+
+ msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_GGA), _message_GGA);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable NMEA GGA.\n");
+ }
+
+ if (uBloxProtocolVersion >= 18) {
+ msglen = makeUBXPacket(0x06, 0x86, sizeof(_message_PMS), _message_PMS);
+ clearBuffer();
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x86, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable powersaving for GPS.\n");
+ }
+ msglen = makeUBXPacket(0x06, 0x3B, sizeof(_message_CFG_PM2), _message_CFG_PM2);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x3B, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable powersaving details for GPS.\n");
+ }
+ // For M8 we want to enable NMEA vserion 4.10 so we can see the additional sats.
+ if (strncmp(info.hwVersion, "00080000", 8) == 0) {
+ msglen = makeUBXPacket(0x06, 0x17, sizeof(_message_NMEA), _message_NMEA);
+ clearBuffer();
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x17, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable NMEA 4.10.\n");
+ }
+ }
+
+ } else {
+ if (strncmp(info.hwVersion, "00040007", 8) == 0) { // This PSM mode is only for Neo-6
msglen = makeUBXPacket(0x06, 0x11, 0x2, _message_CFG_RXM_ECO);
_serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x11, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to enable powersaving ECO mode for GPS.\n");
+ if (getACK(0x06, 0x11, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable powersaving ECO mode for Neo-6.\n");
+ }
+ msglen = makeUBXPacket(0x06, 0x3B, sizeof(_message_CFG_PM2), _message_CFG_PM2);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x3B, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable powersaving details for GPS.\n");
+ }
+ msglen = makeUBXPacket(0x06, 0x01, sizeof(_message_AID), _message_AID);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x01, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable UBX-AID.\n");
+ }
+ } else {
+ msglen = makeUBXPacket(0x06, 0x11, 0x2, _message_CFG_RXM_PSM);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x11, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable powersaving mode for GPS.\n");
+ }
+
+ msglen = makeUBXPacket(0x06, 0x3B, sizeof(_message_CFG_PM2), _message_CFG_PM2);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x3B, 500) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable powersaving details for GPS.\n");
}
}
}
- }
- // The T-beam 1.2 has issues.
- if (!(isProblematicGPS)) {
+
msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE);
_serial_gps->write(UBXscratch, msglen);
- if (getACK(0x06, 0x09, 300) != GNSS_RESPONSE_OK) {
+ if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) {
LOG_WARN("Unable to save GNSS module configuration.\n");
} else {
LOG_INFO("GNSS module configuration saved!\n");
}
+ } else {
+ // LOG_INFO("u-blox M10 hardware found.\n");
+ delay(1000);
+ // First disable all NMEA messages in RAM layer
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_NMEA_RAM), _message_VALSET_DISABLE_NMEA_RAM);
+ clearBuffer();
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable NMEA messages for M10 GPS RAM.\n");
+ }
+ delay(250);
+ // Next disable unwanted NMEA messages in BBR layer
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_NMEA_BBR), _message_VALSET_DISABLE_NMEA_BBR);
+ clearBuffer();
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable NMEA messages for M10 GPS BBR.\n");
+ }
+ delay(250);
+ // Disable Info txt messages in RAM layer
+ msglen =
+ makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_TXT_INFO_RAM), _message_VALSET_DISABLE_TXT_INFO_RAM);
+ clearBuffer();
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable Info messages for M10 GPS RAM.\n");
+ }
+ delay(250);
+ // Next disable Info txt messages in BBR layer
+ msglen =
+ makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_TXT_INFO_BBR), _message_VALSET_DISABLE_TXT_INFO_BBR);
+ clearBuffer();
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable Info messages for M10 GPS BBR.\n");
+ }
+ // Do M10 configuration for Power Management.
+
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_PM_RAM), _message_VALSET_PM_RAM);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable powersaving for M10 GPS RAM.\n");
+ }
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_PM_BBR), _message_VALSET_PM_BBR);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable powersaving for M10 GPS BBR.\n");
+ }
+
+ delay(250);
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_ITFM_RAM), _message_VALSET_ITFM_RAM);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable Jamming detection M10 GPS RAM.\n");
+ }
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_ITFM_BBR), _message_VALSET_ITFM_BBR);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable Jamming detection M10 GPS BBR.\n");
+ }
+
+ // Here is where the init commands should go to do further M10 initialization.
+ delay(250);
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_SBAS_RAM), _message_VALSET_DISABLE_SBAS_RAM);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable SBAS M10 GPS RAM.\n");
+ }
+ delay(750); // will cause a receiver restart so wait a bit
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_SBAS_BBR), _message_VALSET_DISABLE_SBAS_BBR);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable SBAS M10 GPS BBR.\n");
+ }
+ delay(750); // will cause a receiver restart so wait a bit
+ // Done with initialization, Now enable wanted NMEA messages in BBR layer so they will survive a periodic sleep.
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_ENABLE_NMEA_BBR), _message_VALSET_ENABLE_NMEA_BBR);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable messages for M10 GPS BBR.\n");
+ }
+ delay(250);
+ // Next enable wanted NMEA messages in RAM layer
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_ENABLE_NMEA_RAM), _message_VALSET_ENABLE_NMEA_RAM);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable messages for M10 GPS RAM.\n");
+ }
+ // As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR.
+ // BBR will survive a restart, and power off for a while, but modules with small backup
+ // batteries or super caps will not retain the config for a long power off time.
}
}
didSerialInit = true;
@@ -432,6 +596,7 @@ bool GPS::setup()
notifyDeepSleepObserver.observe(¬ifyDeepSleep);
notifyGPSSleepObserver.observe(¬ifyGPSSleep);
+
return true;
}
@@ -478,14 +643,14 @@ void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime)
#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76K and clones
if (on) {
LOG_INFO("Waking GPS");
- digitalWrite(PIN_GPS_STANDBY, 1);
pinMode(PIN_GPS_STANDBY, OUTPUT);
+ digitalWrite(PIN_GPS_STANDBY, 1);
return;
} else {
LOG_INFO("GPS entering sleep");
// notifyGPSSleep.notifyObservers(NULL);
- digitalWrite(PIN_GPS_STANDBY, 0);
pinMode(PIN_GPS_STANDBY, OUTPUT);
+ digitalWrite(PIN_GPS_STANDBY, 0);
return;
}
#endif
@@ -493,10 +658,17 @@ void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime)
if (gnssModel == GNSS_MODEL_UBLOX) {
uint8_t msglen;
LOG_DEBUG("Sleep Time: %i\n", sleepTime);
- for (int i = 0; i < 4; i++) {
- gps->_message_PMREQ[0 + i] = sleepTime >> (i * 8); // Encode the sleep time in millis into the packet
+ if (strncmp(info.hwVersion, "000A0000", 8) != 0) {
+ for (int i = 0; i < 4; i++) {
+ gps->_message_PMREQ[0 + i] = sleepTime >> (i * 8); // Encode the sleep time in millis into the packet
+ }
+ msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ), gps->_message_PMREQ);
+ } else {
+ for (int i = 0; i < 4; i++) {
+ gps->_message_PMREQ_10[4 + i] = sleepTime >> (i * 8); // Encode the sleep time in millis into the packet
+ }
+ msglen = gps->makeUBXPacket(0x02, 0x41, sizeof(_message_PMREQ_10), gps->_message_PMREQ_10);
}
- msglen = gps->makeUBXPacket(0x02, 0x41, 0x08, gps->_message_PMREQ);
gps->_serial_gps->write(gps->UBXscratch, msglen);
}
} else {
@@ -546,15 +718,20 @@ void GPS::setAwake(bool on)
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
#ifdef GPS_UC6580
setGPSPower(on, false, getSleepTime() - averageLockTime);
#else
setGPSPower(on, true, getSleepTime() - averageLockTime);
#endif
- } else if (averageLockTime > 20000) {
+ 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
}
}
@@ -562,11 +739,12 @@ void GPS::setAwake(bool on)
*/
uint32_t GPS::getWakeTime() const
{
- uint32_t t = config.position.gps_attempt_time;
+ uint32_t t = config.position.position_broadcast_secs;
if (t == UINT32_MAX)
return t; // already maxint
- return t * 1000;
+
+ return getConfiguredOrDefaultMs(t, default_broadcast_interval_secs);
}
/** Get how long we should sleep between aqusition attempts in msecs
@@ -576,7 +754,7 @@ uint32_t GPS::getSleepTime() const
uint32_t t = config.position.gps_update_interval;
// We'll not need the GPS thread to wake up again after first acq. with fixed position.
- if (!config.position.gps_enabled || config.position.fixed_position)
+ if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED || config.position.fixed_position)
t = UINT32_MAX; // Sleep forever now
if (t == UINT32_MAX)
@@ -597,21 +775,24 @@ void GPS::publishUpdate()
// Notify any status instances that are observing us
const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasValidLocation, isConnected(), isPowerSaving(), p);
newStatus.notifyObservers(&status);
- if (config.position.gps_enabled)
+ if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
positionModule->handleNewPosition();
+ }
}
}
int32_t GPS::runOnce()
{
if (!GPSInitFinished) {
- if (!_serial_gps)
+ if (!_serial_gps || config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT) {
+ LOG_INFO("GPS set to not-present. Skipping probe.\n");
return disable();
+ }
if (!setup())
return 2000; // Setup failed, re-run in two seconds
// We have now loaded our saved preferences from flash
- if (config.position.gps_enabled == false) {
+ if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
return disable();
}
// ONCE we will factory reset the GPS for bug #327
@@ -634,7 +815,7 @@ int32_t GPS::runOnce()
// if we have received valid NMEA claim we are connected
setConnected();
} else {
- if ((config.position.gps_enabled == 1) && (gnssModel == GNSS_MODEL_UBLOX)) {
+ if ((config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) && (gnssModel == GNSS_MODEL_UBLOX)) {
// reset the GPS on next bootup
if (devicestate.did_gps_reset && (millis() - lastWakeStartMsec > 60000) && !hasFlow()) {
LOG_DEBUG("GPS is not communicating, trying factory reset on next bootup.\n");
@@ -647,7 +828,8 @@ int32_t GPS::runOnce()
// At least one GPS has a bad habit of losing its mind from time to time
if (rebootsSeen > 2) {
rebootsSeen = 0;
- gps->factoryReset();
+ LOG_DEBUG("Would normally factoryReset()\n");
+ // gps->factoryReset();
}
// If we are overdue for an update, turn on the GPS and at least publish the current status
@@ -846,9 +1028,9 @@ GnssModel_t GPS::probe(int serialSpeed)
strncpy((char *)buffer, &(info.extension[i][4]), sizeof(buffer));
// LOG_DEBUG("GetModel:%s\n", (char *)buffer);
if (strlen((char *)buffer)) {
- LOG_INFO("UBlox GNSS init succeeded, using UBlox %s GNSS Module\n", (char *)buffer);
+ LOG_INFO("UBlox GNSS probe succeeded, using UBlox %s GNSS Module\n", (char *)buffer);
} else {
- LOG_INFO("UBlox GNSS init succeeded, using UBlox GNSS Module\n");
+ LOG_INFO("UBlox GNSS probe succeeded, using UBlox GNSS Module\n");
}
} else if (!strncmp(info.extension[i], "PROTVER", 7)) {
char *ptr = nullptr;
@@ -873,7 +1055,7 @@ GPS *GPS::createGps()
int8_t _rx_gpio = config.position.rx_gpio;
int8_t _tx_gpio = config.position.tx_gpio;
int8_t _en_gpio = config.position.gps_en_gpio;
-#if defined(HAS_GPS) && !defined(ARCH_ESP32)
+#if HAS_GPS && !defined(ARCH_ESP32)
_rx_gpio = 1; // We only specify GPS serial ports on ESP32. Otherwise, these are just flags.
_tx_gpio = 1;
#endif
@@ -888,6 +1070,10 @@ GPS *GPS::createGps()
#if defined(PIN_GPS_EN)
if (!_en_gpio)
_en_gpio = PIN_GPS_EN;
+#endif
+#ifdef ARCH_PORTDUINO
+ if (!settingsMap[has_gps])
+ return nullptr;
#endif
if (!_rx_gpio || !_serial_gps) // Configured to have no GPS at all
return nullptr;
@@ -899,8 +1085,8 @@ GPS *GPS::createGps()
if (_en_gpio != 0) {
LOG_DEBUG("Setting %d to output.\n", _en_gpio);
- digitalWrite(_en_gpio, !GPS_EN_ACTIVE);
pinMode(_en_gpio, OUTPUT);
+ digitalWrite(_en_gpio, !GPS_EN_ACTIVE);
}
#ifdef PIN_GPS_PPS
@@ -920,8 +1106,8 @@ GPS *GPS::createGps()
new_gps->setGPSPower(true, false, 0);
#ifdef PIN_GPS_RESET
- digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms
pinMode(PIN_GPS_RESET, OUTPUT);
+ digitalWrite(PIN_GPS_RESET, GPS_RESET_MODE); // assert for 10ms
delay(10);
digitalWrite(PIN_GPS_RESET, !GPS_RESET_MODE);
#endif
@@ -966,8 +1152,8 @@ bool GPS::factoryReset()
{
#ifdef PIN_GPS_REINIT
// The L76K GNSS on the T-Echo requires the RESET pin to be pulled LOW
- digitalWrite(PIN_GPS_REINIT, 0);
pinMode(PIN_GPS_REINIT, OUTPUT);
+ digitalWrite(PIN_GPS_REINIT, 0);
delay(150); // The L76K datasheet calls for at least 100MS delay
digitalWrite(PIN_GPS_REINIT, 1);
#endif
@@ -1065,7 +1251,7 @@ bool GPS::lookForLocation()
#ifndef TINYGPS_OPTION_NO_CUSTOM_FIELDS
fixType = atoi(gsafixtype.value()); // will set to zero if no data
- // LOG_DEBUG("FIX QUAL=%d, TYPE=%d\n", fixQual, fixType);
+ // LOG_DEBUG("FIX QUAL=%d, TYPE=%d\n", fixQual, fixType);
#endif
// check if GPS has an acceptable lock
@@ -1082,6 +1268,10 @@ bool GPS::lookForLocation()
reader.date.age(), reader.time.age());
#endif // GPS_EXTRAVERBOSE
+ // Is this a new point or are we re-reading the previous one?
+ if (!reader.location.isUpdated())
+ return false;
+
// check if a complete GPS solution set is available for reading
// tinyGPSDatum::age() also includes isValid() test
// FIXME
@@ -1094,10 +1284,6 @@ bool GPS::lookForLocation()
return false;
}
- // Is this a new point or are we re-reading the previous one?
- if (!reader.location.isUpdated())
- return false;
-
// We know the solution is fresh and valid, so just read the data
auto loc = reader.location.value();
@@ -1247,4 +1433,17 @@ int32_t GPS::disable()
setAwake(false);
return INT32_MAX;
+}
+
+void GPS::toggleGpsMode()
+{
+ if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
+ config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_DISABLED;
+ LOG_DEBUG("Flag set to false for gps power. GpsMode: DISABLED\n");
+ disable();
+ } else if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_DISABLED) {
+ config.position.gps_mode = meshtastic_Config_PositionConfig_GpsMode_ENABLED;
+ LOG_DEBUG("Flag set to true to restore power. GpsMode: ENABLED\n");
+ enable();
+ }
}
\ No newline at end of file
diff --git a/src/gps/GPS.h b/src/gps/GPS.h
index d52c79182..77e1d8042 100644
--- a/src/gps/GPS.h
+++ b/src/gps/GPS.h
@@ -23,7 +23,7 @@ struct uBloxGnssModelInfo {
typedef enum {
GNSS_MODEL_MTK,
GNSS_MODEL_UBLOX,
- GNSS_MODEL_UC6850,
+ GNSS_MODEL_UC6580,
GNSS_MODEL_UNKNOWN,
} GnssModel_t;
@@ -70,7 +70,7 @@ class GPS : private concurrency::OSThread
/**
* hasValidLocation - indicates that the position variables contain a complete
- * GPS location, valid and fresh (< gps_update_interval + gps_attempt_time)
+ * GPS location, valid and fresh (< gps_update_interval + position_broadcast_secs)
*/
bool hasValidLocation = false; // default to false, until we complete our first read
@@ -95,23 +95,44 @@ class GPS : private concurrency::OSThread
static HardwareSerial *_serial_gps;
static uint8_t _message_PMREQ[];
+ static uint8_t _message_PMREQ_10[];
static const uint8_t _message_CFG_RXM_PSM[];
static const uint8_t _message_CFG_RXM_ECO[];
static const uint8_t _message_CFG_PM2[];
static const uint8_t _message_GNSS_7[];
- static const uint8_t _message_GNSS[];
- static const uint8_t _message_JAM[];
+ static const uint8_t _message_GNSS_8[];
+ static const uint8_t _message_JAM_6_7[];
+ static const uint8_t _message_JAM_8[];
static const uint8_t _message_NAVX5[];
+ static const uint8_t _message_NAVX5_8[];
+ static const uint8_t _message_NMEA[];
+ static const uint8_t _message_DISABLE_TXT_INFO[];
static const uint8_t _message_1HZ[];
- static const uint8_t _message_GGL[];
+ static const uint8_t _message_GLL[];
static const uint8_t _message_GSA[];
static const uint8_t _message_GSV[];
static const uint8_t _message_VTG[];
static const uint8_t _message_RMC[];
+ static const uint8_t _message_AID[];
static const uint8_t _message_GGA[];
static const uint8_t _message_PMS[];
static const uint8_t _message_SAVE[];
+ // VALSET Commands for M10
+ static const uint8_t _message_VALSET_PM[];
+ static const uint8_t _message_VALSET_PM_RAM[];
+ static const uint8_t _message_VALSET_PM_BBR[];
+ static const uint8_t _message_VALSET_ITFM_RAM[];
+ static const uint8_t _message_VALSET_ITFM_BBR[];
+ static const uint8_t _message_VALSET_DISABLE_NMEA_RAM[];
+ static const uint8_t _message_VALSET_DISABLE_NMEA_BBR[];
+ static const uint8_t _message_VALSET_DISABLE_TXT_INFO_RAM[];
+ static const uint8_t _message_VALSET_DISABLE_TXT_INFO_BBR[];
+ static const uint8_t _message_VALSET_ENABLE_NMEA_RAM[];
+ static const uint8_t _message_VALSET_ENABLE_NMEA_BBR[];
+ static const uint8_t _message_VALSET_DISABLE_SBAS_RAM[];
+ static const uint8_t _message_VALSET_DISABLE_SBAS_BBR[];
+
meshtastic_Position p = meshtastic_Position_init_default;
GPS() : concurrency::OSThread("GPS") {}
@@ -132,6 +153,9 @@ class GPS : private concurrency::OSThread
// Disable the thread
int32_t disable() override;
+ // toggle between enabled/disabled
+ void toggleGpsMode();
+
void setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime);
/// Returns true if we have acquired GPS lock.
@@ -143,7 +167,7 @@ class GPS : private concurrency::OSThread
/// Return true if we are connected to a GPS
bool isConnected() const { return hasGPS; }
- bool isPowerSaving() const { return !config.position.gps_enabled; }
+ bool isPowerSaving() const { return config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED; }
// Empty the input buffer as quickly as possible
void clearBuffer();
diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp
index ef438a7dd..10e9e0331 100644
--- a/src/gps/RTC.cpp
+++ b/src/gps/RTC.cpp
@@ -152,7 +152,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
#endif
// nrf52 doesn't have a readable RTC (yet - software not written)
-#ifdef HAS_RTC
+#if HAS_RTC
readFromRTC();
#endif
@@ -208,4 +208,4 @@ uint32_t getTime()
uint32_t getValidTime(RTCQuality minQuality)
{
return (currentQuality >= minQuality) ? getTime() : 0;
-}
\ No newline at end of file
+}
diff --git a/src/gps/ubx.h b/src/gps/ubx.h
index bc839c41e..5b2cb24ce 100644
--- a/src/gps/ubx.h
+++ b/src/gps/ubx.h
@@ -1,8 +1,16 @@
+// Power Management
+
uint8_t GPS::_message_PMREQ[] PROGMEM = {
- 0x00, 0x00, // 4 bytes duration of request task
- 0x00, 0x00, // (milliseconds)
- 0x02, 0x00, // Task flag bitfield
- 0x00, 0x00 // byte index 1 = sleep mode
+ 0x00, 0x00, 0x00, 0x00, // 4 bytes duration of request task (milliseconds)
+ 0x02, 0x00, 0x00, 0x00 // Bitfield, set backup = 1
+};
+
+uint8_t GPS::_message_PMREQ_10[] PROGMEM = {
+ 0x00, // version (0 for this version)
+ 0x00, 0x00, 0x00, // Reserved 1
+ 0x00, 0x00, 0x00, 0x00, // 4 bytes duration of request task (milliseconds)
+ 0x06, 0x00, 0x00, 0x00, // Bitfield, set backup =1 and force =1
+ 0x08, 0x00, 0x00, 0x00 // wakeupSources Wake on uartrx
};
const uint8_t GPS::_message_CFG_RXM_PSM[] PROGMEM = {
@@ -10,26 +18,37 @@ const uint8_t GPS::_message_CFG_RXM_PSM[] PROGMEM = {
0x01 // Power save mode
};
+// only for Neo-6
const uint8_t GPS::_message_CFG_RXM_ECO[] PROGMEM = {
0x08, // Reserved
0x04 // eco mode
};
const uint8_t GPS::_message_CFG_PM2[] PROGMEM = {
- 0x01, 0x06, 0x00, 0x00, // version, Reserved
- 0x0E, 0x81, 0x43, 0x01, // flags
+ 0x01, // version
+ 0x00, // Reserved 1, set to 0x06 by u-Center
+ 0x00, // Reserved 2
+ 0x00, // Reserved 1
+ 0x00, 0x11, 0x03, 0x00, // flags-> cyclic mode, wait for normal fix ok, do not wake to update RTC, doNotEnterOff,
+ // LimitPeakCurrent
0xE8, 0x03, 0x00, 0x00, // update period 1000 ms
0x10, 0x27, 0x00, 0x00, // search period 10s
- 0x00, 0x00, 0x00, 0x00, // Grod offset 0
+ 0x00, 0x00, 0x00, 0x00, // Grid offset 0
0x01, 0x00, // onTime 1 second
0x00, 0x00, // min search time 0
- 0x2C, 0x01, // reserved
- 0x00, 0x00, 0x4F, 0xC1, // reserved
- 0x03, 0x00, 0x87, 0x02, // reserved
- 0x00, 0x00, 0xFF, 0x00, // reserved
- 0x01, 0x00, 0xD6, 0x4D // reserved
+ 0x00, 0x00, // 0x2C, 0x01, // reserved 4
+ 0x00, 0x00, // 0x00, 0x00, // reserved 5
+ 0x00, 0x00, 0x00, 0x00, // 0x4F, 0xC1, 0x03, 0x00, // reserved 6
+ 0x00, 0x00, 0x00, 0x00, // 0x87, 0x02, 0x00, 0x00, // reserved 7
+ 0x00, // 0xFF, // reserved 8
+ 0x00, // 0x00, // reserved 9
+ 0x00, 0x00, // 0x00, 0x00, // reserved 10
+ 0x00, 0x00, 0x00, 0x00 // 0x64, 0x40, 0x01, 0x00 // reserved 11
};
+// Constallation setup, none required for Neo-6
+
+// For Neo-7 GPS & SBAS
const uint8_t GPS::_message_GNSS_7[] = {
0x00, // msgVer (0 for this version)
0x00, // numTrkChHw (max number of hardware channels, read only, so it's always 0)
@@ -46,123 +65,228 @@ const uint8_t GPS::_message_GNSS_7[] = {
// to overwrite a saved state with identical values, no ACK/NAK is received, contrary to
// what is specified in the Ublox documentation.
// There is also a possibility that the module may be GPS-only.
-const uint8_t GPS::_message_GNSS[] = {
- 0x00, // msgVer (0 for this version)
- 0x00, // numTrkChHw (max number of hardware channels, read only, so it's always 0)
- 0xff, // numTrkChUse (max number of channels to use, 0xff = max available)
- 0x03, // numConfigBlocks (number of GNSS systems), most modules support maximum 3 GNSS systems
- // GNSS config format: gnssId, resTrkCh, maxTrkCh, reserved1, flags
+
+// For M8 GPS, GLONASS, Galileo, SBAS, QZSS
+const uint8_t GPS::_message_GNSS_8[] = {
+ 0x00, // msgVer (0 for this version)
+ 0x00, // numTrkChHw (max number of hardware channels, read only, so it's always 0)
+ 0xff, // numTrkChUse (max number of channels to use, 0xff = max available)
+ 0x05, // numConfigBlocks (number of GNSS systems)
+ // GNSS config format: gnssId, resTrkCh, maxTrkCh, reserved1, flags
0x00, 0x08, 0x10, 0x00, 0x01, 0x00, 0x01, 0x01, // GPS
0x01, 0x01, 0x03, 0x00, 0x01, 0x00, 0x01, 0x01, // SBAS
- 0x06, 0x08, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x01 // GLONASS
+ 0x02, 0x04, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, // Galileo
+ 0x05, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0x01, // QZSS
+ 0x06, 0x08, 0x0E, 0x00, 0x01, 0x00, 0x01, 0x01 // GLONASS
+};
+/*
+// For M8 GPS, GLONASS, BeiDou, SBAS, QZSS
+const uint8_t GPS::_message_GNSS_8_B[] = {
+ 0x00, // msgVer (0 for this version)
+ 0x00, // numTrkChHw (max number of hardware channels, read only, so it's always 0)
+ 0xff, // numTrkChUse (max number of channels to use, 0xff = max available) read only for protocol >23
+ 0x05, // numConfigBlocks (number of GNSS systems)
+ // GNSS config format: gnssId, resTrkCh, maxTrkCh, reserved1, flags
+ 0x00, 0x08, 0x10, 0x00, 0x01, 0x00, 0x01, 0x01, // GPS
+ 0x01, 0x01, 0x03, 0x00, 0x01, 0x00, 0x01, 0x01, // SBAS
+ 0x03, 0x08, 0x10, 0x00, 0x01, 0x00, 0x01, 0x01, // BeiDou
+ 0x05, 0x00, 0x03, 0x00, 0x01, 0x00, 0x01, 0x01, // QZSS
+ 0x06, 0x08, 0x0E, 0x00, 0x01, 0x00, 0x01, 0x01 // GLONASS
+};
+*/
+
+// For M8 we want to enable NMEA version 4.10 messages to allow for Galileo and or BeiDou
+const uint8_t GPS::_message_NMEA[]{
+ 0x00, // filter flags
+ 0x41, // NMEA Version
+ 0x00, // Max number of SVs to report per TaklerId
+ 0x02, // flags
+ 0x00, 0x00, 0x00, 0x00, // gnssToFilter
+ 0x00, // svNumbering
+ 0x00, // mainTalkerId
+ 0x00, // gsvTalkerId
+ 0x01, // Message version
+ 0x00, 0x00, // bdsTalkerId 2 chars 0=default
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Reserved
+};
+// Enable jamming/interference monitor
+
+// For Neo-6, Max-7 and Neo-7
+const uint8_t GPS::_message_JAM_6_7[] = {
+ 0xf3, 0xac, 0x62, 0xad, // config1 bbThreshold = 3, cwThreshold = 15, enable = 1, reserved bits 0x16B156
+ 0x1e, 0x03, 0x00, 0x00 // config2 antennaSetting Unknown = 0, reserved 3, = 0x00,0x00, reserved 2 = 0x31E
};
-// Enable interference resistance, because we are using LoRa, WiFi and Bluetooth on same board,
-// and we need to reduce interference from them
-const uint8_t GPS::_message_JAM[] = {
- // bbThreshold (Broadband jamming detection threshold) is set to 0x3F (63 in decimal)
- // cwThreshold (CW jamming detection threshold) is set to 0x10 (16 in decimal)
- // algorithmBits (Reserved algorithm settings) is set to 0x16B156 as recommended
- // enable (Enable interference detection) is set to 1 (enabled)
- 0x3F, 0x10, 0xB1, 0x56, // config: Interference config word
- // generalBits (General settings) is set to 0x31E as recommended
- // antSetting (Antenna setting, 0=unknown, 1=passive, 2=active) is set to 0 (unknown)
- // ToDo: Set to 1 (passive) or 2 (active) if known, for example from UBX-MON-HW, or from board info
- // enable2 (Set to 1 to scan auxiliary bands, u-blox 8 / u-blox M8 only, otherwise ignored) is set to 1
- // (enabled)
- 0x1E, 0x03, 0x00, 0x01 // config2: Extra settings for jamming/interference monitor
+// For M8
+const uint8_t GPS::_message_JAM_8[] = {
+ 0xf3, 0xac, 0x62, 0xad, // config1 bbThreshold = 3, cwThreshold = 15, enable1 = 1, reserved bits 0x16B156
+ 0x1e, 0x43, 0x00, 0x00 // config2 antennaSetting Unknown = 0, enable2 = 1, generalBits = 0x31E
};
// Configure navigation engine expert settings:
+// there are many variations of what were Reserved fields for the Neo-6 in later versions
+// ToDo: check UBX-MON-VER for module type and protocol version
+
+// For the Neo-6
const uint8_t GPS::_message_NAVX5[] = {
- 0x00, 0x00, // msgVer (0 for this version)
- // minMax flag = 1: apply min/max SVs settings
- // minCno flag = 1: apply minimum C/N0 setting
- // initial3dfix flag = 0: apply initial 3D fix settings
- // aop flag = 1: apply aopCfg (useAOP flag) settings (AssistNow Autonomous)
- 0x1B, 0x00, // mask1 (First parameters bitmask)
- // adr flag = 0: apply ADR sensor fusion on/off setting (useAdr flag)
- // If firmware is not ADR/UDR, enabling this flag will fail configuration
- // ToDo: check this with UBX-MON-VER
- 0x00, 0x00, 0x00, 0x00, // mask2 (Second parameters bitmask)
- 0x00, 0x00, // Reserved
- 0x03, // minSVs (Minimum number of satellites for navigation) = 3
- 0x10, // maxSVs (Maximum number of satellites for navigation) = 16
- 0x06, // minCNO (Minimum satellite signal level for navigation) = 6 dBHz
- 0x00, // Reserved
- 0x00, // iniFix3D (Initial fix must be 3D) = 0 (disabled)
- 0x00, 0x00, // Reserved
- 0x00, // ackAiding (Issue acknowledgements for assistance message input) = 0 (disabled)
- 0x00, 0x00, // Reserved
- 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Reserved
- 0x00, // Reserved
- 0x01, // aopCfg (AssistNow Autonomous configuration) = 1 (enabled)
- 0x00, 0x00, // Reserved
- 0x00, 0x00, // Reserved
- 0x00, 0x00, 0x00, 0x00, // Reserved
- 0x00, 0x00, 0x00, // Reserved
- 0x01, // useAdr (Enable/disable ADR sensor fusion) = 1 (enabled)
+ 0x00, 0x00, // msgVer (0 for this version)
+ 0x4c, 0x66, // mask1
+ 0x00, 0x00, 0x00, 0x00, // Reserved 0
+ 0x00, // Reserved 1
+ 0x00, // Reserved 2
+ 0x03, // minSVs (Minimum number of satellites for navigation) = 3
+ 0x10, // maxSVs (Maximum number of satellites for navigation) = 16
+ 0x06, // minCNO (Minimum satellite signal level for navigation) = 6 dBHz
+ 0x00, // Reserved 5
+ 0x00, // iniFix3D (Initial fix must be 3D) (0 = false 1 = true)
+ 0x00, // Reserved 6
+ 0x00, // Reserved 7
+ 0x00, // Reserved 8
+ 0x00, 0x00, // wknRollover 0 = firmware default
+ 0x00, 0x00, 0x00, 0x00, // Reserved 9
+ 0x00, // Reserved 10
+ 0x00, // Reserved 11
+ 0x00, // usePPP (Precice Point Positioning) (0 = false, 1 = true)
+ 0x01, // useAOP (AssistNow Autonomous configuration) = 1 (enabled)
+ 0x00, // Reserved 12
+ 0x00, // Reserved 13
+ 0x00, 0x00, // aopOrbMaxErr = 0 to reset to firmware default
+ 0x00, // Reserved 14
+ 0x00, // Reserved 15
+ 0x00, 0x00, // Reserved 3
+ 0x00, 0x00, 0x00, 0x00 // Reserved 4
+};
+// For the M8
+const uint8_t GPS::_message_NAVX5_8[] = {
+ 0x02, 0x00, // msgVer (2 for this version)
+ 0x4c, 0x66, // mask1
+ 0x00, 0x00, 0x00, 0x00, // mask2
+ 0x00, 0x00, // Reserved 1
+ 0x03, // minSVs (Minimum number of satellites for navigation) = 3
+ 0x10, // maxSVs (Maximum number of satellites for navigation) = 16
+ 0x06, // minCNO (Minimum satellite signal level for navigation) = 6 dBHz
+ 0x00, // Reserved 2
+ 0x00, // iniFix3D (Initial fix must be 3D) (0 = false 1 = true)
+ 0x00, 0x00, // Reserved 3
+ 0x00, // ackAiding
+ 0x00, 0x00, // wknRollover 0 = firmware default
+ 0x00, // sigAttenCompMode
+ 0x00, // Reserved 4
+ 0x00, 0x00, // Reserved 5
+ 0x00, 0x00, // Reserved 6
+ 0x00, // usePPP (Precice Point Positioning) (0 = false, 1 = true)
+ 0x01, // aopCfg (AssistNow Autonomous configuration) = 1 (enabled)
+ 0x00, 0x00, // Reserved 7
+ 0x00, 0x00, // aopOrbMaxErr = 0 to reset to firmware default
+ 0x00, 0x00, 0x00, 0x00, // Reserved 8
+ 0x00, 0x00, 0x00, // Reserved 9
+ 0x00 // useAdr
};
// Set GPS update rate to 1Hz
// Lowering the update rate helps to save power.
// Additionally, for some new modules like the M9/M10, an update rate lower than 5Hz
// is recommended to avoid a known issue with satellites disappearing.
+// The module defaults for M8, M9, M10 are the same as we use here so no update is necessary
const uint8_t GPS::_message_1HZ[] = {
0xE8, 0x03, // Measurement Rate (1000ms for 1Hz)
0x01, 0x00, // Navigation rate, always 1 in GPS mode
- 0x01, 0x00, // Time reference
+ 0x01, 0x00 // Time reference
};
-// Disable GGL. GGL - Geographic position (latitude and longitude), which provides the current geographical
+// Disable GLL. GLL - Geographic position (latitude and longitude), which provides the current geographical
// coordinates.
-const uint8_t GPS::_message_GGL[] = {
- 0xF0, 0x01, // NMEA ID for GLL
- 0x01, // I/O Target 0=I/O, 1=UART1, 2=UART2, 3=USB, 4=SPI
- 0x00, // Disable
- 0x01, 0x01, 0x01, 0x01 // Reserved
+const uint8_t GPS::_message_GLL[] = {
+ 0xF0, 0x01, // NMEA ID for GLL
+ 0x00, // Rate for DDC
+ 0x00, // Rate for UART1
+ 0x00, // Rate for UART2
+ 0x00, // Rate for USB
+ 0x00, // Rate for SPI
+ 0x00 // Reserved
};
// Enable 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
- 0x01, // I/O Target 0=I/O, 1=UART1, 2=UART2, 3=USB, 4=SPI
- 0x01, // Enable
- 0x01, 0x01, 0x01, 0x01 // Reserved
+ 0xF0, 0x02, // NMEA ID for GSA
+ 0x00, // Rate for DDC
+ 0x01, // Rate for UART1
+ 0x00, // Rate for UART2
+ 0x01, // Rate for USB usefull for native linux
+ 0x00, // Rate for SPI
+ 0x00 // Reserved
};
// Disable GSV. GSV - Satellites in view, details the number and location of satellites in view.
const uint8_t GPS::_message_GSV[] = {
- 0xF0, 0x03, // NMEA ID for GSV
- 0x01, // I/O Target 0=I/O, 1=UART1, 2=UART2, 3=USB, 4=SPI
- 0x00, // Disable
- 0x01, 0x01, 0x01, 0x01 // Reserved
+ 0xF0, 0x03, // NMEA ID for GSV
+ 0x00, // Rate for DDC
+ 0x00, // Rate for UART1
+ 0x00, // Rate for UART2
+ 0x00, // Rate for USB
+ 0x00, // Rate for SPI
+ 0x00 // Reserved
};
// Disable VTG. VTG - Track made good and ground speed, which provides course and speed information relative to
// the ground.
const uint8_t GPS::_message_VTG[] = {
- 0xF0, 0x05, // NMEA ID for VTG
- 0x01, // I/O Target 0=I/O, 1=UART1, 2=UART2, 3=USB, 4=SPI
- 0x00, // Disable
- 0x01, 0x01, 0x01, 0x01 // Reserved
+ 0xF0, 0x05, // NMEA ID for VTG
+ 0x00, // Rate for DDC
+ 0x00, // Rate for UART1
+ 0x00, // Rate for UART2
+ 0x00, // Rate for USB
+ 0x00, // Rate for SPI
+ 0x00 // Reserved
};
// Enable RMC. RMC - Recommended Minimum data, the essential gps pvt (position, velocity, time) data.
const uint8_t GPS::_message_RMC[] = {
- 0xF0, 0x04, // NMEA ID for RMC
- 0x01, // I/O Target 0=I/O, 1=UART1, 2=UART2, 3=USB, 4=SPI
- 0x01, // Enable
- 0x01, 0x01, 0x01, 0x01 // Reserved
+ 0xF0, 0x04, // NMEA ID for RMC
+ 0x00, // Rate for DDC
+ 0x01, // Rate for UART1
+ 0x00, // Rate for UART2
+ 0x01, // Rate for USB usefull for native linux
+ 0x00, // Rate for SPI
+ 0x00 // Reserved
};
// Enable GGA. GGA - Global Positioning System Fix Data, which provides 3D location and accuracy data.
const uint8_t GPS::_message_GGA[] = {
- 0xF0, 0x00, // NMEA ID for GGA
- 0x01, // I/O Target 0=I/O, 1=UART1, 2=UART2, 3=USB, 4=SPI
- 0x01, // Enable
- 0x01, 0x01, 0x01, 0x01 // Reserved
+ 0xF0, 0x00, // NMEA ID for GGA
+ 0x00, // Rate for DDC
+ 0x01, // Rate for UART1
+ 0x00, // Rate for UART2
+ 0x01, // Rate for USB, usefull for native linux
+ 0x00, // Rate for SPI
+ 0x00 // Reserved
+};
+
+// Disable UBX-AID-ALPSRV as it may confuse TinyGPS. The Neo-6 seems to send this message
+// whether the AID Autonomous is enabled or not
+const uint8_t GPS::_message_AID[] = {
+ 0x0B, 0x32, // NMEA ID for UBX-AID-ALPSRV
+ 0x00, // Rate for DDC
+ 0x00, // Rate for UART1
+ 0x00, // Rate for UART2
+ 0x00, // Rate for USB
+ 0x00, // Rate for SPI
+ 0x00 // Reserved
+};
+
+// Turn off TEXT INFO Messages for all but M10 series
+
+// B5 62 06 02 0A 00 01 00 00 00 03 03 00 03 03 00 1F 20
+const uint8_t GPS::_message_DISABLE_TXT_INFO[] = {
+ 0x01, // Protocol ID for NMEA
+ 0x00, 0x00, 0x00, // Reserved
+ 0x03, // I2C
+ 0x03, // I/O Port 1
+ 0x00, // I/O Port 2
+ 0x03, // USB
+ 0x03, // SPI
+ 0x00 // Reserved
};
// The Power Management configuration allows the GPS module to operate in different power modes for optimized
@@ -176,17 +300,154 @@ const uint8_t GPS::_message_GGA[] = {
// is set to Interval; otherwise, it must be set to '0'. The 'onTime' field specifies the duration of the ON phase
// and must be smaller than the period. It is only valid when the powerSetupValue is set to Interval; otherwise,
// it must be set to '0'.
+// This command applies to M8 products
const uint8_t GPS::_message_PMS[] = {
0x00, // Version (0)
- 0x03, // Power setup value
+ 0x03, // Power setup value 3 = Agresssive 1Hz
0x00, 0x00, // period: not applicable, set to 0
0x00, 0x00, // onTime: not applicable, set to 0
- 0x97, 0x6F // reserved, generated by u-center
+ 0x00, 0x00 // reserved, generated by u-center
};
const uint8_t GPS::_message_SAVE[] = {
0x00, 0x00, 0x00, 0x00, // clearMask: no sections cleared
0xFF, 0xFF, 0x00, 0x00, // saveMask: save all sections
0x00, 0x00, 0x00, 0x00, // loadMask: no sections loaded
- 0x0F // deviceMask: BBR, Flash, EEPROM, and SPI Flash
+ 0x17 // deviceMask: BBR, Flash, EEPROM, and SPI Flash
};
+
+// 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.
+
+// VALSET Commands for M10
+// Please refer to the M10 Protocol Specification:
+// https://content.u-blox.com/sites/default/files/u-blox-M10-SPG-5.10_InterfaceDescription_UBX-21035062.pdf
+// Where the VALSET/VALGET/VALDEL commands are described in detail.
+// and:
+// https://content.u-blox.com/sites/default/files/u-blox-M10-ROM-5.10_ReleaseNotes_UBX-22001426.pdf
+// for interesting insights.
+/*
+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
+
+// 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
+
+// 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};
+
+/*
+CFG-ITFM replaced by 5 valset messages which can be combined into one for RAM and one for BBR
+
+20410001 bbthreshold U1 3
+20410002 cwthreshold U1 15
+1041000d enable L 0 -> 1
+20410010 ant E1 0
+10410013 enable aux L 0 -> 1
+
+
+b5 62 06 8a 0e 00 00 01 00 00 0d 00 41 10 01 13 00 41 10 01 63 c6
+*/
+const uint8_t GPS::_message_VALSET_ITFM_RAM[] = {0x00, 0x01, 0x00, 0x00, 0x0d, 0x00, 0x41,
+ 0x10, 0x01, 0x13, 0x00, 0x41, 0x10, 0x01};
+const uint8_t GPS::_message_VALSET_ITFM_BBR[] = {0x00, 0x02, 0x00, 0x00, 0x0d, 0x00, 0x41,
+ 0x10, 0x01, 0x13, 0x00, 0x41, 0x10, 0x01};
+
+// Turn off all NMEA messages:
+// Ram layer config message:
+// b5 62 06 8a 22 00 00 01 00 00 c0 00 91 20 00 ca 00 91 20 00 c5 00 91 20 00 ac 00 91 20 00 b1 00 91 20 00 bb 00 91 20 00 40 8f
+
+// Disable GLL, GSV, VTG messages in BBR layer
+// BBR layer config message:
+// b5 62 06 8a 13 00 00 02 00 00 ca 00 91 20 00 c5 00 91 20 00 b1 00 91 20 00 f8 4e
+
+const uint8_t GPS::_message_VALSET_DISABLE_NMEA_RAM[] = {
+ /*0x00, 0x01, 0x00, 0x00, 0xca, 0x00, 0x91, 0x20, 0x00, 0xc5, 0x00, 0x91, 0x20, 0x00, 0xb1, 0x00, 0x91, 0x20, 0x00 */
+ 0x00, 0x01, 0x00, 0x00, 0xc0, 0x00, 0x91, 0x20, 0x00, 0xca, 0x00, 0x91, 0x20, 0x00, 0xc5, 0x00, 0x91,
+ 0x20, 0x00, 0xac, 0x00, 0x91, 0x20, 0x00, 0xb1, 0x00, 0x91, 0x20, 0x00, 0xbb, 0x00, 0x91, 0x20, 0x00};
+
+const uint8_t GPS::_message_VALSET_DISABLE_NMEA_BBR[] = {0x00, 0x02, 0x00, 0x00, 0xca, 0x00, 0x91, 0x20, 0x00, 0xc5,
+ 0x00, 0x91, 0x20, 0x00, 0xb1, 0x00, 0x91, 0x20, 0x00};
+
+// Turn off text info messages:
+// Ram layer config message:
+// b5 62 06 8a 09 00 00 01 00 00 07 00 92 20 06 59 50
+
+// 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
+
+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_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:
+
+PowerSave doesn't work with SBAS, seems like you can have SBAS enabled, but it will never lock
+onto the SBAS sats.
+PowerSave doesn't work with BDS B1C, u-blox says use B1l instead.
+BDS B1l cannot be enabled with BDS B1C or GLONASS L1OF, so GLONASS will work with B1C, but not B1l
+So no powersave with GLONASS and BDS B1l enabled.
+So disable GLONASS and use BDS B1l, which is part of the default M10 config.
+
+GNSS configuration:
+
+Default GNSS configuration is: GPS, Galileo, BDS B1l, with QZSS and SBAS enabled.
+The PMREQ puts the receiver to sleep and wakeup re-acquires really fast and seems to not need
+the PM config. Lets try without it.
+PMREQ sort of works with SBAS, but the awake time is too short to re-acquire any SBAS sats.
+The defination of "Got Fix" doesn't seem to include SBAS. Much more too this...
+Even if it was, it can take minutes (up to 12.5),
+even under good sat visability conditions to re-acquire the SBAS data.
+
+Another effect fo the quick transition to sleep is that no other sats will be acquired so the
+sat count will tend to remain at what the initial fix was.
+*/
+
+// GNSS disable SBAS as recommended by u-blox if using GNSS defaults and power save mode
+/*
+Ram layer config message:
+b5 62 06 8a 0e 00 00 01 00 00 20 00 31 10 00 05 00 31 10 00 46 87
+
+BBR layer config message:
+b5 62 06 8a 0e 00 00 02 00 00 20 00 31 10 00 05 00 31 10 00 47 94
+*/
\ No newline at end of file
diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp
index 91365ce60..f2642a56c 100644
--- a/src/graphics/EInkDisplay2.cpp
+++ b/src/graphics/EInkDisplay2.cpp
@@ -7,7 +7,8 @@
#include "main.h"
#include
-#if defined(HELTEC_WIRELESS_PAPER)// || defined(LORA_TYPE)
+
+#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) // || defined(LORA_TYPE)
SPIClass *hspi = NULL;
#endif
@@ -50,8 +51,13 @@ SPIClass *hspi = NULL;
#define TECHO_DISPLAY_MODEL GxEPD2_154_M09
#elif defined(HELTEC_WIRELESS_PAPER)
-//#define TECHO_DISPLAY_MODEL GxEPD2_213_T5D
+// #define TECHO_DISPLAY_MODEL GxEPD2_213_T5D
+#define TECHO_DISPLAY_MODEL GxEPD2_213_FC1
+
+#elif defined(HELTEC_WIRELESS_PAPER_V1_0)
+// 2.13" 122x250 - DEPG0213BNS800
#define TECHO_DISPLAY_MODEL GxEPD2_213_BN
+
#endif
GxEPD2_BW *adafruitDisplay;
@@ -59,12 +65,12 @@ GxEPD2_BW *adafruitDisplay;
EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
{
#if defined(TTGO_T_ECHO)
- setGeometry(GEOMETRY_RAWMODE, TECHO_DISPLAY_MODEL::WIDTH, TECHO_DISPLAY_MODEL::HEIGHT);
+ setGeometry(GEOMETRY_RAWMODE, 200, 200);
#elif defined(RAK4630)
// GxEPD2_213_BN - RAK14000 2.13 inch b/w 250x122
setGeometry(GEOMETRY_RAWMODE, 250, 122);
-
+ this->displayBufferSize = 250 * (128 / 8);
// GxEPD2_420_M01
// setGeometry(GEOMETRY_RAWMODE, 300, 400);
@@ -74,8 +80,19 @@ EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY
// GxEPD2_154_M09
// setGeometry(GEOMETRY_RAWMODE, 200, 200);
+#elif defined(HELTEC_WIRELESS_PAPER_V1_0)
+
+ // The display's memory is actually 128px x 250px
+ // Setting the buffersize manually prevents 122/8 truncating to a 15 byte width
+ // (Or something like that..)
+
+ this->geometry = GEOMETRY_RAWMODE;
+ this->displayWidth = 250;
+ this->displayHeight = 122;
+ this->displayBufferSize = 250 * (128 / 8);
+
#elif defined(HELTEC_WIRELESS_PAPER)
- // setGeometry(GEOMETRY_RAWMODE, 212, 104);
+ // GxEPD2_213_BN - 2.13 inch b/w 250x122
setGeometry(GEOMETRY_RAWMODE, 250, 122);
#elif defined(MAKERPYTHON)
// GxEPD2_290_T5D
@@ -97,6 +114,12 @@ EInkDisplay::EInkDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY
setGeometry(GEOMETRY_RAWMODE, 296, 128);
LOG_DEBUG("GEOMETRY_RAWMODE, 296, 128\n");
+#elif defined(ESP32_S3_PICO)
+
+ // GxEPD2_290_T94_V2
+ setGeometry(GEOMETRY_RAWMODE, EPD_WIDTH, EPD_HEIGHT);
+ LOG_DEBUG("GEOMETRY_RAWMODE, 296, 128\n");
+
#endif
// setGeometry(GEOMETRY_RAWMODE, 128, 64); // old resolution
// setGeometry(GEOMETRY_128_64); // We originally used this because I wasn't sure if rawmode worked - it does
@@ -113,60 +136,69 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
// No need to grab this lock because we are on our own SPI bus
// concurrency::LockGuard g(spiLock);
+#if defined(USE_EINK_DYNAMIC_PARTIAL)
+ // Decide if update is partial or full
+ bool continueUpdate = determineRefreshMode();
+ if (!continueUpdate)
+ return false;
+#else
+
uint32_t now = millis();
uint32_t sinceLast = now - lastDrawMsec;
- if (adafruitDisplay && (sinceLast > msecLimit || lastDrawMsec == 0)) {
+ if (adafruitDisplay && (sinceLast > msecLimit || lastDrawMsec == 0))
lastDrawMsec = now;
-
- // FIXME - only draw bits have changed (use backbuf similar to the other displays)
- // tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK);
- 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 ? COLORED : UNCOLORED);
- }
- }
-
- LOG_DEBUG("Updating E-Paper... ");
-
-#if defined(TTGO_T_ECHO)
- // ePaper.Reset(); // wake the screen from sleep
- adafruitDisplay->display(false); // FIXME, use partial update mode
-#elif defined(RAK4630) || defined(MAKERPYTHON)
-
- // RAK14000 2.13 inch b/w 250x122 actually now does support partial updates
-
- // Full update mode (slow)
- // adafruitDisplay->display(false); // FIXME, use partial update mode
-
- // Only enable for e-Paper with support for partial updates and comment out above adafruitDisplay->display(false);
- // 1.54 inch 200x200 - GxEPD2_154_M09
- // 2.13 inch 250x122 - GxEPD2_213_BN
- // 2.9 inch 296x128 - GxEPD2_290_T5D
- // 4.2 inch 300x400 - GxEPD2_420_M01
- adafruitDisplay->nextPage();
-
-#elif defined(PCA10059) || defined(M5_COREINK) || defined(LORA_TYPE)
- adafruitDisplay->nextPage();
-
-#elif defined(PRIVATE_HW) || defined(my)
- adafruitDisplay->nextPage();
+ else
+ return false;
#endif
- // Put screen to sleep to save power (possibly not necessary because we already did poweroff inside of display)
- adafruitDisplay->hibernate();
- LOG_DEBUG("done\n");
-
- return true;
- } else {
- // LOG_DEBUG("Skipping eink display\n");
- return false;
+ // FIXME - only draw bits have changed (use backbuf similar to the other displays)
+ // tft.drawBitmap(0, 0, buffer, 128, 64, TFT_YELLOW, TFT_BLACK);
+ 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 ? COLORED : UNCOLORED);
+ }
}
+
+ LOG_DEBUG("Updating E-Paper... ");
+
+#if defined(TTGO_T_ECHO)
+ adafruitDisplay->nextPage();
+#elif defined(RAK4630) || defined(MAKERPYTHON)
+
+ // RAK14000 2.13 inch b/w 250x122 actually now does support partial updates
+
+ // Full update mode (slow)
+ // adafruitDisplay->display(false); // FIXME, use partial update mode
+
+ // Only enable for e-Paper with support for partial updates and comment out above adafruitDisplay->display(false);
+ // 1.54 inch 200x200 - GxEPD2_154_M09
+ // 2.13 inch 250x122 - GxEPD2_213_BN
+ // 2.9 inch 296x128 - GxEPD2_290_T5D
+ // 4.2 inch 300x400 - GxEPD2_420_M01
+ adafruitDisplay->nextPage();
+
+#elif defined(PCA10059) || defined(M5_COREINK) || defined(LORA_TYPE)
+ adafruitDisplay->nextPage();
+#elif defined(HELTEC_WIRELESS_PAPER_V1_0)
+ adafruitDisplay->nextPage();
+#elif defined(HELTEC_WIRELESS_PAPER)
+ adafruitDisplay->nextPage();
+#elif defined(ESP32_S3_PICO)
+ adafruitDisplay->nextPage();
+#elif defined(PRIVATE_HW) || defined(my)
+ adafruitDisplay->nextPage();
+#endif
+
+ // Put screen to sleep to save power (possibly not necessary because we already did poweroff inside of display)
+ adafruitDisplay->hibernate();
+ LOG_DEBUG("done\n");
+
+ return true;
}
// Write the buffer to the display memory
@@ -175,8 +207,16 @@ void EInkDisplay::display(void)
// We don't allow regular 'dumb' display() calls to draw on eink until we've shown
// at least one forceDisplay() keyframe. This prevents flashing when we should the critical
// bootscreen (that we want to look nice)
- if (lastDrawMsec)
+
+#ifdef USE_EINK_DYNAMIC_PARTIAL
+ lowPriority();
+ forceDisplay();
+ highPriority();
+#else
+ if (lastDrawMsec) {
forceDisplay(slowUpdateMsec); // Show the first screen a few seconds after boot, then slower
+ }
+#endif
}
// Send a command to the display (low level function)
@@ -197,14 +237,14 @@ bool EInkDisplay::connect()
LOG_INFO("Doing EInk init\n");
#ifdef PIN_EINK_PWR_ON
- digitalWrite(PIN_EINK_PWR_ON, HIGH); // If we need to assert a pin to power external peripherals
pinMode(PIN_EINK_PWR_ON, OUTPUT);
+ digitalWrite(PIN_EINK_PWR_ON, HIGH); // If we need to assert a pin to power external peripherals
#endif
#ifdef PIN_EINK_EN
// backlight power, HIGH is backlight on, LOW is off
- digitalWrite(PIN_EINK_EN, LOW);
pinMode(PIN_EINK_EN, OUTPUT);
+ digitalWrite(PIN_EINK_EN, LOW);
#endif
#if defined(TTGO_T_ECHO)
@@ -214,16 +254,14 @@ bool EInkDisplay::connect()
adafruitDisplay = new GxEPD2_BW(*lowLevel);
adafruitDisplay->init();
adafruitDisplay->setRotation(3);
+ adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
}
#elif defined(RAK4630) || defined(MAKERPYTHON)
{
if (eink_found) {
auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
-
adafruitDisplay = new GxEPD2_BW(*lowLevel);
-
adafruitDisplay->init(115200, true, 10, false, SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
-
// RAK14000 2.13 inch b/w 250x122 does actually now support partial updates
adafruitDisplay->setRotation(3);
// Partial update support for 1.54, 2.13 RAK14000 b/w , 2.9 and 4.2
@@ -233,15 +271,55 @@ bool EInkDisplay::connect()
(void)adafruitDisplay;
}
}
+
+#elif defined(HELTEC_WIRELESS_PAPER_V1_0)
+ {
+ // Is this a normal boot, or a wake from deep sleep?
+ esp_sleep_wakeup_cause_t wakeReason = esp_sleep_get_wakeup_cause();
+
+ // If waking from sleep, need to reverse rtc_gpio_isolate(), called in cpuDeepSleep()
+ // Otherwise, SPI won't work
+ if (wakeReason != ESP_SLEEP_WAKEUP_UNDEFINED) {
+ // HSPI + other display pins
+ rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_SCLK);
+ rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_DC);
+ rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_RES);
+ rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_BUSY);
+ rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_CS);
+ rtc_gpio_hold_dis((gpio_num_t)PIN_EINK_MOSI);
+ }
+
+ // Start HSPI
+ hspi = new SPIClass(HSPI);
+ hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
+
+ // Enable VExt (ACTIVE LOW)
+ // Unsure if called elsewhere first?
+ delay(100);
+ pinMode(Vext, OUTPUT);
+ digitalWrite(Vext, LOW);
+ delay(100);
+
+ // Create GxEPD2 objects
+ auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
+ adafruitDisplay = new GxEPD2_BW(*lowLevel);
+
+ // Init GxEPD2
+ adafruitDisplay->init();
+ adafruitDisplay->setRotation(3);
+ }
#elif defined(HELTEC_WIRELESS_PAPER)
{
- auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
+ hspi = new SPIClass(HSPI);
+ hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
+ delay(100);
+ pinMode(Vext, OUTPUT);
+ digitalWrite(Vext, LOW);
+ delay(100);
+ auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi);
adafruitDisplay = new GxEPD2_BW(*lowLevel);
- // hspi = new SPIClass(HSPI);
- // hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
- adafruitDisplay->init(115200, true, 10, false, SPI, SPISettings(6000000, MSBFIRST, SPI_MODE0));
+ adafruitDisplay->init();
adafruitDisplay->setRotation(3);
- adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
}
#elif defined(PCA10059)
{
@@ -263,7 +341,7 @@ bool EInkDisplay::connect()
adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
adafruitDisplay->setRotation(0);
adafruitDisplay->setPartialWindow(0, 0, EPD_WIDTH, EPD_HEIGHT);
-#elif defined(my)
+#elif defined(my) || defined(ESP32_S3_PICO)
{
auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
adafruitDisplay = new GxEPD2_BW(*lowLevel);
@@ -277,8 +355,195 @@ bool EInkDisplay::connect()
// adafruitDisplay->fillScreen(UNCOLORED);
// adafruitDisplay->drawCircle(100, 100, 20, COLORED);
// adafruitDisplay->display(false);
-
return true;
}
+// Use a mix of full and partial refreshes, to preserve display health
+#if defined(USE_EINK_DYNAMIC_PARTIAL)
+
+// Suggest that subsequent updates should use partial-refresh
+void EInkDisplay::highPriority()
+{
+ isHighPriority = true;
+}
+
+// Suggest that subsequent updates should use full-refresh
+void EInkDisplay::lowPriority()
+{
+ isHighPriority = false;
+}
+
+// Full-refresh is explicitly requested for next one update - no skipping please
+void EInkDisplay::demandFullRefresh()
+{
+ demandingFull = true;
+}
+
+// configure display for partial-refresh
+void EInkDisplay::configForPartialRefresh()
+{
+ // Display-specific code can go here
+#if defined(PRIVATE_HW)
+#else
+ // Otherwise:
+ adafruitDisplay->setPartialWindow(0, 0, adafruitDisplay->width(), adafruitDisplay->height());
+#endif
+}
+
+// Configure display for full-refresh
+void EInkDisplay::configForFullRefresh()
+{
+ // Display-specific code can go here
+#if defined(PRIVATE_HW)
+#else
+ // Otherwise:
+ adafruitDisplay->setFullWindow();
+#endif
+}
+
+#ifdef EINK_PARTIAL_ERASURE_LIMIT
+// Count black pixels in an image. Used for "erasure tracking"
+int32_t EInkDisplay::countBlackPixels()
+{
+ int32_t blackCount = 0; // Signed, to avoid underflow when comparing
+ for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) {
+ for (uint8_t i = 0; i < 7; i++) {
+ // Check if each bit is black or white
+ blackCount += (buffer[b] >> i) & 1;
+ }
+ }
+ return blackCount;
+}
+
+// Evaluate the (rough) amount of black->white pixel change since last full refresh
+bool EInkDisplay::tooManyErasures()
+{
+ // Ideally, we would compare the new and old buffers, to count *actual* white-to-black pixel changes
+ // but that would require substantially more "code tampering"
+
+ // Get the black pixel stats for this image
+ int32_t blackCount = countBlackPixels();
+ int32_t blackDifference = blackCount - prevBlackCount;
+
+ // Update the running total of "erasures" - black pixels which have become white, since last full-refresh
+ if (blackDifference < 0)
+ erasedSinceFull -= blackDifference;
+
+ // Store black pixel count for next time
+ prevBlackCount = blackCount;
+
+ // Log the running total - help devs setup new boards
+ LOG_DEBUG("Dynamic Partial: erasedSinceFull=%hu, EINK_PARTIAL_ERASURE_LIMIT=%hu\n", erasedSinceFull,
+ EINK_PARTIAL_ERASURE_LIMIT);
+
+ // Check if too many pixels have been erased
+ if (erasedSinceFull > EINK_PARTIAL_ERASURE_LIMIT)
+ return true; // Too many
+ else
+ return false; // Still okay
+}
+#endif // ifdef EINK_PARTIAL_BRIGHTEN_LIMIT_PX
+
+bool EInkDisplay::newImageMatchesOld()
+{
+ uint32_t newImageHash = 0;
+
+ // Generate hash: sum all bytes in the image buffer
+ for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) {
+ newImageHash += buffer[b];
+ }
+
+ // Compare hashes
+ bool hashMatches = (newImageHash == prevImageHash);
+
+ // Update the cached hash
+ prevImageHash = newImageHash;
+
+ // Return the comparison result
+ return hashMatches;
+}
+
+// Change between partial and full refresh config, or skip update, balancing urgency and display health.
+bool EInkDisplay::determineRefreshMode()
+{
+ uint32_t now = millis();
+ uint32_t sinceLast = now - lastUpdateMsec;
+
+ // If rate-limiting dropped a high-priority update:
+ // promote this update, so it runs ASAP
+ if (missedHighPriorityUpdate) {
+ isHighPriority = true;
+ missedHighPriorityUpdate = false;
+ }
+
+ // Abort: if too soon for a new frame (unless demanding full)
+ if (!demandingFull && isHighPriority && partialRefreshCount > 0 && sinceLast < highPriorityLimitMsec) {
+ LOG_DEBUG("Dynamic Partial: update skipped. Exceeded EINK_HIGHPRIORITY_LIMIT_SECONDS\n");
+ missedHighPriorityUpdate = true;
+ return false;
+ }
+ if (!demandingFull && !isHighPriority && !demandingFull && sinceLast < lowPriorityLimitMsec) {
+ return false;
+ }
+
+ // If demanded full refresh: give it to them
+ if (demandingFull)
+ needsFull = true;
+
+ // Check if old image (partial) should be redrawn (as full), for image quality
+ if (partialRefreshCount > 0 && !isHighPriority)
+ needsFull = true;
+
+ // If too many partials, require a full-refresh (display health)
+ if (partialRefreshCount >= partialRefreshLimit)
+ needsFull = true;
+
+#ifdef EINK_PARTIAL_ERASURE_LIMIT
+ // Some displays struggle with erasing black pixels to white, during partial refresh
+ if (tooManyErasures())
+ needsFull = true;
+#endif
+
+ // If image matches
+ // (Block must run, even if full already selected, to store hash for next time)
+ if (newImageMatchesOld()) {
+ // If low priority: limit rate
+ // otherwise, every loop() will run the hash method
+ if (!isHighPriority)
+ lastUpdateMsec = now;
+
+ // If update is *not* for display health or image quality, skip it
+ if (!needsFull)
+ return false;
+ }
+
+ // Conditions assessed - not skipping - load the appropriate config
+
+ // If options require a full refresh
+ if (!isHighPriority || needsFull) {
+ if (partialRefreshCount > 0)
+ configForFullRefresh();
+
+ LOG_DEBUG("Dynamic Partial: conditions met for full-refresh\n");
+ partialRefreshCount = 0;
+ needsFull = false;
+ demandingFull = false;
+ erasedSinceFull = 0; // Reset the count for EINK_PARTIAL_ERASURE_LIMIT - tracks ghosting buildup
+ }
+
+ // If options allow a partial refresh
+ else {
+ if (partialRefreshCount == 0)
+ configForPartialRefresh();
+
+ LOG_DEBUG("Dynamic Partial: conditions met for partial-refresh\n");
+ partialRefreshCount++;
+ }
+
+ lastUpdateMsec = now; // Mark time for rate limiting
+ return true; // Instruct calling method to continue with update
+}
+
+#endif // End USE_EINK_DYNAMIC_PARTIAL
+
#endif
diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h
index 2529b1f0e..aeaddee2d 100644
--- a/src/graphics/EInkDisplay2.h
+++ b/src/graphics/EInkDisplay2.h
@@ -2,6 +2,11 @@
#include
+#if defined(HELTEC_WIRELESS_PAPER_V1_0)
+// Re-enable SPI after deep sleep: rtc_gpio_hold_dis()
+#include "driver/rtc_io.h"
+#endif
+
/**
* An adapter class that allows using the GxEPD2 library as if it was an OLEDDisplay implementation.
*
@@ -49,4 +54,81 @@ class EInkDisplay : public OLEDDisplay
// Connect to the display
virtual bool connect() override;
+
+#if defined(USE_EINK_DYNAMIC_PARTIAL)
+ // Full, partial, or skip: balance urgency with display health
+
+ // Use partial refresh if EITHER:
+ // * highPriority() was set
+ // * a highPriority() update was previously skipped, for rate-limiting - (EINK_HIGHPRIORITY_LIMIT_SECONDS)
+
+ // Use full refresh if EITHER:
+ // * lowPriority() was set
+ // * demandFullRefresh() was called - (single shot)
+ // * too many partial updates in a row: protect display - (EINK_PARTIAL_REPEAT_LIMIT)
+ // * no recent updates, and last update was partial: redraw for image quality (EINK_LOWPRIORITY_LIMIT_SECONDS)
+ // * (optional) too many "erasures" since full-refresh (black pixels cleared to white)
+
+ // Rate limit if:
+ // * lowPriority() - (EINK_LOWPRIORITY_LIMIT_SECONDS)
+ // * highPriority(), if multiple partials have run back-to-back - (EINK_HIGHPRIORITY_LIMIT_SECONDS)
+
+ // Skip update entirely if ALL criteria met:
+ // * new image matches old image
+ // * lowPriority()
+ // * no call to demandFullRefresh()
+ // * not redrawing for image quality
+ // * not refreshing for display health
+
+ // ------------------------------------
+
+ // To implement for your E-Ink display:
+ // * edit configForPartialRefresh()
+ // * edit configForFullRefresh()
+ // * add macros to variant.h, and adjust to taste:
+
+ /*
+ #define USE_EINK_DYNAMIC_PARTIAL
+ #define EINK_LOWPRIORITY_LIMIT_SECONDS 30
+ #define EINK_HIGHPRIORITY_LIMIT_SECONDS 1
+ #define EINK_PARTIAL_REPEAT_LIMIT 5
+ #define EINK_PARTIAL_ERASURE_LIMIT 300 // optional
+ */
+
+ public:
+ void highPriority(); // Suggest partial refresh
+ void lowPriority(); // Suggest full refresh
+ void demandFullRefresh(); // For next update: explicitly request full refresh
+
+ protected:
+ void configForPartialRefresh(); // Display specific code to select partial refresh mode
+ void configForFullRefresh(); // Display specific code to return to full refresh mode
+ bool newImageMatchesOld(); // Is the new update actually different to the last image?
+ bool determineRefreshMode(); // Called immediately before data written to display - choose refresh mode, or abort update
+#ifdef EINK_PARTIAL_ERASURE_LIMIT
+ int32_t countBlackPixels(); // Calculate the number of black pixels in the new image
+ bool tooManyErasures(); // Has too much "ghosting" (black pixels erased to white) accumulated since last full-refresh?
+#endif
+
+ bool isHighPriority = true; // Does the method calling update believe that this is urgent?
+ bool needsFull = false; // Is a full refresh forced? (display health)
+ bool demandingFull = false; // Was full refresh specifically requested? (splash screens, etc)
+ bool missedHighPriorityUpdate = false; // Was a high priority update skipped for rate-limiting?
+ uint16_t partialRefreshCount = 0; // How many partials have occurred since last full refresh?
+ uint32_t lastUpdateMsec = 0; // When did the last update occur?
+ uint32_t prevImageHash = 0; // Used to check if update will change screen image (skippable or not)
+ int32_t prevBlackCount = 0; // How many black pixels were in the previous image
+ uint32_t erasedSinceFull = 0; // How many black pixels have been set back to white since last full-refresh? (roughly)
+
+ // Set in variant.h
+ const uint32_t lowPriorityLimitMsec = (uint32_t)1000 * EINK_LOWPRIORITY_LIMIT_SECONDS; // Max rate for partial refreshes
+ const uint32_t highPriorityLimitMsec = (uint32_t)1000 * EINK_HIGHPRIORITY_LIMIT_SECONDS; // Max rate for full refreshes
+ const uint32_t partialRefreshLimit = EINK_PARTIAL_REPEAT_LIMIT; // Max consecutive partials, before full is triggered
+
+#else // !USE_EINK_DYNAMIC_PARTIAL
+ // Tolerate calls to these methods anywhere, just to be safe
+ void highPriority() {}
+ void lowPriority() {}
+ void demandFullRefresh() {}
+#endif
};
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index b626e393a..c0e55ea83 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -43,12 +43,19 @@ along with this program. If not, see .
#include "sleep.h"
#include "target_specific.h"
+#if HAS_WIFI && !defined(ARCH_PORTDUINO)
+#include "mesh/wifi/WiFiAPClient.h"
+#endif
+
#ifdef ARCH_ESP32
#include "esp_task_wdt.h"
-#include "mesh/http/WiFiAPClient.h"
#include "modules/esp32/StoreForwardModule.h"
#endif
+#if ARCH_PORTDUINO
+#include "platform/portduino/PortduinoGlue.h"
+#endif
+
#ifdef OLED_RU
#include "fonts/OLEDDisplayFontsRU.h"
#endif
@@ -143,7 +150,7 @@ static void drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLEDDispl
// draw centered icon left to right and centered above the one line of app text
display->drawXbm(x + (SCREEN_WIDTH - icon_width) / 2, y + (SCREEN_HEIGHT - FONT_HEIGHT_MEDIUM - icon_height) / 2 + 2,
- icon_width, icon_height, (const uint8_t *)icon_bits);
+ icon_width, icon_height, icon_bits);
display->setFont(FONT_MEDIUM);
display->setTextAlignment(TEXT_ALIGN_LEFT);
@@ -551,15 +558,20 @@ static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus
}
}
-// Draw status when gps is disabled by PMU
+// Draw status when GPS is disabled or not present
static void drawGPSpowerstat(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
{
- String displayLine = "GPS disabled";
- int16_t xPos = display->getStringWidth(displayLine);
-
- if (!config.position.gps_enabled) {
- display->drawString(x + xPos, y, displayLine);
+ String displayLine;
+ int pos;
+ if (y < FONT_HEIGHT_SMALL) { // Line 1: use short string
+ displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off";
+ pos = SCREEN_WIDTH - display->getStringWidth(displayLine);
+ } else {
+ displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "GPS not present"
+ : "GPS is disabled";
+ pos = (SCREEN_WIDTH - display->getStringWidth(displayLine)) / 2;
}
+ display->drawString(x + pos, y, displayLine);
}
static void drawGPSAltitude(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
@@ -587,7 +599,7 @@ static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const
String displayLine = "";
if (!gps->getIsConnected() && !config.position.fixed_position) {
- displayLine = "No GPS Module";
+ displayLine = "No GPS present";
display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
} else if (!gps->getHasLock() && !config.position.fixed_position) {
displayLine = "No GPS Lock";
@@ -906,10 +918,40 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
}
Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_OledType screenType, OLEDDISPLAY_GEOMETRY geometry)
- : concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32),
- dispdev(address.address, -1, -1, geometry, (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE),
- ui(&dispdev)
+ : concurrency::OSThread("Screen"), address_found(address), model(screenType), geometry(geometry), cmdQueue(32)
{
+#if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64)
+ dispdev = new SH1106Wire(address.address, -1, -1, geometry,
+ (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
+#elif defined(USE_SSD1306)
+ dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
+ (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
+#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014)
+ dispdev = new TFTDisplay(address.address, -1, -1, geometry,
+ (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
+#elif defined(USE_EINK)
+ dispdev = new EInkDisplay(address.address, -1, -1, geometry,
+ (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
+#elif defined(USE_ST7567)
+ dispdev = new ST7567Wire(address.address, -1, -1, geometry,
+ (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
+#elif ARCH_PORTDUINO
+ if (settingsMap[displayPanel] != no_screen) {
+ LOG_DEBUG("Making TFTDisplay!\n");
+ dispdev = new TFTDisplay(address.address, -1, -1, geometry,
+ (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
+ } else {
+ dispdev = new AutoOLEDWire(address.address, -1, -1, geometry,
+ (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
+ isAUTOOled = true;
+ }
+#else
+ dispdev = new AutoOLEDWire(address.address, -1, -1, geometry,
+ (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
+ isAUTOOled = true;
+#endif
+
+ ui = new OLEDDisplayUi(dispdev);
cmdQueue.setReader(this);
}
@@ -922,8 +964,8 @@ void Screen::doDeepSleep()
#ifdef USE_EINK
static FrameCallback sleepFrames[] = {drawSleepScreen};
static const int sleepFrameCount = sizeof(sleepFrames) / sizeof(sleepFrames[0]);
- ui.setFrames(sleepFrames, sleepFrameCount);
- ui.update();
+ ui->setFrames(sleepFrames, sleepFrameCount);
+ ui->update();
#endif
setOn(false);
}
@@ -939,14 +981,16 @@ void Screen::handleSetOn(bool on)
#ifdef T_WATCH_S3
PMU->enablePowerOutput(XPOWERS_ALDO2);
#endif
- dispdev.displayOn();
- dispdev.displayOn();
+#if !ARCH_PORTDUINO
+ dispdev->displayOn();
+#endif
+ dispdev->displayOn();
enabled = true;
setInterval(0); // Draw ASAP
runASAP = true;
} else {
LOG_INFO("Turning off screen\n");
- dispdev.displayOff();
+ dispdev->displayOff();
#ifdef T_WATCH_S3
PMU->disablePowerOutput(XPOWERS_ALDO2);
#endif
@@ -963,32 +1007,33 @@ void Screen::setup()
useDisplay = true;
#ifdef AutoOLEDWire_h
- dispdev.setDetected(model);
+ if (isAUTOOled)
+ static_cast(dispdev)->setDetected(model);
#endif
#ifdef USE_SH1107_128_64
- dispdev.setSubtype(7);
+ static_cast(dispdev)->setSubtype(7);
#endif
// Initialising the UI will init the display too.
- ui.init();
+ ui->init();
- displayWidth = dispdev.width();
- displayHeight = dispdev.height();
+ displayWidth = dispdev->width();
+ displayHeight = dispdev->height();
- ui.setTimePerTransition(0);
+ ui->setTimePerTransition(0);
- ui.setIndicatorPosition(BOTTOM);
+ ui->setIndicatorPosition(BOTTOM);
// Defines where the first frame is located in the bar.
- ui.setIndicatorDirection(LEFT_RIGHT);
- ui.setFrameAnimation(SLIDE_LEFT);
+ ui->setIndicatorDirection(LEFT_RIGHT);
+ ui->setFrameAnimation(SLIDE_LEFT);
// Don't show the page swipe dots while in boot screen.
- ui.disableAllIndicators();
+ ui->disableAllIndicators();
// Store a pointer to Screen so we can get to it from static functions.
- ui.getUiState()->userData = this;
+ ui->getUiState()->userData = this;
// Set the utf8 conversion function
- dispdev.setFontTableLookupFunction(customFontTableLookup);
+ dispdev->setFontTableLookupFunction(customFontTableLookup);
if (strlen(oemStore.oem_text) > 0)
logo_timeout *= 2;
@@ -996,23 +1041,27 @@ void Screen::setup()
// Add frames.
static FrameCallback bootFrames[] = {drawBootScreen};
static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
- ui.setFrames(bootFrames, bootFrameCount);
+ ui->setFrames(bootFrames, bootFrameCount);
// No overlays.
- ui.setOverlays(nullptr, 0);
+ ui->setOverlays(nullptr, 0);
// Require presses to switch between frames.
- ui.disableAutoTransition();
+ ui->disableAutoTransition();
// Set up a log buffer with 3 lines, 32 chars each.
- dispdev.setLogBuffer(3, 32);
+ dispdev->setLogBuffer(3, 32);
#ifdef SCREEN_MIRROR
- dispdev.mirrorScreen();
+ dispdev->mirrorScreen();
#else
// Standard behaviour is to FLIP the screen (needed on T-Beam). If this config item is set, unflip it, and thereby logically
// flip it. If you have a headache now, you're welcome.
if (!config.display.flip_screen) {
- dispdev.flipScreenVertically();
+#if defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS) || defined(RAK14014)
+ static_cast(dispdev)->flipScreenVertically();
+#else
+ dispdev->flipScreenVertically();
+#endif
}
#endif
@@ -1020,20 +1069,30 @@ void Screen::setup()
uint8_t dmac[6];
getMacAddr(dmac);
snprintf(ourId, sizeof(ourId), "%02x%02x", dmac[4], dmac[5]);
+#if ARCH_PORTDUINO
+ handleSetOn(false); // force clean init
+#endif
// Turn on the display.
handleSetOn(true);
// On some ssd1306 clones, the first draw command is discarded, so draw it
// twice initially. Skip this for EINK Displays to save a few seconds during boot
- ui.update();
+ ui->update();
#ifndef USE_EINK
- ui.update();
+ ui->update();
#endif
serialSinceMsec = millis();
-#if HAS_TOUCHSCREEN
- touchScreenImpl1 = new TouchScreenImpl1(dispdev.getWidth(), dispdev.getHeight(), dispdev.getTouch);
+#if ARCH_PORTDUINO
+ if (settingsMap[touchscreenModule]) {
+ touchScreenImpl1 =
+ new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch);
+ touchScreenImpl1->init();
+ }
+#elif HAS_TOUCHSCREEN
+ touchScreenImpl1 =
+ new TouchScreenImpl1(dispdev->getWidth(), dispdev->getHeight(), static_cast(dispdev)->getTouch);
touchScreenImpl1->init();
#endif
@@ -1054,7 +1113,7 @@ void Screen::forceDisplay()
{
// Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup.
#ifdef USE_EINK
- dispdev.forceDisplay();
+ static_cast(dispdev)->forceDisplay();
#endif
}
@@ -1085,10 +1144,10 @@ int32_t Screen::runOnce()
// Change frames.
static FrameCallback bootOEMFrames[] = {drawOEMBootScreen};
static const int bootOEMFrameCount = sizeof(bootOEMFrames) / sizeof(bootOEMFrames[0]);
- ui.setFrames(bootOEMFrames, bootOEMFrameCount);
- ui.update();
+ ui->setFrames(bootOEMFrames, bootOEMFrameCount);
+ ui->update();
#ifndef USE_EINK
- ui.update();
+ ui->update();
#endif
showingOEMBootScreen = false;
}
@@ -1161,16 +1220,16 @@ int32_t Screen::runOnce()
// this must be before the frameState == FIXED check, because we always
// want to draw at least one FIXED frame before doing forceDisplay
- ui.update();
+ ui->update();
// Switch to a low framerate (to save CPU) when we are not in transition
// but we should only call setTargetFPS when framestate changes, because
// otherwise that breaks animations.
- if (targetFramerate != IDLE_FRAMERATE && ui.getUiState()->frameState == FIXED) {
- // oldFrameState = ui.getUiState()->frameState;
+ if (targetFramerate != IDLE_FRAMERATE && ui->getUiState()->frameState == FIXED) {
+ // oldFrameState = ui->getUiState()->frameState;
targetFramerate = IDLE_FRAMERATE;
- ui.setTargetFPS(targetFramerate);
+ ui->setTargetFPS(targetFramerate);
forceDisplay();
}
@@ -1186,7 +1245,7 @@ int32_t Screen::runOnce()
}
// LOG_DEBUG("want fps %d, fixed=%d\n", targetFramerate,
- // ui.getUiState()->frameState); If we are scrolling we need to be called
+ // ui->getUiState()->frameState); If we are scrolling we need to be called
// soon, otherwise just 1 fps (to save CPU) We also ask to be called twice
// as fast as we really need so that any rounding errors still result with
// the correct framerate
@@ -1218,8 +1277,8 @@ void Screen::setSSLFrames()
if (address_found.address) {
// LOG_DEBUG("showing SSL frames\n");
static FrameCallback sslFrames[] = {drawSSLScreen};
- ui.setFrames(sslFrames, 1);
- ui.update();
+ ui->setFrames(sslFrames, 1);
+ ui->update();
}
}
@@ -1294,7 +1353,7 @@ void Screen::setFrames()
// call a method on debugInfoScreen object (for more details)
normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline;
-#ifdef ARCH_ESP32
+#if HAS_WIFI && !defined(ARCH_PORTDUINO)
if (isWifiAvailable()) {
// call a method on debugInfoScreen object (for more details)
normalFrames[numframes++] = &Screen::drawDebugInfoWiFiTrampoline;
@@ -1303,8 +1362,8 @@ void Screen::setFrames()
LOG_DEBUG("Finished building frames. numframes: %d\n", numframes);
- ui.setFrames(normalFrames, numframes);
- ui.enableAllIndicators();
+ ui->setFrames(normalFrames, numframes);
+ ui->enableAllIndicators();
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
// just changed)
@@ -1324,8 +1383,8 @@ void Screen::handleStartBluetoothPinScreen(uint32_t pin)
void Screen::setFrameImmediateDraw(FrameCallback *drawFrames)
{
- ui.disableAllIndicators();
- ui.setFrames(drawFrames, 1);
+ ui->disableAllIndicators();
+ ui->setFrames(drawFrames, 1);
setFastFramerate();
}
@@ -1367,17 +1426,17 @@ void Screen::blink()
{
setFastFramerate();
uint8_t count = 10;
- dispdev.setBrightness(254);
+ dispdev->setBrightness(254);
while (count > 0) {
- dispdev.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
- dispdev.display();
+ dispdev->fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+ dispdev->display();
delay(50);
- dispdev.clear();
- dispdev.display();
+ dispdev->clear();
+ dispdev->display();
delay(50);
count = count - 1;
}
- dispdev.setBrightness(brightness);
+ dispdev->setBrightness(brightness);
}
std::string Screen::drawTimeDelta(uint32_t days, uint32_t hours, uint32_t minutes, uint32_t seconds)
@@ -1405,15 +1464,15 @@ void Screen::handlePrint(const char *text)
if (!useDisplay || !showingNormalScreen)
return;
- dispdev.print(text);
+ dispdev->print(text);
}
void Screen::handleOnPress()
{
// If screen was off, just wake it, otherwise advance to next frame
// If we are in a transition, the press must have bounced, drop it.
- if (ui.getUiState()->frameState == FIXED) {
- ui.nextFrame();
+ if (ui->getUiState()->frameState == FIXED) {
+ ui->nextFrame();
lastScreenTransition = millis();
setFastFramerate();
}
@@ -1423,8 +1482,8 @@ void Screen::handleShowPrevFrame()
{
// If screen was off, just wake it, otherwise go back to previous frame
// If we are in a transition, the press must have bounced, drop it.
- if (ui.getUiState()->frameState == FIXED) {
- ui.previousFrame();
+ if (ui->getUiState()->frameState == FIXED) {
+ ui->previousFrame();
lastScreenTransition = millis();
setFastFramerate();
}
@@ -1434,8 +1493,8 @@ void Screen::handleShowNextFrame()
{
// If screen was off, just wake it, otherwise advance to next frame
// If we are in a transition, the press must have bounced, drop it.
- if (ui.getUiState()->frameState == FIXED) {
- ui.nextFrame();
+ if (ui->getUiState()->frameState == FIXED) {
+ ui->nextFrame();
lastScreenTransition = millis();
setFastFramerate();
}
@@ -1450,7 +1509,7 @@ void Screen::setFastFramerate()
// We are about to start a transition so speed up fps
targetFramerate = SCREEN_TRANSITION_FRAMERATE;
- ui.setTargetFPS(targetFramerate);
+ ui->setTargetFPS(targetFramerate);
setInterval(0); // redraw ASAP
runASAP = true;
}
@@ -1495,7 +1554,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus);
}
// Display GPS status
- if (!config.position.gps_enabled) {
+ if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
drawGPSpowerstat(display, x, y + 2, gpsStatus);
} else {
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT) {
@@ -1537,7 +1596,8 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
}
#endif
} else {
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
+ // TODO: Raspberry Pi supports more than just the one screen size
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgInfoL1);
@@ -1564,7 +1624,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
// Jm
void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
-#if HAS_WIFI
+#if HAS_WIFI && !defined(ARCH_PORTDUINO)
const char *wifiName = config.network.wifi_ssid;
display->setFont(FONT_SMALL);
@@ -1618,12 +1678,19 @@ void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, i
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Connection Failed");
} else if (WiFi.status() == WL_IDLE_STATUS) {
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Idle ... Reconnecting");
- } else {
+ }
+#ifdef ARCH_ESP32
+ else {
// Codes:
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/wifi.html#wi-fi-reason-code
display->drawString(x, y + FONT_HEIGHT_SMALL * 1,
WiFi.disconnectReasonName(static_cast(getWifiDisconnectReason())));
}
+#else
+ else {
+ display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Unkown status: " + String(WiFi.status()));
+ }
+#endif
display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "SSID: " + String(wifiName));
@@ -1715,7 +1782,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
char chUtil[13];
snprintf(chUtil, sizeof(chUtil), "ChUtil %2.0f%%", airTime->channelUtilizationPercent());
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(chUtil), y + FONT_HEIGHT_SMALL * 1, chUtil);
- if (config.position.gps_enabled) {
+ if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
// Line 3
if (config.display.gps_format !=
meshtastic_Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude
@@ -1724,10 +1791,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
// Line 4
drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus);
} else {
- drawGPSpowerstat(display, x - (SCREEN_WIDTH / 4), y + FONT_HEIGHT_SMALL * 2, gpsStatus);
-#ifdef GPS_POWER_TOGGLE
- display->drawString(x + 30, (y + FONT_HEIGHT_SMALL * 3), " by button");
-#endif
+ drawGPSpowerstat(display, x, y + FONT_HEIGHT_SMALL * 2, gpsStatus);
}
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
#ifdef SHOW_REDRAWS
@@ -1770,7 +1834,7 @@ int Screen::handleUIFrameEvent(const UIFrameEvent *event)
setFastFramerate();
// TODO: We might also want switch to corresponding frame,
// but we don't know the exact frame number.
- // ui.switchToFrame(0);
+ // ui->switchToFrame(0);
}
}
diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h
index d6fb7b5d3..baee4b140 100644
--- a/src/graphics/Screen.h
+++ b/src/graphics/Screen.h
@@ -324,6 +324,8 @@ class Screen : public concurrency::OSThread
// Called periodically from the main loop.
int32_t runOnce() final;
+ bool isAUTOOled = false;
+
private:
struct ScreenCmd {
Cmd cmd;
@@ -385,22 +387,10 @@ class Screen : public concurrency::OSThread
DebugInfo debugInfo;
/// Display device
+ OLEDDisplay *dispdev;
-#if defined(USE_SH1106) || defined(USE_SH1107) || defined(USE_SH1107_128_64)
- SH1106Wire dispdev;
-#elif defined(USE_SSD1306)
- SSD1306Wire dispdev;
-#elif defined(ST7735_CS) || defined(ILI9341_DRIVER) || defined(ST7789_CS)
- TFTDisplay dispdev;
-#elif defined(USE_EINK)
- EInkDisplay dispdev;
-#elif defined(USE_ST7567)
- ST7567Wire dispdev;
-#else
- AutoOLEDWire dispdev;
-#endif
/// UI helper for rendering to frames and switching between them
- OLEDDisplayUi ui;
+ OLEDDisplayUi *ui;
};
} // namespace graphics
diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp
index 5eec2b200..9475e0296 100644
--- a/src/graphics/TFTDisplay.cpp
+++ b/src/graphics/TFTDisplay.cpp
@@ -1,5 +1,8 @@
#include "configuration.h"
#include "main.h"
+#if ARCH_PORTDUINO
+#include "platform/portduino/PortduinoGlue.h"
+#endif
#ifndef TFT_BACKLIGHT_ON
#define TFT_BACKLIGHT_ON HIGH
@@ -16,6 +19,10 @@
#define TFT_BL ST7735_BACKLIGHT_EN
#endif
+#ifndef TFT_INVERT
+#define TFT_INVERT true
+#endif
+
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_ST7735S _panel_instance;
@@ -65,7 +72,7 @@ class LGFX : public lgfx::LGFX_Device
cfg.dummy_read_pixel = 8; // Number of bits for dummy read before pixel readout
cfg.dummy_read_bits = 1; // Number of bits for dummy read before non-pixel data read
cfg.readable = true; // Set to true if data can be read
- cfg.invert = true; // Set to true if the light/darkness of the panel is reversed
+ cfg.invert = TFT_INVERT; // Set to true if the light/darkness of the panel is reversed
cfg.rgb_order = false; // Set to true if the panel's red and blue are swapped
cfg.dlen_16bit =
false; // Set to true for panels that transmit data length in 16-bit units with 16-bit parallel or SPI
@@ -83,11 +90,9 @@ class LGFX : public lgfx::LGFX_Device
auto cfg = _light_instance.config(); // Gets a structure for backlight settings.
#ifdef ST7735_BL_V03
- if (heltec_version == 3) {
- cfg.pin_bl = ST7735_BL_V03;
- } else {
- cfg.pin_bl = ST7735_BL_V05;
- }
+ cfg.pin_bl = ST7735_BL_V03;
+#elif defined(ST7735_BL_V05)
+ cfg.pin_bl = ST7735_BL_V05;
#else
cfg.pin_bl = ST7735_BL; // Pin number to which the backlight is connected
#endif
@@ -103,7 +108,11 @@ class LGFX : public lgfx::LGFX_Device
}
};
-static LGFX tft;
+static LGFX *tft = nullptr;
+
+#elif defined(RAK14014)
+#include
+TFT_eSPI *tft = nullptr;
#elif defined(ST7789_CS)
#include // Graphics and font library for ST7735 driver chip
@@ -229,7 +238,7 @@ class LGFX : public lgfx::LGFX_Device
}
};
-static LGFX tft;
+static LGFX *tft = nullptr;
#elif defined(ILI9341_DRIVER)
@@ -318,23 +327,99 @@ class LGFX : public lgfx::LGFX_Device
}
};
-static LGFX tft;
+static LGFX *tft = nullptr;
#elif defined(ST7735_CS)
#include // Graphics and font library for ILI9341 driver chip
-static TFT_eSPI tft = TFT_eSPI(); // Invoke library, pins defined in User_Setup.h
+static TFT_eSPI *tft = nullptr; // Invoke library, pins defined in User_Setup.h
+#elif ARCH_PORTDUINO
+#include // Graphics and font library for ST7735 driver chip
+class LGFX : public lgfx::LGFX_Device
+{
+ lgfx::Panel_LCD *_panel_instance;
+ lgfx::Bus_SPI _bus_instance;
+
+ lgfx::ITouch *_touch_instance;
+
+ public:
+ LGFX(void)
+ {
+ if (settingsMap[displayPanel] == st7789)
+ _panel_instance = new lgfx::Panel_ST7789;
+ else if (settingsMap[displayPanel] == st7735)
+ _panel_instance = new lgfx::Panel_ST7735;
+ else if (settingsMap[displayPanel] == st7735s)
+ _panel_instance = new lgfx::Panel_ST7735S;
+ else if (settingsMap[displayPanel] == ili9341)
+ _panel_instance = new lgfx::Panel_ILI9341;
+ auto buscfg = _bus_instance.config();
+ buscfg.spi_mode = 0;
+
+ buscfg.pin_dc = settingsMap[displayDC]; // Set SPI DC pin number (-1 = disable)
+
+ _bus_instance.config(buscfg); // applies the set value to the bus.
+ _panel_instance->setBus(&_bus_instance); // set the bus on the panel.
+
+ auto cfg = _panel_instance->config(); // Gets a structure for display panel settings.
+ LOG_DEBUG("Height: %d, Width: %d \n", settingsMap[displayHeight], settingsMap[displayWidth]);
+ cfg.pin_cs = settingsMap[displayCS]; // Pin number where CS is connected (-1 = disable)
+ cfg.pin_rst = settingsMap[displayReset];
+ cfg.panel_width = settingsMap[displayWidth]; // actual displayable width
+ cfg.panel_height = settingsMap[displayHeight]; // actual displayable height
+ cfg.offset_x = settingsMap[displayOffsetX]; // Panel offset amount in X direction
+ cfg.offset_y = settingsMap[displayOffsetY]; // Panel offset amount in Y direction
+ cfg.offset_rotation = 0; // Rotation direction value offset 0~7 (4~7 is mirrored)
+ cfg.invert = settingsMap[displayInvert]; // Set to true if the light/darkness of the panel is reversed
+
+ _panel_instance->config(cfg);
+
+ // Configure settings for touch control.
+ if (settingsMap[touchscreenModule]) {
+ if (settingsMap[touchscreenModule] == xpt2046) {
+ _touch_instance = new lgfx::Touch_XPT2046;
+ } else if (settingsMap[touchscreenModule] == stmpe610) {
+ _touch_instance = new lgfx::Touch_STMPE610;
+ }
+ auto touch_cfg = _touch_instance->config();
+
+ touch_cfg.pin_cs = settingsMap[touchscreenCS];
+ touch_cfg.x_min = 0;
+ touch_cfg.x_max = settingsMap[displayHeight] - 1;
+ touch_cfg.y_min = 0;
+ touch_cfg.y_max = settingsMap[displayWidth] - 1;
+ touch_cfg.pin_int = settingsMap[touchscreenIRQ];
+ touch_cfg.bus_shared = true;
+ touch_cfg.offset_rotation = 1;
+
+ _touch_instance->config(touch_cfg);
+ _panel_instance->setTouch(_touch_instance);
+ }
+
+ setPanel(_panel_instance); // Sets the panel to use.
+ }
+};
+
+static LGFX *tft = nullptr;
#endif
-#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER)
+#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || ARCH_PORTDUINO
#include "SPILock.h"
#include "TFTDisplay.h"
#include
TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
{
-#ifdef SCREEN_ROTATE
+ LOG_DEBUG("TFTDisplay!\n");
+#if ARCH_PORTDUINO
+ if (settingsMap[displayRotate]) {
+ setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayHeight], settingsMap[configNames::displayWidth]);
+ } else {
+ setGeometry(GEOMETRY_RAWMODE, settingsMap[configNames::displayWidth], settingsMap[configNames::displayHeight]);
+ }
+
+#elif defined(SCREEN_ROTATE)
setGeometry(GEOMETRY_RAWMODE, TFT_HEIGHT, TFT_WIDTH);
#else
setGeometry(GEOMETRY_RAWMODE, TFT_WIDTH, TFT_HEIGHT);
@@ -342,19 +427,26 @@ TFTDisplay::TFTDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY g
}
// Write the buffer to the display memory
-void TFTDisplay::display(void)
+void TFTDisplay::display(bool fromBlank)
{
+ if (fromBlank)
+ tft->fillScreen(TFT_BLACK);
+ // tft->clear();
concurrency::LockGuard g(spiLock);
uint16_t x, y;
for (y = 0; y < displayHeight; y++) {
for (x = 0; x < displayWidth; x++) {
- // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
auto isset = buffer[x + (y / 8) * displayWidth] & (1 << (y & 7));
- auto dblbuf_isset = buffer_back[x + (y / 8) * displayWidth] & (1 << (y & 7));
- if (isset != dblbuf_isset) {
- tft.drawPixel(x, y, isset ? TFT_MESH : TFT_BLACK);
+ if (!fromBlank) {
+ // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficent
+ auto dblbuf_isset = buffer_back[x + (y / 8) * displayWidth] & (1 << (y & 7));
+ if (isset != dblbuf_isset) {
+ tft->drawPixel(x, y, isset ? TFT_MESH : TFT_BLACK);
+ }
+ } else if (isset) {
+ tft->drawPixel(x, y, TFT_MESH);
}
}
}
@@ -373,54 +465,57 @@ void TFTDisplay::sendCommand(uint8_t com)
// handle display on/off directly
switch (com) {
case DISPLAYON: {
-#if defined(ST7735_BACKLIGHT_EN_V03) && defined(TFT_BACKLIGHT_ON)
- if (heltec_version == 3) {
- digitalWrite(ST7735_BACKLIGHT_EN_V03, TFT_BACKLIGHT_ON);
- } else {
- digitalWrite(ST7735_BACKLIGHT_EN_V05, TFT_BACKLIGHT_ON);
- }
+#if ARCH_PORTDUINO
+ display(true);
+ if (settingsMap[displayBacklight] > 0)
+ digitalWrite(settingsMap[displayBacklight], TFT_BACKLIGHT_ON);
+#elif defined(ST7735_BL_V03)
+ digitalWrite(ST7735_BL_V03, TFT_BACKLIGHT_ON);
+#elif defined(ST7735_BL_V05)
+ pinMode(ST7735_BL_V05, OUTPUT);
+ digitalWrite(ST7735_BL_V05, TFT_BACKLIGHT_ON);
#endif
#if defined(TFT_BL) && defined(TFT_BACKLIGHT_ON)
digitalWrite(TFT_BL, TFT_BACKLIGHT_ON);
#endif
+
#ifdef VTFT_CTRL_V03
- if (heltec_version == 3) {
- digitalWrite(VTFT_CTRL_V03, LOW);
- } else {
- digitalWrite(VTFT_CTRL_V05, LOW);
- }
+ digitalWrite(VTFT_CTRL_V03, LOW);
#endif
+
#ifdef VTFT_CTRL
digitalWrite(VTFT_CTRL, LOW);
#endif
-#ifndef M5STACK
- tft.setBrightness(128);
+
+#ifdef RAK14014
+#elif !defined(M5STACK)
+ tft->setBrightness(172);
#endif
break;
}
case DISPLAYOFF: {
-#if defined(ST7735_BACKLIGHT_EN_V03) && defined(TFT_BACKLIGHT_ON)
- if (heltec_version == 3) {
- digitalWrite(ST7735_BACKLIGHT_EN_V03, !TFT_BACKLIGHT_ON);
- } else {
- digitalWrite(ST7735_BACKLIGHT_EN_V05, !TFT_BACKLIGHT_ON);
- }
+#if ARCH_PORTDUINO
+ tft->clear();
+ if (settingsMap[displayBacklight] > 0)
+ digitalWrite(settingsMap[displayBacklight], !TFT_BACKLIGHT_ON);
+#elif defined(ST7735_BL_V03)
+ digitalWrite(ST7735_BL_V03, !TFT_BACKLIGHT_ON);
+#elif defined(ST7735_BL_V05)
+ pinMode(ST7735_BL_V05, OUTPUT);
+ digitalWrite(ST7735_BL_V05, !TFT_BACKLIGHT_ON);
#endif
#if defined(TFT_BL) && defined(TFT_BACKLIGHT_ON)
digitalWrite(TFT_BL, !TFT_BACKLIGHT_ON);
#endif
#ifdef VTFT_CTRL_V03
- if (heltec_version == 3) {
- digitalWrite(VTFT_CTRL_V03, HIGH);
- } else {
- digitalWrite(VTFT_CTRL_V05, HIGH);
- }
+ digitalWrite(VTFT_CTRL_V03, HIGH);
#endif
#ifdef VTFT_CTRL
digitalWrite(VTFT_CTRL, HIGH);
#endif
-#ifndef M5STACK
- tft.setBrightness(0);
+#ifdef RAK14014
+#elif !defined(M5STACK)
+ tft->setBrightness(0);
#endif
break;
}
@@ -435,14 +530,15 @@ void TFTDisplay::flipScreenVertically()
{
#if defined(T_WATCH_S3)
LOG_DEBUG("Flip TFT vertically\n"); // T-Watch S3 right-handed orientation
- tft.setRotation(0);
+ tft->setRotation(0);
#endif
}
bool TFTDisplay::hasTouch(void)
{
-#ifndef M5STACK
- return tft.touch() != nullptr;
+#ifdef RAK14014
+#elif !defined(M5STACK)
+ return tft->touch() != nullptr;
#else
return false;
#endif
@@ -450,8 +546,9 @@ bool TFTDisplay::hasTouch(void)
bool TFTDisplay::getTouch(int16_t *x, int16_t *y)
{
-#ifndef M5STACK
- return tft.getTouch(x, y);
+#ifdef RAK14014
+#elif !defined(M5STACK)
+ return tft->getTouch(x, y);
#else
return false;
#endif
@@ -467,33 +564,44 @@ bool TFTDisplay::connect()
{
concurrency::LockGuard g(spiLock);
LOG_INFO("Doing TFT init\n");
+#ifdef RAK14014
+ tft = new TFT_eSPI;
+#else
+ tft = new LGFX;
+#endif
#ifdef TFT_BL
- digitalWrite(TFT_BL, TFT_BACKLIGHT_ON);
pinMode(TFT_BL, OUTPUT);
+ digitalWrite(TFT_BL, TFT_BACKLIGHT_ON);
+ // pinMode(PIN_3V3_EN, OUTPUT);
+ // digitalWrite(PIN_3V3_EN, HIGH);
+ LOG_INFO("Power to TFT Backlight\n");
#endif
-#ifdef ST7735_BACKLIGHT_EN_V03
- if (heltec_version == 3) {
- digitalWrite(ST7735_BACKLIGHT_EN_V03, TFT_BACKLIGHT_ON);
- pinMode(ST7735_BACKLIGHT_EN_V03, OUTPUT);
- } else {
- digitalWrite(ST7735_BACKLIGHT_EN_V05, TFT_BACKLIGHT_ON);
- pinMode(ST7735_BACKLIGHT_EN_V05, OUTPUT);
- }
+#ifdef ST7735_BL_V03
+ digitalWrite(ST7735_BL_V03, TFT_BACKLIGHT_ON);
+#elif defined(ST7735_BL_V05)
+ pinMode(ST7735_BL_V05, OUTPUT);
+ digitalWrite(ST7735_BL_V05, TFT_BACKLIGHT_ON);
#endif
- tft.init();
+ tft->init();
+
#if defined(M5STACK)
- tft.setRotation(0);
-#elif defined(T_DECK) || defined(PICOMPUTER_S3)
- tft.setRotation(1); // T-Deck has the TFT in landscape
+ tft->setRotation(0);
+#elif defined(RAK14014)
+ tft->setRotation(1);
+ tft->setSwapBytes(true);
+// tft->fillScreen(TFT_BLACK);
+#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)
- tft.setRotation(2); // T-Watch S3 left-handed orientation
+ tft->setRotation(2); // T-Watch S3 left-handed orientation
#else
- tft.setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
+ tft->setRotation(3); // Orient horizontal and wide underneath the silkscreen name label
#endif
- tft.fillScreen(TFT_BLACK);
+ tft->fillScreen(TFT_BLACK);
+
return true;
}
diff --git a/src/graphics/TFTDisplay.h b/src/graphics/TFTDisplay.h
index 8c9a9b62e..3d6ea6cc6 100644
--- a/src/graphics/TFTDisplay.h
+++ b/src/graphics/TFTDisplay.h
@@ -20,7 +20,8 @@ class TFTDisplay : public OLEDDisplay
TFTDisplay(uint8_t, int, int, OLEDDISPLAY_GEOMETRY, HW_I2C);
// Write the buffer to the display memory
- virtual void display(void) override;
+ virtual void display() override { display(false); };
+ virtual void display(bool fromBlank);
// Turn the display upside down
virtual void flipScreenVertically();
diff --git a/src/graphics/images.h b/src/graphics/images.h
index a1191076b..207fc3a86 100644
--- a/src/graphics/images.h
+++ b/src/graphics/images.h
@@ -14,7 +14,7 @@ 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};
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff};
const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f};
@@ -30,4 +30,4 @@ 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
-#include "img/icon.xbm"
+#include "img/icon.xbm"
\ No newline at end of file
diff --git a/src/graphics/img/icon.xbm b/src/graphics/img/icon.xbm
index 2b698e4c9..297f31ed6 100644
--- a/src/graphics/img/icon.xbm
+++ b/src/graphics/img/icon.xbm
@@ -1,6 +1,6 @@
#define icon_width 50
#define icon_height 28
-static char icon_bits[] = {
+static uint8_t icon_bits[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x03,
0x00, 0x00, 0x00, 0x80, 0x07, 0xC0, 0x07, 0x00, 0x00, 0x00, 0xC0, 0x1F,
0xC0, 0x0F, 0x00, 0x00, 0x00, 0xE0, 0x0F, 0xE0, 0x0F, 0x00, 0x00, 0x00,
@@ -17,4 +17,4 @@ static char icon_bits[] = {
0xFE, 0x00, 0x00, 0xFC, 0x01, 0x7E, 0x00, 0x7F, 0x00, 0x00, 0xF8, 0x01,
0x7E, 0x00, 0x3E, 0x00, 0x00, 0xF8, 0x01, 0x38, 0x00, 0x3C, 0x00, 0x00,
0x70, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x00, 0x00, };
+ 0x00, 0x00, 0x00, 0x00, };
\ No newline at end of file
diff --git a/src/input/LinuxInput.cpp b/src/input/LinuxInput.cpp
new file mode 100644
index 000000000..d2a94e94e
--- /dev/null
+++ b/src/input/LinuxInput.cpp
@@ -0,0 +1,183 @@
+#include "configuration.h"
+#if ARCH_PORTDUINO
+#include "LinuxInput.h"
+#include "platform/portduino/PortduinoGlue.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// Inspired by https://github.com/librerpi/rpi-tools/blob/master/keyboard-proxy/main.c which is GPL-v2
+
+LinuxInput::LinuxInput(const char *name) : concurrency::OSThread(name)
+{
+ this->_originName = name;
+}
+
+void LinuxInput::deInit()
+{
+ close(fd);
+}
+
+int32_t LinuxInput::runOnce()
+{
+
+ if (firstTime) {
+ if (settingsStrings[keyboardDevice] == "")
+ return disable();
+ fd = open(settingsStrings[keyboardDevice].c_str(), O_RDWR);
+ if (fd < 0)
+ return disable();
+ ret = ioctl(fd, EVIOCGRAB, (void *)1);
+ if (ret != 0)
+ return disable();
+
+ epollfd = epoll_create1(0);
+ assert(epollfd >= 0);
+
+ ev.events = EPOLLIN;
+ ev.data.fd = fd;
+ if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev)) {
+ perror("unable to epoll add");
+ return disable();
+ }
+ // This is the first time the OSThread library has called this function, so do port setup
+ firstTime = 0;
+ }
+
+ int nfds = epoll_wait(epollfd, events, MAX_EVENTS, 1);
+ if (nfds < 0) {
+ printf("%d ", nfds);
+ perror("epoll_wait failed");
+ return disable();
+ } else if (nfds == 0) {
+ return 50;
+ }
+
+ int keys = 0;
+ memset(report, 0, 8);
+ for (int i = 0; i < nfds; i++) {
+
+ struct input_event ev[64];
+ int rd = read(events[i].data.fd, ev, sizeof(ev));
+ assert(rd > ((signed int)sizeof(struct input_event)));
+ for (int j = 0; j < rd / ((signed int)sizeof(struct input_event)); j++) {
+ InputEvent e;
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE;
+ e.source = this->_originName;
+ e.kbchar = 0;
+ unsigned int type, code;
+ type = ev[j].type;
+ code = ev[j].code;
+ int value = ev[j].value;
+ // printf("Event: time %ld.%06ld, ", ev[j].time.tv_sec, ev[j].time.tv_usec);
+
+ if (type == EV_KEY) {
+ uint8_t mod = 0;
+
+ switch (code) {
+ case KEY_LEFTCTRL:
+ mod = 0x01;
+ break;
+ case KEY_RIGHTCTRL:
+ mod = 0x10;
+ break;
+ case KEY_LEFTSHIFT:
+ mod = 0x02;
+ break;
+ case KEY_RIGHTSHIFT:
+ mod = 0x20;
+ break;
+ case KEY_LEFTALT:
+ mod = 0x04;
+ break;
+ case KEY_RIGHTALT:
+ mod = 0x40;
+ break;
+ case KEY_LEFTMETA:
+ mod = 0x08;
+ break;
+ }
+ if (value == 1) {
+ switch (code) {
+ case KEY_LEFTCTRL:
+ mod = 0x01;
+ break;
+ case KEY_RIGHTCTRL:
+ mod = 0x10;
+ break;
+ case KEY_LEFTSHIFT:
+ mod = 0x02;
+ break;
+ case KEY_RIGHTSHIFT:
+ mod = 0x20;
+ break;
+ case KEY_LEFTALT:
+ mod = 0x04;
+ break;
+ case KEY_RIGHTALT:
+ mod = 0x40;
+ break;
+ case KEY_LEFTMETA:
+ mod = 0x08;
+ break;
+ case KEY_ESC: // ESC
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL;
+ break;
+ case KEY_BACK: // Back
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK;
+ // e.kbchar = key;
+ break;
+
+ case KEY_UP: // Up
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP;
+ break;
+ case KEY_DOWN: // Down
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN;
+ break;
+ case KEY_LEFT: // Left
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT;
+ break;
+ e.kbchar = 0xb4;
+ case KEY_RIGHT: // Right
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT;
+ break;
+ e.kbchar = 0xb7;
+ case KEY_ENTER: // Enter
+ e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT;
+ break;
+ default: // all other keys
+ if (keymap[code]) {
+ e.inputEvent = ANYKEY;
+ e.kbchar = keymap[code];
+ }
+ break;
+ }
+ }
+ if (ev[j].value) {
+ modifiers |= mod;
+ } else {
+ modifiers &= ~mod;
+ }
+ report[0] = modifiers;
+ }
+ if (e.inputEvent != meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE) {
+ if (e.inputEvent == ANYKEY && (modifiers && 0x22))
+ e.kbchar = uppers[e.kbchar]; // doesn't get punctuation. Meh.
+ this->notifyObservers(&e);
+ }
+ }
+ }
+
+ return 50; // Keyscan every 50msec to avoid key bounce
+}
+
+#endif
\ No newline at end of file
diff --git a/src/input/LinuxInput.h b/src/input/LinuxInput.h
new file mode 100644
index 000000000..aa1e8e340
--- /dev/null
+++ b/src/input/LinuxInput.h
@@ -0,0 +1,65 @@
+#pragma once
+#if ARCH_PORTDUINO
+#include "InputBroker.h"
+#include "concurrency/OSThread.h"
+#include
+#include
+#include
+#include
+#include