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_esp32.yml b/.github/workflows/build_esp32.yml
index c9664152e..31f0dd5a0 100644
--- a/.github/workflows/build_esp32.yml
+++ b/.github/workflows/build_esp32.yml
@@ -35,6 +35,7 @@ jobs:
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
+ sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini
- name: Build ESP32
run: bin/build-esp32.sh ${{ inputs.board }}
diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml
new file mode 100644
index 000000000..a30cf33f1
--- /dev/null
+++ b/.github/workflows/build_esp32_c3.yml
@@ -0,0 +1,62 @@
+name: Build ESP32-C3
+
+on:
+ workflow_call:
+ inputs:
+ board:
+ required: true
+ type: string
+
+permissions: read-all
+
+jobs:
+ build-esp32-c3:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Build base
+ id: base
+ uses: ./.github/actions/setup-base
+
+ - name: Pull web ui
+ uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
+ with:
+ repo: meshtastic/web
+ file: build.tar
+ target: build.tar
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Unpack web ui
+ run: |
+ tar -xf build.tar -C data/static
+ rm build.tar
+ - name: Remove debug flags for release
+ if: ${{ github.event_name == 'workflow_dispatch' }}
+ run: |
+ sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
+ sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
+ sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
+ sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini
+ - name: Build ESP32
+ run: bin/build-esp32.sh ${{ inputs.board }}
+
+ - name: Pull OTA Firmware
+ uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
+ with:
+ repo: meshtastic/firmware-ota
+ file: firmware-c3.bin
+ target: release/bleota-c3.bin
+ token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Get release version string
+ shell: bash
+ run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+ id: version
+
+ - name: Store binaries as an artifact
+ uses: actions/upload-artifact@v3
+ with:
+ name: firmware-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
+ path: |
+ release/*.bin
+ release/*.elf
diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml
index 9611dd5b8..f603a6a31 100644
--- a/.github/workflows/build_esp32_s3.yml
+++ b/.github/workflows/build_esp32_s3.yml
@@ -34,6 +34,7 @@ jobs:
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32.ini
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s2.ini
sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32s3.ini
+ sed -i '/DDEBUG_HEAP/d' ./arch/esp32/esp32c3.ini
- name: Build ESP32
run: bin/build-esp32.sh ${{ inputs.board }}
diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml
index 103f43a71..7a25892bc 100644
--- a/.github/workflows/build_raspbian.yml
+++ b/.github/workflows/build_raspbian.yml
@@ -41,5 +41,5 @@ jobs:
with:
name: firmware-raspbian-${{ steps.version.outputs.version }}.zip
path: |
- release/meshtasticd_linux_arm64
+ release/meshtasticd_linux_aarch64
bin/config-dist.yaml
diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
index 6b6ff1ad7..9ca0764b5 100644
--- a/.github/workflows/main_matrix.yml
+++ b/.github/workflows/main_matrix.yml
@@ -32,7 +32,7 @@ jobs:
- board: meshtastic-diy-v1
- board: rak4631
- board: t-echo
- - board: station-g1
+ - board: station-g2
- board: m5stack-coreink
- board: tbeam-s3-core
- board: tlora-t3s3-v1
@@ -64,9 +64,9 @@ 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-v2_0
- board: heltec-v2_1
- board: tbeam0_7
@@ -78,6 +78,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,16 +91,30 @@ jobs:
- board: heltec-v3
- board: heltec-wsl-v3
- board: heltec-wireless-tracker
- - board: heltec-wireless-paper
+ - board: heltec-wireless-tracker-V1-0
+ - board: heltec-wireless-paper-v1_0
+ - board: heltec-wireless-paper #v1.1
- board: tbeam-s3-core
- board: tlora-t3s3-v1
- board: t-watch-s3
- board: t-deck
- board: picomputer-s3
+ - board: station-g2
+ - board: unphone
uses: ./.github/workflows/build_esp32_s3.yml
with:
board: ${{ matrix.board }}
+ build-esp32-c3:
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - board: heltec-ht62-esp32c3-sx1262
+ uses: ./.github/workflows/build_esp32_c3.yml
+ with:
+ board: ${{ matrix.board }}
+
build-nrf52:
strategy:
fail-fast: false
@@ -109,6 +124,7 @@ jobs:
- board: rak4631_eink
- board: monteops_hw1
- board: t-echo
+ - board: canaryone
- board: pca10059_diy_eink
- board: feather_diy
- board: nano-g2-ultra
@@ -119,11 +135,13 @@ 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 }}
@@ -210,11 +228,15 @@ 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-esp32-c3,
build-nrf52,
build-raspbian,
build-native,
@@ -240,7 +262,7 @@ jobs:
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 ./firmware-raspbian-*/release/meshtasticd_linux_arm64 ./firmware-raspbian-*/bin/config-dist.yaml
+ run: mv -b -t ./ ./*tbeam-2*/littlefs*.bin ./*tbeam-2*/bleota.bin ./*tbeam-s3*/bleota-s3.bin ./*esp32c3*/bleota-c3.bin ./**/firmware*.bin ./*t-echo*/Meshtastic_nRF52_factory_erase_v2.uf2 ./**/firmware-*.uf2 ./**/firmware-*-ota.zip ./**/*.elf ./*native*/*device-*.sh ./*native*/*device-*.bat ./firmware-raspbian-*/release/meshtasticd_linux_aarch64 ./firmware-raspbian-*/bin/config-dist.yaml
- name: Repackage in single firmware zip
uses: actions/upload-artifact@v3
@@ -283,14 +305,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:
diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml
index 61f82e9d7..dd4133dab 100644
--- a/.github/workflows/package_raspbian.yml
+++ b/.github/workflows/package_raspbian.yml
@@ -23,6 +23,14 @@ jobs:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
+ - name: Pull web ui
+ uses: dsaltares/fetch-gh-release-asset@a40c8b4a0471f9ab81bdf73a010f74cc51476ad4
+ with:
+ repo: meshtastic/web
+ file: build.tar
+ target: build.tar
+ token: ${{ secrets.GITHUB_TOKEN }}
+
- name: Get release version string
run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
id: version
@@ -37,10 +45,13 @@ jobs:
- name: build .debpkg
run: |
+ mkdir -p .debpkg/usr/share/doc/meshtasticd/web
mkdir -p .debpkg/usr/sbin
mkdir -p .debpkg/etc/meshtasticd
mkdir -p .debpkg/usr/lib/systemd/system/
- cp release/meshtasticd_linux_arm64 .debpkg/usr/sbin/meshtasticd
+ tar -xf build.tar -C .debpkg/usr/share/doc/meshtasticd/web
+ gunzip .debpkg/usr/share/doc/meshtasticd/web/*.gz
+ cp release/meshtasticd_linux_aarch64 .debpkg/usr/sbin/meshtasticd
cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
chmod +x .debpkg/usr/sbin/meshtasticd
cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
@@ -52,7 +63,7 @@ jobs:
maintainer: Jonathan Bennett
version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.*
arch: arm64
- depends: libyaml-cpp0.7
+ depends: libyaml-cpp0.7, openssl, libulfius2.7
desc: Native Linux Meshtastic binary.
- uses: actions/upload-artifact@v3
diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml
index 6944d827e..30f9b3578 100644
--- a/.github/workflows/update_protobufs.yml
+++ b/.github/workflows/update_protobufs.yml
@@ -17,9 +17,9 @@ jobs:
- name: Download nanopb
run: |
- wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.7-linux-x86.tar.gz
- tar xvzf nanopb-0.4.7-linux-x86.tar.gz
- mv nanopb-0.4.7-linux-x86 nanopb-0.4.7
+ wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.8-linux-x86.tar.gz
+ tar xvzf nanopb-0.4.8-linux-x86.tar.gz
+ mv nanopb-0.4.8-linux-x86 nanopb-0.4.8
- name: Re-generate protocol buffers
run: |
diff --git a/.gitignore b/.gitignore
index 89f8ee065..0f2202f8d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,5 @@ venv/
release/
.vscode/extensions.json
/compile_commands.json
+src/mesh/raspihttp/certificate.pem
+src/mesh/raspihttp/private_key.pem
\ No newline at end of file
diff --git a/.trunk/.gitignore b/.trunk/.gitignore
index 1e2465290..15966d087 100644
--- a/.trunk/.gitignore
+++ b/.trunk/.gitignore
@@ -6,3 +6,4 @@
plugins
user_trunk.yaml
user.yaml
+tmp
diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index e31b026f4..0826b71d9 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -1,35 +1,36 @@
version: 0.1
cli:
- version: 1.17.1
+ version: 1.20.1
plugins:
sources:
- id: trunk
- ref: v1.2.6
+ ref: v1.4.4
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- - bandit@1.7.5
- - checkov@3.0.16
- - terrascan@1.18.3
- - trivy@0.46.1
- - trufflehog@3.62.1
+ - trufflehog@3.68.5
+ - yamllint@1.35.1
+ - bandit@1.7.7
+ - checkov@3.2.32
+ - terrascan@1.19.1
+ - trivy@0.49.1
+ #- trufflehog@3.63.2-rc0
- taplo@0.8.1
- - ruff@0.1.3
- - yamllint@1.32.0
- - isort@5.12.0
- - markdownlint@0.37.0
+ - ruff@0.3.1
+ - isort@5.13.2
+ - markdownlint@0.39.0
- oxipng@9.0.0
- - svgo@3.0.2
- - actionlint@1.6.26
- - flake8@6.1.0
+ - svgo@3.2.0
+ - actionlint@1.6.27
+ - flake8@7.0.0
- hadolint@2.12.0
- shfmt@3.6.0
- shellcheck@0.9.0
- - black@23.9.1
+ - black@24.2.0
- git-diff-check
- - gitleaks@8.18.0
+ - gitleaks@8.18.2
- clang-format@16.0.3
- - prettier@3.0.3
+ - prettier@3.2.5
runtimes:
enabled:
- python@3.10.8
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 03922dc72..e86d31c7d 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,7 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "trunk.io",
- "trunk.enableWindows": true
+ "trunk.enableWindows": true,
+ "files.insertFinalNewline": false,
+ "files.trimFinalNewlines": false
}
diff --git a/Dockerfile b/Dockerfile
index 8e3cd2154..fee6c62d4 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM debian:bullseye-slim AS builder
+FROM debian:bookworm-slim AS builder
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Etc/UTC
@@ -11,31 +11,45 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Install build deps
USER root
-RUN apt-get update && \
- apt-get -y install wget python3 g++ zip python3-venv git vim ca-certificates
-# create a non-priveleged user & group
+# trunk-ignore(terrascan/AC_DOCKER_0002): Known terrascan issue
+# trunk-ignore(hadolint/DL3008): Use latest version of packages for buildchain
+RUN apt-get update && apt-get install --no-install-recommends -y wget python3 python3-pip python3-wheel python3-venv g++ zip git \
+ ca-certificates libgpiod-dev libyaml-cpp-dev libbluetooth-dev \
+ libulfius-dev liborcania-dev libssl-dev pkg-config && \
+ apt-get clean && rm -rf /var/lib/apt/lists/* && mkdir /tmp/firmware
+
+RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh && chown mesh:mesh /tmp/firmware
+USER mesh
+
+WORKDIR /tmp/firmware
+RUN python3 -m venv /tmp/firmware
+RUN source ./bin/activate && pip3 install --no-cache-dir -U platformio==6.1.14
+# trunk-ignore(terrascan/AC_DOCKER_00024): We would actually like these files to be owned by mesh tyvm
+COPY --chown=mesh:mesh . /tmp/firmware
+RUN source ./bin/activate && chmod +x /tmp/firmware/bin/build-native.sh && ./bin/build-native.sh
+RUN cp "/tmp/firmware/release/meshtasticd_linux_$(uname -m)" "/tmp/firmware/release/meshtasticd"
+
+
+##### PRODUCTION BUILD #############
+
+FROM debian:bookworm-slim
+ENV DEBIAN_FRONTEND=noninteractive
+ENV TZ=Etc/UTC
+
+# trunk-ignore(terrascan/AC_DOCKER_0002): Known terrascan issue
+# trunk-ignore(hadolint/DL3008): Use latest version of packages for buildchain
+RUN apt-get update && apt-get --no-install-recommends -y install libc-bin libc6 libgpiod2 libyaml-cpp0.7 libulfius2.7 liborcania2.3 libssl3 && \
+ apt-get clean && rm -rf /var/lib/apt/lists/*
+
RUN groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh
-
USER mesh
-RUN wget https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py -qO /tmp/get-platformio.py && \
- chmod +x /tmp/get-platformio.py && \
- python3 /tmp/get-platformio.py && \
- git clone https://github.com/meshtastic/firmware --recurse-submodules /tmp/firmware && \
- cd /tmp/firmware && \
- chmod +x /tmp/firmware/bin/build-native.sh && \
- source ~/.platformio/penv/bin/activate && \
- ./bin/build-native.sh
-FROM frolvlad/alpine-glibc
-
-RUN apk --update add --no-cache g++ shadow && \
- groupadd -g 1000 mesh && useradd -ml -u 1000 -g 1000 mesh
-
-COPY --from=builder /tmp/firmware/release/meshtasticd_linux_amd64 /home/mesh/
-
-USER mesh
WORKDIR /home/mesh
-CMD sh -cx "./meshtasticd_linux_amd64 --hwid '$RANDOM'"
+COPY --from=builder /tmp/firmware/release/meshtasticd /home/mesh/
+
+VOLUME /home/mesh/data
+
+CMD [ "sh", "-cx", "./meshtasticd -d /home/mesh/data --hwid=${HWID:-$RANDOM}" ]
HEALTHCHECK NONE
diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini
index aa31db13e..39935b849 100644
--- a/arch/esp32/esp32.ini
+++ b/arch/esp32/esp32.ini
@@ -4,7 +4,7 @@ extends = arduino_base
platform = platformio/espressif32@6.3.2 # This is a temporary fix to the S3-based devices bluetooth issues until we can determine what within ESP-IDF changed and can develop a suitable patch.
build_src_filter =
- ${arduino_base.build_src_filter} - - - -
+ ${arduino_base.build_src_filter} - - - - -
upload_speed = 921600
debug_init_break = tbreak setup
@@ -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/esp32/esp32s2.ini b/arch/esp32/esp32s2.ini
index 3bde3465a..5de0fa549 100644
--- a/arch/esp32/esp32s2.ini
+++ b/arch/esp32/esp32s2.ini
@@ -2,7 +2,7 @@
extends = esp32_base
build_src_filter =
- ${esp32_base.build_src_filter} -
+ ${esp32_base.build_src_filter} - -
monitor_speed = 115200
@@ -12,5 +12,4 @@ build_flags =
lib_ignore =
${esp32_base.lib_ignore}
- NimBLE-Arduino
-
+ NimBLE-Arduino
\ No newline at end of file
diff --git a/arch/nrf52/nrf52.ini b/arch/nrf52/nrf52.ini
index d1826a96a..2505fe315 100644
--- a/arch/nrf52/nrf52.ini
+++ b/arch/nrf52/nrf52.ini
@@ -1,6 +1,6 @@
[nrf52_base]
; Instead of the standard nordicnrf52 platform, we use our fork which has our added variant files
-platform = platformio/nordicnrf52@^10.1.0
+platform = platformio/nordicnrf52@^10.4.0
extends = arduino_base
build_type = debug ; I'm debugging with ICE a lot now
@@ -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..53f06c9f3 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#6fb39b6f94ece9c042141edb4afb91aca94dcaab
framework = arduino
build_src_filter =
@@ -10,7 +10,9 @@ build_src_filter =
-
-
-
+ -
-
+ +
-
-
-
@@ -22,9 +24,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
diff --git a/arch/rp2040/rp2040.ini b/arch/rp2040/rp2040.ini
index 495b52a86..dd3a4d7ff 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#60d6ae81fcc73c34b1493ca9e261695e471bc0c2
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.7.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
rweather/Crypto
\ No newline at end of file
diff --git a/arch/stm32/stm32wl5e.ini b/arch/stm32/stm32wl5e.ini
index 524edd6b9..4d74ade8f 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 64c5adb50..9d31d091a 100755
--- a/bin/build-native.sh
+++ b/bin/build-native.sh
@@ -13,15 +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
-
-if command -v raspi-config &>/dev/null; then
- pio run --environment raspbian
- cp .pio/build/raspbian/program $OUTDIR/meshtasticd_linux_arm64
-else
- pio run --environment native
- cp .pio/build/native/program $OUTDIR/meshtasticd_linux_amd64
-fi
-
+platformio pkg update --environment native
+pio run --environment native
+cp .pio/build/native/program "$OUTDIR/meshtasticd_linux_$(uname -m)"
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
index 6c8f1946f..f729f1ac7 100644
--- a/bin/config-dist.yaml
+++ b/bin/config-dist.yaml
@@ -1,5 +1,6 @@
-# Define your devices here using Broadcom pin numbering
-# Uncomment the block that corresponds to your hardware
+### Define your devices here using Broadcom pin numbering
+### Uncomment the block that corresponds to your hardware
+### Including the "Module:" line!
---
Lora:
# Module: sx1262 # Waveshare SX126X XXXM
@@ -14,6 +15,12 @@ Lora:
# IRQ: 17
# Reset: 22
+# Module: sx1262 # pinedio
+# CS: 0
+# IRQ: 10
+# Busy: 11
+# spidev: spidev0.1
+
# Module: RF95 # Adafruit RFM9x
# Reset: 25
# CS: 7
@@ -24,3 +31,99 @@ Lora:
# 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:
+### Note, at least for now, the touchscreen must have a CS pin defined, even if you let Linux manage the CS switching.
+
+# Module: STMPE610
+# CS: 7
+# IRQ: 24
+
+# Module: XPT2046 # Waveshare 2.8inch
+# CS: 7
+# IRQ: 17
+
+### Configure device for direct keyboard input
+
+Input:
+# KeyboardDevice: /dev/input/by-id/usb-_Raspberry_Pi_Internal_Keyboard-event-kbd
+
+###
+
+Logging:
+ LogLevel: info # debug, info, warn, error
+
+Webserver:
+# Port: 443 # Port for Webserver & Webservices
+# RootPath: /usr/share/doc/meshtasticd/web # Root Dir of WebServer
+
+General:
+ MaxNodes: 200
diff --git a/bin/device-install.bat b/bin/device-install.bat
index c7d8a10cf..6c880185e 100755
--- a/bin/device-install.bat
+++ b/bin/device-install.bat
@@ -31,9 +31,13 @@ IF EXIST %FILENAME% IF x%FILENAME:update=%==x%FILENAME% (
%PYTHON% -m esptool --baud 115200 erase_flash
%PYTHON% -m esptool --baud 115200 write_flash 0x00 %FILENAME%
- @REM Account for S3 board's different OTA partition
- IF x%FILENAME:s3=%==x%FILENAME% IF x%FILENAME:v3=%==x%FILENAME% IF x%FILENAME:t-deck=%==x%FILENAME% IF x%FILENAME:wireless-paper=%==x%FILENAME% IF x%FILENAME:wireless-tracker=%==x%FILENAME% (
- %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin
+ @REM Account for S3 and C3 board's different OTA partition
+ IF x%FILENAME:s3=%==x%FILENAME% IF x%FILENAME:v3=%==x%FILENAME% IF x%FILENAME:t-deck=%==x%FILENAME% IF x%FILENAME:wireless-paper=%==x%FILENAME% IF x%FILENAME:wireless-tracker=%==x%FILENAME% IF x%FILENAME:station-g2=%==x%FILENAME% IF x%FILENAME:unphone=%==x%FILENAME% (
+ IF x%FILENAME:esp32c3=%==x%FILENAME% (
+ %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin
+ ) else (
+ %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota-c3.bin
+ )
) else (
%PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota-s3.bin
)
diff --git a/bin/device-install.sh b/bin/device-install.sh
index 35d99286d..563a87af4 100755
--- a/bin/device-install.sh
+++ b/bin/device-install.sh
@@ -1,12 +1,12 @@
#!/bin/sh
-PYTHON=${PYTHON:-$(which python3 python|head -n 1)}
+PYTHON=${PYTHON:-$(which python3 python | head -n 1)}
set -e
# Usage info
show_help() {
-cat << EOF
+ cat <&2
- exit 1
- ;;
- esac
+ case "${opt}" in
+ h)
+ show_help
+ exit 0
+ ;;
+ p)
+ export ESPTOOL_PORT=${OPTARG}
+ ;;
+ P)
+ PYTHON=${OPTARG}
+ ;;
+ f)
+ FILENAME=${OPTARG}
+ ;;
+ *)
+ echo "Invalid flag."
+ show_help >&2
+ exit 1
+ ;;
+ esac
done
-shift "$((OPTIND-1))"
+shift "$((OPTIND - 1))"
[ -z "$FILENAME" -a -n "$1" ] && {
- FILENAME=$1
- shift
+ FILENAME=$1
+ shift
}
-if [ -f "${FILENAME}" ] && [ ! -z "${FILENAME##*"update"*}" ]; then
+if [ -f "${FILENAME}" ] && [ -n "${FILENAME##*"update"*}" ]; then
echo "Trying to flash ${FILENAME}, but first erasing and writing system information"
- "$PYTHON" -m esptool erase_flash
- "$PYTHON" -m esptool write_flash 0x00 ${FILENAME}
+ "$PYTHON" -m esptool erase_flash
+ "$PYTHON" -m esptool write_flash 0x00 ${FILENAME}
# Account for S3 board's different OTA partition
- if [ ! -z "${FILENAME##*"s3"*}" ] && [ ! -z "${FILENAME##*"-v3"*}" ] && [ ! -z "${FILENAME##*"t-deck"*}" ] && [ ! -z "${FILENAME##*"wireless-paper"*}" ] && [ ! -z "${FILENAME##*"wireless-tracker"*}" ]; then
- "$PYTHON" -m esptool write_flash 0x260000 bleota.bin
+ if [ -n "${FILENAME##*"s3"*}" ] && [ -n "${FILENAME##*"-v3"*}" ] && [ -n "${FILENAME##*"t-deck"*}" ] && [ -n "${FILENAME##*"wireless-paper"*}" ] && [ -n "${FILENAME##*"wireless-tracker"*}" ] && [ -n "${FILENAME##*"station-g2"*}" ] && [ -n "${FILENAME##*"unphone"*}" ]; then
+ if [ -n "${FILENAME##*"esp32c3"*}" ]; then
+ "$PYTHON" -m esptool write_flash 0x260000 bleota.bin
+ else
+ "$PYTHON" -m esptool write_flash 0x260000 bleota-c3.bin
+ fi
else
- "$PYTHON" -m esptool write_flash 0x260000 bleota-s3.bin
+ "$PYTHON" -m esptool write_flash 0x260000 bleota-s3.bin
fi
- "$PYTHON" -m esptool write_flash 0x300000 littlefs-*.bin
+ "$PYTHON" -m esptool write_flash 0x300000 littlefs-*.bin
else
show_help
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
index 4ed1bfd8f..f15fdc871 100644
--- a/bin/meshtasticd.service
+++ b/bin/meshtasticd.service
@@ -1,9 +1,12 @@
-[unit]
-description=Meshtastic Native Daemon
+[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
+WantedBy=multi-user.target
\ No newline at end of file
diff --git a/bin/native-install.sh b/bin/native-install.sh
index d1d0c8707..ba71c4f46 100755
--- a/bin/native-install.sh
+++ b/bin/native-install.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
-cp release/meshtasticd_linux_arm64 /usr/sbin/meshtasticd
+cp "release/meshtasticd_linux_$(uname -m)" /usr/sbin/meshtasticd
mkdir /etc/meshtasticd
if [[ -f "/etc/meshtasticd/config.yaml" ]]; then
cp bin/config-dist.yaml /etc/meshtasticd/config-upgrade.yaml
diff --git a/bin/regen-protos.bat b/bin/regen-protos.bat
index 1422f7914..f28ef0025 100644
--- a/bin/regen-protos.bat
+++ b/bin/regen-protos.bat
@@ -1 +1 @@
-cd protobufs && ..\nanopb-0.4.7\generator-bin\protoc.exe --experimental_allow_proto3_optional --nanopb_out=-v:..\src\mesh\generated -I=..\protobufs ..\protobufs\meshtastic\*.proto
+cd protobufs && ..\nanopb-0.4.8\generator-bin\protoc.exe --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:..\src\mesh\generated" -I=..\protobufs\ ..\protobufs\meshtastic\*.proto
diff --git a/bin/regen-protos.sh b/bin/regen-protos.sh
index ad771ab45..2e60784e3 100755
--- a/bin/regen-protos.sh
+++ b/bin/regen-protos.sh
@@ -2,19 +2,10 @@
set -e
-echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.7 to be located in the"
+echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.8 to be located in the"
echo "firmware root directory if the following step fails, you should download the correct"
-echo "prebuilt binaries for your computer into nanopb-0.4.7"
+echo "prebuilt binaries for your computer into nanopb-0.4.8"
# the nanopb tool seems to require that the .options file be in the current directory!
cd protobufs
-../nanopb-0.4.7/generator-bin/protoc --nanopb_out=-v:../src/mesh/generated/ -I=../protobufs meshtastic/*.proto --experimental_allow_proto3_optional
-
-# cd ../src/mesh/generated/meshtastic
-# sed -i 's/#include "meshtastic/#include "./g' -- *
-
-# sed -i 's/meshtastic_//g' -- *
-
-#echo "Regenerating protobuf documentation - if you see an error message"
-#echo "you can ignore it unless doing a new protobuf release to github."
-#bin/regen-docs.sh
+../nanopb-0.4.8/generator-bin/protoc --experimental_allow_proto3_optional "--nanopb_out=-S.cpp -v:../src/mesh/generated/" -I=../protobufs meshtastic/*.proto
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/CDEBYTE_EoRa-S3.json b/boards/CDEBYTE_EoRa-S3.json
new file mode 100644
index 000000000..9ecee3c9f
--- /dev/null
+++ b/boards/CDEBYTE_EoRa-S3.json
@@ -0,0 +1,38 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "esp32s3_out.ld"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-D CDEBYTE_EORA_S3",
+ "-D ARDUINO_USB_CDC_ON_BOOT=1",
+ "-D ARDUINO_USB_MODE=0",
+ "-D ARDUINO_RUNNING_CORE=1",
+ "-D ARDUINO_EVENT_RUNNING_CORE=1",
+ "-D BOARD_HAS_PSRAM"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "80000000L",
+ "flash_mode": "dio",
+ "hwids": [["0x303A", "0x1001"]],
+ "mcu": "esp32s3",
+ "variant": "CDEBYTE_EoRa-S3"
+ },
+ "connectivity": ["wifi"],
+ "debug": {
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": ["arduino", "espidf"],
+ "name": "CDEBYTE EoRa-S3",
+ "upload": {
+ "flash_size": "4MB",
+ "maximum_ram_size": 327680,
+ "maximum_size": 4194304,
+ "wait_for_upload_port": true,
+ "require_upload_port": true,
+ "speed": 921600
+ },
+ "url": "https://www.cdebyte.com/Module-Testkits-EoRaPI",
+ "vendor": "CDEBYTE"
+}
diff --git a/boards/canaryone.json b/boards/canaryone.json
new file mode 100644
index 000000000..f64a4a7c7
--- /dev/null
+++ b/boards/canaryone.json
@@ -0,0 +1,52 @@
+{
+ "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"],
+ ["0x239A", "0x009F"]
+ ],
+ "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/station-g2.json b/boards/station-g2.json
new file mode 100755
index 000000000..871f067aa
--- /dev/null
+++ b/boards/station-g2.json
@@ -0,0 +1,41 @@
+{
+ "build": {
+ "arduino": {
+ "ldscript": "esp32s3_out.ld",
+ "memory_type": "qio_opi"
+ },
+ "core": "esp32",
+ "extra_flags": [
+ "-DBOARD_HAS_PSRAM",
+ "-DARDUINO_USB_CDC_ON_BOOT=1",
+ "-DARDUINO_USB_MODE=0",
+ "-DARDUINO_RUNNING_CORE=1",
+ "-DARDUINO_EVENT_RUNNING_CORE=0"
+ ],
+ "f_cpu": "240000000L",
+ "f_flash": "80000000L",
+ "flash_mode": "qio",
+ "hwids": [["0x303A", "0x1001"]],
+ "mcu": "esp32s3",
+ "variant": "station-g2"
+ },
+ "connectivity": ["wifi", "bluetooth", "lora"],
+ "debug": {
+ "default_tool": "esp-builtin",
+ "onboard_tools": ["esp-builtin"],
+ "openocd_target": "esp32s3.cfg"
+ },
+ "frameworks": ["arduino", "espidf"],
+ "name": "BQ Station G2",
+ "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://wiki.uniteng.com/en/meshtastic/station-g2",
+ "vendor": "BQ Consulting"
+}
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/tbeam-s3-core.json b/boards/tbeam-s3-core.json
index 7bda2e5a0..4c82a2789 100644
--- a/boards/tbeam-s3-core.json
+++ b/boards/tbeam-s3-core.json
@@ -7,8 +7,7 @@
"extra_flags": [
"-DBOARD_HAS_PSRAM",
"-DLILYGO_TBEAM_S3_CORE",
- "-DARDUINO_USB_CDC_ON_BOOT=1",
- "-DARDUINO_USB_MODE=0",
+ "-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
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 cb79565f1..a1082a84a 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
@@ -27,7 +30,7 @@
;default_envs = m5stack-coreink
;default_envs = rak4631
;default_envs = rak10701
-default_envs = wio-e5
+;default_envs = wio-e5
extra_configs =
arch/*/*.ini
@@ -51,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
@@ -64,15 +69,17 @@ build_flags = -Wno-missing-field-initializers
-DRADIOLIB_EXCLUDE_PAGER
-DRADIOLIB_EXCLUDE_FSK4
-DRADIOLIB_EXCLUDE_APRS
+ -DRADIOLIB_EXCLUDE_LORAWAN
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.5.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/ArduinoThread.git#72921ac222eed6f526ba1682023cee290d9aa1b3
+ https://github.com/meshtastic/TinyGPSPlus.git#964f75a72cccd6b53cd74e4add1f7a42c6f7344d
+ https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0
nanopb/Nanopb@^0.4.7
erriez/ErriezCRC32@^1.0.1
@@ -93,7 +100,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
@@ -107,14 +114,15 @@ lib_deps =
; (not included in native / portduino)
[environmental_base]
lib_deps =
- adafruit/Adafruit BusIO@^1.11.4
+ adafruit/Adafruit BusIO@^1.15.0
adafruit/Adafruit Unified Sensor@^1.1.11
adafruit/Adafruit BMP280 Library@^2.6.8
+ adafruit/Adafruit BMP085 Library@^1.2.4
adafruit/Adafruit BME280 Library@^2.2.2
https://github.com/boschsensortec/Bosch-BSEC2-Library#v1.5.2400
boschsensortec/BME68x Sensor Library@^1.1.40407
adafruit/Adafruit MCP9808 Library@^2.0.0
- https://github.com/Tinyu-Zhao/INA3221@^0.0.1
+ 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
@@ -123,4 +131,5 @@ 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/SensorLib#27fd0f721e20cd09e1f81383f0ba58a54fe84a17
+ adafruit/Adafruit LSM6DS@^4.7.2
\ No newline at end of file
diff --git a/protobufs b/protobufs
index c845b7848..0d08acd9c 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit c845b7848eebb11150ca0427773303bf8758e533
+Subproject commit 0d08acd9c51c4e5575f3ea42368834ec990b2278
diff --git a/src/AccelerometerThread.h b/src/AccelerometerThread.h
index da5695368..fa5acdaae 100644
--- a/src/AccelerometerThread.h
+++ b/src/AccelerometerThread.h
@@ -5,18 +5,19 @@
#include "power.h"
#include
+#include
#include
#include
+#include
#include
-#include
-BMA423 bmaSensor;
+SensorBMA423 bmaSensor;
bool BMA_IRQ = false;
#define ACCELEROMETER_CHECK_INTERVAL_MS 100
#define ACCELEROMETER_CLICK_THRESHOLD 40
-uint16_t readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len)
+int readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
{
Wire.beginTransmission(address);
Wire.write(reg);
@@ -29,7 +30,7 @@ uint16_t readRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len)
return 0; // Pass
}
-uint16_t writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint16_t len)
+int writeRegister(uint8_t address, uint8_t reg, uint8_t *data, uint8_t len)
{
Wire.beginTransmission(address);
Wire.write(reg);
@@ -42,7 +43,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");
@@ -72,24 +73,14 @@ class AccelerometerThread : public concurrency::OSThread
lis.setRange(LIS3DH_RANGE_2_G);
// Adjust threshold, higher numbers are less sensitive
lis.setClick(config.device.double_tap_as_button_press ? 2 : 1, ACCELEROMETER_CLICK_THRESHOLD);
- } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.begin(readRegister, writeRegister, delay)) {
+ } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 &&
+ bmaSensor.begin(accelerometer_found.address, &readRegister, &writeRegister)) {
LOG_DEBUG("BMA423 initializing\n");
- Acfg cfg;
- cfg.odr = BMA4_OUTPUT_DATA_RATE_100HZ;
- cfg.range = BMA4_ACCEL_RANGE_2G;
- cfg.bandwidth = BMA4_ACCEL_NORMAL_AVG4;
- cfg.perf_mode = BMA4_CONTINUOUS_MODE;
- bmaSensor.setAccelConfig(cfg);
- bmaSensor.enableAccel();
-
- struct bma4_int_pin_config pin_config;
- pin_config.edge_ctrl = BMA4_LEVEL_TRIGGER;
- pin_config.lvl = BMA4_ACTIVE_HIGH;
- pin_config.od = BMA4_PUSH_PULL;
- pin_config.output_en = BMA4_OUTPUT_ENABLE;
- pin_config.input_en = BMA4_INPUT_DISABLE;
- // The correct trigger interrupt needs to be configured as needed
- bmaSensor.setINTPinConfig(pin_config, BMA4_INTR1_MAP);
+ bmaSensor.configAccelerometer(bmaSensor.RANGE_2G, bmaSensor.ODR_100HZ, bmaSensor.BW_NORMAL_AVG4,
+ bmaSensor.PERF_CONTINUOUS_MODE);
+ bmaSensor.enableAccelerometer();
+ bmaSensor.configInterrupt(BMA4_LEVEL_TRIGGER, BMA4_ACTIVE_HIGH, BMA4_PUSH_PULL, BMA4_OUTPUT_ENABLE,
+ BMA4_INPUT_DISABLE);
#ifdef BMA423_INT
pinMode(BMA4XX_INT, INPUT);
@@ -102,25 +93,31 @@ class AccelerometerThread : public concurrency::OSThread
RISING); // Select the interrupt mode according to the actual circuit
#endif
- struct bma423_axes_remap remap_data;
- remap_data.x_axis = 0;
- remap_data.x_axis_sign = 1;
- remap_data.y_axis = 1;
- remap_data.y_axis_sign = 0;
- remap_data.z_axis = 2;
- remap_data.z_axis_sign = 1;
+#ifdef T_WATCH_S3
// Need to raise the wrist function, need to set the correct axis
- bmaSensor.setRemapAxes(&remap_data);
- // sensor.enableFeature(BMA423_STEP_CNTR, true);
- bmaSensor.enableFeature(BMA423_TILT, true);
- bmaSensor.enableFeature(BMA423_WAKEUP, true);
- // sensor.resetStepCounter();
+ bmaSensor.setReampAxes(bmaSensor.REMAP_TOP_LAYER_RIGHT_CORNER);
+#else
+ bmaSensor.setReampAxes(bmaSensor.REMAP_BOTTOM_LAYER_BOTTOM_LEFT_CORNER);
+#endif
+ // bmaSensor.enableFeature(bmaSensor.FEATURE_STEP_CNTR, true);
+ bmaSensor.enableFeature(bmaSensor.FEATURE_TILT, true);
+ bmaSensor.enableFeature(bmaSensor.FEATURE_WAKEUP, true);
+ // bmaSensor.resetPedometer();
// Turn on feature interrupt
- bmaSensor.enableStepCountInterrupt();
- bmaSensor.enableTiltInterrupt();
+ bmaSensor.enablePedometerIRQ();
+ bmaSensor.enableTiltIRQ();
// It corresponds to isDoubleClick interrupt
- bmaSensor.enableWakeupInterrupt();
+ bmaSensor.enableWakeupIRQ();
+ } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.begin_I2C(accelerometer_found.address)) {
+ LOG_DEBUG("LSM6DS3 initializing\n");
+ // Default threshold of 2G, less sensitive options are 4, 8 or 16G
+ lsm.setAccelRange(LSM6DS_ACCEL_RANGE_2_G);
+#ifndef LSM6DS3_WAKE_THRESH
+#define LSM6DS3_WAKE_THRESH 20
+#endif
+ lsm.enableWakeup(config.display.wake_on_tap_or_motion, 1, LSM6DS3_WAKE_THRESH);
+ // Duration is number of occurances needed to trigger, higher threshold is less sensitive
}
}
@@ -141,11 +138,14 @@ class AccelerometerThread : public concurrency::OSThread
buttonPress();
return 500;
}
- } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.getINT()) {
- if (bmaSensor.isTilt() || bmaSensor.isDoubleClick()) {
+ } else if (acceleremoter_type == ScanI2C::DeviceType::BMA423 && bmaSensor.readIrqStatus() != DEV_WIRE_NONE) {
+ if (bmaSensor.isTilt() || bmaSensor.isDoubleTap()) {
wakeScreen();
return 500;
}
+ } else if (acceleremoter_type == ScanI2C::DeviceType::LSM6DS3 && lsm.shake()) {
+ wakeScreen();
+ return 500;
}
return ACCELEROMETER_CHECK_INTERVAL_MS;
@@ -169,6 +169,7 @@ class AccelerometerThread : public concurrency::OSThread
ScanI2C::DeviceType acceleremoter_type;
Adafruit_MPU6050 mpu;
Adafruit_LIS3DH lis;
+ Adafruit_LSM6DS3TRC lsm;
};
} // namespace concurrency
\ No newline at end of file
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..206bb7239
--- /dev/null
+++ b/src/ButtonThread.cpp
@@ -0,0 +1,317 @@
+#include "ButtonThread.h"
+#include "configuration.h"
+#if !MESHTASTIC_EXCLUDE_GPS
+#include "GPS.h"
+#endif
+#include "MeshService.h"
+#include "PowerFSM.h"
+#include "RadioLibInterface.h"
+#include "buzz.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;
+
+ButtonThread *buttonThread; // Declared extern in header
+volatile ButtonThread::ButtonEventType ButtonThread::btnEvent = ButtonThread::BUTTON_EVENT_NONE;
+
+#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
+OneButton ButtonThread::userButton; // Get reference to static member
+#endif
+
+ButtonThread::ButtonThread() : OSThread("Button")
+{
+#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
+
+#if defined(ARCH_PORTDUINO)
+ if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC) {
+ this->userButton = OneButton(settingsMap[user], true, true);
+ LOG_DEBUG("Using GPIO%02d for button\n", settingsMap[user]);
+ }
+#elif defined(BUTTON_PIN)
+ int pin = config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN; // Resolved 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
+
+#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
+ userButton.attachClick(userButtonPressed);
+ userButton.setClickMs(250);
+ userButton.setPressMs(c_longPressTime);
+ userButton.setDebounceMs(1);
+ userButton.attachDoubleClick(userButtonDoublePressed);
+ userButton.attachMultiClick(userButtonMultiPressed, this); // Reference to instance: get click count from non-static OneButton
+#ifndef T_DECK // T-Deck immediately wakes up after shutdown, so disable this function
+ userButton.attachLongPressStart(userButtonPressedLongStart);
+ userButton.attachLongPressStop(userButtonPressedLongStop);
+#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);
+#endif
+
+#ifdef BUTTON_PIN_TOUCH
+ userButtonTouch = OneButton(BUTTON_PIN_TOUCH, true, true);
+ userButtonTouch.setPressMs(400);
+ userButtonTouch.attachLongPressStart(touchPressedLongStart); // Better handling with longpress than click?
+#endif
+
+ attachButtonInterrupts();
+#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");
+ service.refreshLocalMeshNode();
+ service.sendNetworkPing(NODENUM_BROADCAST, true);
+ if (screen) {
+ screen->print("Sent ad-hoc ping\n");
+ screen->forceDisplay(true); // Force a new UI frame, then force an EInk update
+ }
+ break;
+ }
+
+ case BUTTON_EVENT_MULTI_PRESSED: {
+ LOG_BUTTON("Mulitipress! %hux\n", multipressClickCount);
+ switch (multipressClickCount) {
+#if HAS_GPS
+ // 3 clicks: toggle GPS
+ case 3:
+ if (!config.device.disable_triple_click && (gps != nullptr)) {
+ gps->toggleGpsMode();
+ if (screen)
+ screen->forceDisplay(true); // Force a new UI frame, then force an EInk update
+ }
+ break;
+#endif
+#if defined(USE_EINK) && defined(PIN_EINK_EN) // i.e. T-Echo
+ // 4 clicks: toggle backlight
+ case 4:
+ digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
+ break;
+#endif
+ // No valid multipress action
+ default:
+ break;
+ } // end switch: click count
+
+ break;
+ } // end multipress event
+
+ 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;
+ }
+
+#ifdef BUTTON_PIN_TOUCH
+ case BUTTON_EVENT_TOUCH_LONG_PRESSED: {
+ LOG_BUTTON("Touch press!\n");
+ if (config.display.wake_on_tap_or_motion) {
+ if (screen) {
+ // Wake if asleep
+ if (powerFSM.getState() == &stateDARK)
+ powerFSM.trigger(EVENT_PRESS);
+
+ // Update display (legacy behaviour)
+ screen->forceDisplay();
+ }
+ }
+ break;
+ }
+#endif // BUTTON_PIN_TOUCH
+
+ default:
+ break;
+ }
+ btnEvent = BUTTON_EVENT_NONE;
+ }
+
+ return 50;
+}
+
+/*
+ * Attach (or re-attach) hardware interrupts for buttons
+ * Public method. Used outside class when waking from MCU sleep
+ */
+void ButtonThread::attachButtonInterrupts()
+{
+#if defined(ARCH_PORTDUINO)
+ if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC)
+ wakeOnIrq(settingsMap[user], FALLING);
+#elif defined(BUTTON_PIN)
+ // Interrupt for user button, during normal use. Improves responsiveness.
+ attachInterrupt(
+ config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN,
+ []() {
+ BaseType_t higherWake = 0;
+ mainDelay.interruptFromISR(&higherWake);
+ ButtonThread::userButton.tick();
+ },
+ CHANGE);
+#endif
+
+#ifdef BUTTON_PIN_ALT
+ wakeOnIrq(BUTTON_PIN_ALT, FALLING);
+#endif
+
+#ifdef BUTTON_PIN_TOUCH
+ wakeOnIrq(BUTTON_PIN_TOUCH, FALLING);
+#endif
+}
+
+/*
+ * Detach the "normal" button interrupts.
+ * Public method. Used before attaching a "wake-on-button" interrupt for MCU sleep
+ */
+void ButtonThread::detachButtonInterrupts()
+{
+#if defined(ARCH_PORTDUINO)
+ if (settingsMap.count(user) != 0 && settingsMap[user] != RADIOLIB_NC)
+ detachInterrupt(settingsMap[user]);
+#elif defined(BUTTON_PIN)
+ detachInterrupt(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
+#endif
+
+#ifdef BUTTON_PIN_ALT
+ detachInterrupt(BUTTON_PIN_ALT);
+#endif
+
+#ifdef BUTTON_PIN_TOUCH
+ detachInterrupt(BUTTON_PIN_TOUCH);
+#endif
+}
+
+/**
+ * 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);
+}
+
+// Static callback
+void ButtonThread::userButtonMultiPressed(void *callerThread)
+{
+ // Grab click count from non-static button, while the info is still valid
+ ButtonThread *thread = (ButtonThread *)callerThread;
+ thread->storeClickCount();
+
+ // Then handle later, in the usual way
+ btnEvent = BUTTON_EVENT_MULTI_PRESSED;
+}
+
+// Non-static method, runs during callback. Grabs info while still valid
+void ButtonThread::storeClickCount()
+{
+#ifdef BUTTON_PIN
+ multipressClickCount = userButton.getNumberClicks();
+#endif
+}
+
+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..07c7ccff7 100644
--- a/src/ButtonThread.h
+++ b/src/ButtonThread.h
@@ -1,35 +1,34 @@
-#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
-#ifdef BUTTON_PIN
- OneButton userButton;
+ 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_LONG_PRESSED,
+ };
+
+ ButtonThread();
+ int32_t runOnce() override;
+ void attachButtonInterrupts();
+ void detachButtonInterrupts();
+ void storeClickCount();
+
+ private:
+#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
+ static OneButton userButton; // Static - accessed from an interrupt
#endif
#ifdef BUTTON_PIN_ALT
OneButton userButtonAlt;
@@ -37,166 +36,22 @@ class ButtonThread : public concurrency::OSThread
#ifdef BUTTON_PIN_TOUCH
OneButton userButtonTouch;
#endif
- static bool shutdown_on_long_stop;
- public:
- static uint32_t longPressTime;
+ // set during IRQ
+ static volatile ButtonEventType btnEvent;
- // 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);
-#endif
+ // Store click count during callback, for later use
+ volatile int multipressClickCount = 0;
-#ifdef BUTTON_PIN_TOUCH
- userButtonTouch = OneButton(BUTTON_PIN_TOUCH, true, true);
- userButtonTouch.attachClick(touchPressed);
- wakeOnIrq(BUTTON_PIN_TOUCH, FALLING);
-#endif
- }
+ static void wakeOnIrq(int irq, int mode);
- 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
-
-#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 userButtonPressed() { btnEvent = BUTTON_EVENT_PRESSED; }
+ static void userButtonDoublePressed() { btnEvent = BUTTON_EVENT_DOUBLE_PRESSED; }
+ static void userButtonMultiPressed(void *callerThread); // Retrieve click count from non-static Onebutton while still valid
+ static void userButtonPressedLongStart();
+ static void userButtonPressedLongStop();
+ static void touchPressedLongStart() { btnEvent = BUTTON_EVENT_TOUCH_LONG_PRESSED; }
};
-} // namespace concurrency
\ No newline at end of file
+extern ButtonThread *buttonThread;
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/GPSStatus.h b/src/GPSStatus.h
index bcfb5f2eb..1245d5e5d 100644
--- a/src/GPSStatus.h
+++ b/src/GPSStatus.h
@@ -4,8 +4,6 @@
#include "configuration.h"
#include
-extern NodeDB nodeDB;
-
namespace meshtastic
{
@@ -55,7 +53,7 @@ class GPSStatus : public Status
#ifdef GPS_EXTRAVERBOSE
LOG_WARN("Using fixed latitude\n");
#endif
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum());
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
return node->position.latitude_i;
} else {
return p.latitude_i;
@@ -68,7 +66,7 @@ class GPSStatus : public Status
#ifdef GPS_EXTRAVERBOSE
LOG_WARN("Using fixed longitude\n");
#endif
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum());
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
return node->position.longitude_i;
} else {
return p.longitude_i;
@@ -81,27 +79,18 @@ class GPSStatus : public Status
#ifdef GPS_EXTRAVERBOSE
LOG_WARN("Using fixed altitude\n");
#endif
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(nodeDB.getNodeNum());
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
return node->position.altitude;
} else {
return p.altitude;
}
}
- uint32_t getDOP() const
- {
- return p.PDOP;
- }
+ uint32_t getDOP() const { return p.PDOP; }
- uint32_t getHeading() const
- {
- return p.ground_track;
- }
+ uint32_t getHeading() const { return p.ground_track; }
- uint32_t getNumSatellites() const
- {
- return p.sats_in_view;
- }
+ uint32_t getNumSatellites() const { return p.sats_in_view; }
bool matches(const GPSStatus *newStatus) const
{
@@ -149,4 +138,4 @@ class GPSStatus : public Status
} // namespace meshtastic
-extern meshtastic::GPSStatus *gpsStatus;
\ No newline at end of file
+extern meshtastic::GPSStatus *gpsStatus;
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 0a56a1ba2..d13fd6891 100644
--- a/src/Power.cpp
+++ b/src/Power.cpp
@@ -24,11 +24,13 @@
#include "nrfx_power.h"
#endif
-#ifdef DEBUG_HEAP_MQTT
+#if defined(DEBUG_HEAP_MQTT) && !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
#include "target_specific.h"
+#if !MESTASTIC_EXCLUDE_WIFI
#include
#endif
+#endif
#ifndef DELAY_FOREVER
#define DELAY_FOREVER portMAX_DELAY
@@ -54,6 +56,19 @@ static const adc_atten_t atten = ADC_ATTENUATION;
#endif
#endif // BATTERY_PIN && ARCH_ESP32
+#ifdef EXT_CHRG_DETECT
+#ifndef EXT_CHRG_DETECT_MODE
+static const uint8_t ext_chrg_detect_mode = INPUT;
+#else
+static const uint8_t ext_chrg_detect_mode = EXT_CHRG_DETECT_MODE;
+#endif
+#ifndef EXT_CHRG_DETECT_VALUE
+static const uint8_t ext_chrg_detect_value = HIGH;
+#else
+static const uint8_t ext_chrg_detect_value = EXT_CHRG_DETECT_VALUE;
+#endif
+#endif
+
#if HAS_TELEMETRY && !defined(ARCH_PORTDUINO)
INA260Sensor ina260Sensor;
INA219Sensor ina219Sensor;
@@ -127,8 +142,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
{
@@ -137,13 +150,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);
}
/**
@@ -164,7 +196,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
@@ -176,66 +209,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,28 +337,27 @@ class AnalogBatteryLevel : public HasBatteryLevel
/// Assume charging if we have a battery and external power is connected.
/// we can't be smart enough to say 'full'?
- virtual bool isCharging() override { return isBatteryConnect() && isVbusIn(); }
+ virtual bool isCharging() override
+ {
+#ifdef EXT_CHRG_DETECT
+ return digitalRead(EXT_CHRG_DETECT) == ext_chrg_detect_value;
+#else
+ return isBatteryConnect() && isVbusIn();
+#endif
+ }
private:
/// 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)
@@ -335,6 +411,9 @@ bool Power::analogInit()
#ifdef EXT_PWR_DETECT
pinMode(EXT_PWR_DETECT, INPUT);
#endif
+#ifdef EXT_CHRG_DETECT
+ pinMode(EXT_CHRG_DETECT, ext_chrg_detect_mode);
+#endif
#ifdef BATTERY_PIN
LOG_DEBUG("Using analog input %d for battery level\n", BATTERY_PIN);
@@ -358,8 +437,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);
@@ -368,13 +450,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
@@ -383,11 +468,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
@@ -402,11 +488,8 @@ bool Power::analogInit()
*/
bool Power::setup()
{
- bool found = axpChipInit();
+ bool found = axpChipInit() || analogInit();
- if (!found) {
- found = analogInit();
- }
enabled = found;
low_voltage_counter = 0;
@@ -415,19 +498,9 @@ bool Power::setup()
void Power::shutdown()
{
- screen->setOn(false);
-#if defined(USE_EINK) && defined(PIN_EINK_EN)
- digitalWrite(PIN_EINK_EN, LOW); // power off backlight first
-#endif
-
LOG_INFO("Shutting down\n");
-#ifdef HAS_PMU
- if (pmu_found == true) {
- PMU->setChargingLedMode(XPOWERS_CHG_LED_OFF);
- PMU->shutdown();
- }
-#elif defined(ARCH_NRF52) || defined(ARCH_ESP32)
+#if defined(ARCH_NRF52) || defined(ARCH_ESP32)
#ifdef PIN_LED1
ledOff(PIN_LED1);
#endif
@@ -435,7 +508,7 @@ void Power::shutdown()
ledOff(PIN_LED2);
#endif
#ifdef PIN_LED3
- ledOff(PIN_LED2);
+ ledOff(PIN_LED3);
#endif
doDeepSleep(DELAY_FOREVER, false);
#endif
@@ -457,11 +530,11 @@ 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);
}
}
@@ -526,10 +599,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) {
@@ -897,4 +971,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..4f42b36b5 100644
--- a/src/PowerFSM.cpp
+++ b/src/PowerFSM.cpp
@@ -8,6 +8,7 @@
* actions to be taken upon entering or exiting each state.
*/
#include "PowerFSM.h"
+#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "configuration.h"
@@ -16,6 +17,10 @@
#include "sleep.h"
#include "target_specific.h"
+#ifndef SLEEP_TIME
+#define SLEEP_TIME 30
+#endif
+
/// Should we behave as if we have AC power now?
static bool isPowered()
{
@@ -45,7 +50,7 @@ static void sdsEnter()
{
LOG_DEBUG("Enter state: SDS\n");
// FIXME - make sure GPS and LORA radio are off first - because we want close to zero current draw
- doDeepSleep(getConfiguredOrDefaultMs(config.power.sds_secs), false);
+ doDeepSleep(Default::getConfiguredOrDefaultMs(config.power.sds_secs), false);
}
extern Power *power;
@@ -80,7 +85,7 @@ static void lsIdle()
// If some other service would stall sleep, don't let sleep happen yet
if (doPreflightSleep()) {
// Briefly come out of sleep long enough to blink the led once every few seconds
- uint32_t sleepTime = 30;
+ uint32_t sleepTime = SLEEP_TIME;
setLed(false); // Never leave led on while in light sleep
esp_sleep_source_t wakeCause2 = doLightSleep(sleepTime * 1000LL);
@@ -102,9 +107,7 @@ static void lsIdle()
break;
default:
- // We woke for some other reason (button press, device interrupt)
- // uint64_t status = esp_sleep_get_ext1_wakeup_status();
- LOG_INFO("wakeCause2 %d\n", wakeCause2);
+ // We woke for some other reason (button press, device IRQ interrupt)
#ifdef BUTTON_PIN
bool pressed = !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
@@ -182,10 +185,12 @@ static void powerEnter()
screen->setOn(true);
setBluetoothEnable(true);
// within enter() the function getState() returns the state we came from
- if (strcmp(powerFSM.getState()->name, "BOOT") != 0 && strcmp(powerFSM.getState()->name, "POWER") != 0 &&
+
+ // Mothballed: print change of power-state to device screen
+ /* if (strcmp(powerFSM.getState()->name, "BOOT") != 0 && strcmp(powerFSM.getState()->name, "POWER") != 0 &&
strcmp(powerFSM.getState()->name, "DARK") != 0) {
screen->print("Powered...\n");
- }
+ }*/
}
}
@@ -202,8 +207,10 @@ static void powerExit()
{
screen->setOn(true);
setBluetoothEnable(true);
- if (!isPowered())
- screen->print("Unpowered...\n");
+
+ // Mothballed: print change of power-state to device screen
+ /*if (!isPowered())
+ screen->print("Unpowered...\n");*/
}
static void onEnter()
@@ -246,6 +253,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();
@@ -341,13 +349,10 @@ void PowerFSM_setup()
powerFSM.add_transition(&stateDARK, &stateDARK, EVENT_CONTACT_FROM_PHONE, NULL, "Contact from phone");
powerFSM.add_timed_transition(&stateON, &stateDARK,
- getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
+ Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
"Screen-on timeout");
powerFSM.add_timed_transition(&statePOWER, &stateDARK,
- getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
- "Screen-on timeout");
- powerFSM.add_timed_transition(&stateDARK, &stateDARK,
- getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
+ Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
"Screen-on timeout");
// We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally)
@@ -357,12 +362,26 @@ void PowerFSM_setup()
// modules
if ((isRouter || config.power.is_power_saving) && !isTrackerOrSensor) {
powerFSM.add_timed_transition(&stateNB, &stateLS,
- getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs), NULL,
+ Default::getConfiguredOrDefaultMs(config.power.min_wake_secs, default_min_wake_secs), NULL,
"Min wake timeout");
- powerFSM.add_timed_transition(&stateDARK, &stateLS,
- getConfiguredOrDefaultMs(config.power.wait_bluetooth_secs, default_wait_bluetooth_secs),
- NULL, "Bluetooth timeout");
+
+ // If ESP32 and using power-saving, timer mover from DARK to light-sleep
+ // Also serves purpose of the old DARK to DARK transition(?) See https://github.com/meshtastic/firmware/issues/3517
+ powerFSM.add_timed_transition(
+ &stateDARK, &stateLS,
+ Default::getConfiguredOrDefaultMs(config.power.wait_bluetooth_secs, default_wait_bluetooth_secs), NULL,
+ "Bluetooth timeout");
+ } else {
+ // If ESP32, but not using power-saving, check periodically if config has drifted out of stateDark
+ powerFSM.add_timed_transition(&stateDARK, &stateDARK,
+ Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs),
+ NULL, "Screen-on timeout");
}
+#else
+ // If not ESP32, light-sleep not used. Check periodically if config has drifted out of stateDark
+ powerFSM.add_timed_transition(&stateDARK, &stateDARK,
+ Default::getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL,
+ "Screen-on timeout");
#endif
powerFSM.run_machine(); // run one iteration of the state machine, so we run our on enter tasks for the initial DARK state
diff --git a/src/PowerFSMThread.h b/src/PowerFSMThread.h
index 541522f43..fb640dd8b 100644
--- a/src/PowerFSMThread.h
+++ b/src/PowerFSMThread.h
@@ -1,3 +1,4 @@
+#include "Default.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "concurrency/OSThread.h"
@@ -21,19 +22,19 @@ 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()) {
timeLastPowered = millis();
} else if (config.power.on_battery_shutdown_after_secs > 0 && config.power.on_battery_shutdown_after_secs != UINT32_MAX &&
millis() > (timeLastPowered +
- getConfiguredOrDefaultMs(
+ Default::getConfiguredOrDefaultMs(
config.power.on_battery_shutdown_after_secs))) { // shutdown after 30 minutes unpowered
powerFSM.trigger(EVENT_SHUTDOWN);
}
- return 10;
+ return 100;
}
};
diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp
index 2d73c7c9b..e09e5fe30 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;
@@ -87,7 +99,7 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...)
// If we are the first message on a report, include the header
if (!isContinuationMessage) {
- uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice);
+ uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // display local time on logfile
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
// hms += tz.tz_dsttime * SEC_PER_HOUR;
@@ -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) {
@@ -163,11 +182,11 @@ size_t RedirectablePrint::log(const char *logLevel, const char *format, ...)
void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16_t len)
{
const char alphabet[17] = "0123456789abcdef";
- log(logLevel, " +------------------------------------------------+ +----------------+\n");
- log(logLevel, " |.0 .1 .2 .3 .4 .5 .6 .7 .8 .9 .a .b .c .d .e .f | | ASCII |\n");
+ log(logLevel, " +------------------------------------------------+ +----------------+\n");
+ log(logLevel, " |.0 .1 .2 .3 .4 .5 .6 .7 .8 .9 .a .b .c .d .e .f | | ASCII |\n");
for (uint16_t i = 0; i < len; i += 16) {
if (i % 128 == 0)
- log(logLevel, " +------------------------------------------------+ +----------------+\n");
+ log(logLevel, " +------------------------------------------------+ +----------------+\n");
char s[] = "| | | |\n";
uint8_t ix = 1, iy = 52;
for (uint8_t j = 0; j < 16; j++) {
@@ -189,7 +208,7 @@ void RedirectablePrint::hexDump(const char *logLevel, unsigned char *buf, uint16
log(logLevel, ".");
log(logLevel, s);
}
- log(logLevel, " +------------------------------------------------+ +----------------+\n");
+ log(logLevel, " +------------------------------------------------+ +----------------+\n");
}
std::string RedirectablePrint::mt_sprintf(const std::string fmt_str, ...)
diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp
index ed217c3ed..e17c8f99e 100644
--- a/src/SerialConsole.cpp
+++ b/src/SerialConsole.cpp
@@ -3,7 +3,11 @@
#include "PowerFSM.h"
#include "configuration.h"
+#ifdef RP2040_SLOW_CLOCK
+#define Port Serial2
+#else
#define Port Serial
+#endif
// Defaulting to the formerly removed phone_timeout_secs value of 15 minutes
#define SERIAL_CONNECTION_TIMEOUT (15 * 60) * 1000UL
@@ -31,6 +35,10 @@ SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port), con
canWrite = false; // We don't send packets to our port until it has talked to us first
// setDestination(&noopPrint); for testing, try turning off 'all' debug output and see what leaks
+#ifdef RP2040_SLOW_CLOCK
+ Port.setTX(SERIAL2_TX);
+ Port.setRX(SERIAL2_RX);
+#endif
Port.begin(SERIAL_BAUD);
#if defined(ARCH_NRF52) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) || defined(ARCH_RP2040)
time_t timeout = millis();
@@ -64,7 +72,7 @@ bool SerialConsole::checkIsConnected()
/**
* we override this to notice when we've received a protobuf over the serial
- * stream. Then we shunt off debug serial output.
+ * stream. Then we shut off debug serial output.
*/
bool SerialConsole::handleToRadio(const uint8_t *buf, size_t len)
{
diff --git a/src/configuration.h b/src/configuration.h
index cb7ee218b..701e07a32 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -74,6 +74,13 @@ along with this program. If not, see .
#define RTC_DATA_ATTR
#endif
+// -----------------------------------------------------------------------------
+// Regulatory overrides for producing regional builds
+// -----------------------------------------------------------------------------
+
+// Define if region should override user saved region
+// #define LORA_REGIONCODE meshtastic_Config_LoRaConfig_RegionCode_SG_923
+
// -----------------------------------------------------------------------------
// Feature toggles
// -----------------------------------------------------------------------------
@@ -111,6 +118,7 @@ along with this program. If not, see .
#define MCP9808_ADDR 0x18
#define INA_ADDR 0x40
#define INA_ADDR_ALTERNATE 0x41
+#define INA_ADDR_WAVESHARE_UPS 0x43
#define INA3221_ADDR 0x42
#define QMC6310_ADDR 0x1C
#define QMI8658_ADDR 0x6B
@@ -127,6 +135,7 @@ along with this program. If not, see .
#define MPU6050_ADDR 0x68
#define LIS3DH_ADR 0x18
#define BMA423_ADDR 0x19
+#define LSM6DS3_ADDR 0x6A
// -----------------------------------------------------------------------------
// LED
@@ -136,14 +145,19 @@ along with this program. If not, see .
// -----------------------------------------------------------------------------
// Security
// -----------------------------------------------------------------------------
-
#define ATECC608B_ADDR 0x35
+// -----------------------------------------------------------------------------
+// IO Expander
+// -----------------------------------------------------------------------------
+#define TCA9555_ADDR 0x26
+
// -----------------------------------------------------------------------------
// GPS
// -----------------------------------------------------------------------------
-
+#ifndef GPS_BAUDRATE
#define GPS_BAUDRATE 9600
+#endif
#ifndef GPS_THREAD_INTERVAL
#define GPS_THREAD_INTERVAL 200
@@ -159,6 +173,14 @@ along with this program. If not, see .
also enable HAS_ option not specifically disabled by variant.h */
#include "architecture.h"
+#ifndef DEFAULT_REBOOT_SECONDS
+#define DEFAULT_REBOOT_SECONDS 7
+#endif
+
+#ifndef DEFAULT_SHUTDOWN_SECONDS
+#define DEFAULT_SHUTDOWN_SECONDS 2
+#endif
+
/* Step #3: mop up with disabled values for HAS_ options not handled by the above two */
#ifndef HAS_WIFI
@@ -209,4 +231,65 @@ along with this program. If not, see .
#ifndef HW_VENDOR
#error HW_VENDOR must be defined
+#endif
+
+// -----------------------------------------------------------------------------
+// Global switches to turn off features for a minimized build
+// -----------------------------------------------------------------------------
+
+// #define MESHTASTIC_MINIMIZE_BUILD 1
+#ifdef MESHTASTIC_MINIMIZE_BUILD
+#define MESHTASTIC_EXCLUDE_MODULES 1
+#define MESHTASTIC_EXCLUDE_WIFI 1
+#define MESHTASTIC_EXCLUDE_BLUETOOTH 1
+#define MESHTASTIC_EXCLUDE_GPS 1
+#define MESHTASTIC_EXCLUDE_SCREEN 1
+#define MESHTASTIC_EXCLUDE_MQTT 1
+#endif
+
+// Turn off all optional modules
+#ifdef MESHTASTIC_EXCLUDE_MODULES
+#define MESHTASTIC_EXCLUDE_AUDIO 1
+#define MESHTASTIC_EXCLUDE_DETECTIONSENSOR 1
+#define MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR 1
+#define MESHTASTIC_EXCLUDE_EXTERNALNOTIFICATION 1
+#define MESHTASTIC_EXCLUDE_PAXCOUNTER 1
+#define MESHTASTIC_EXCLUDE_POWER_TELEMETRY 1
+#define MESHTASTIC_EXCLUDE_RANGETEST 1
+#define MESHTASTIC_EXCLUDE_REMOTEHARDWARE 1
+#define MESHTASTIC_EXCLUDE_STOREFORWARD 1
+#define MESHTASTIC_EXCLUDE_ATAK 1
+#define MESHTASTIC_EXCLUDE_CANNEDMESSAGES 1
+#define MESHTASTIC_EXCLUDE_NEIGHBORINFO 1
+#define MESHTASTIC_EXCLUDE_TRACEROUTE 1
+#define MESHTASTIC_EXCLUDE_WAYPOINT 1
+#define MESHTASTIC_EXCLUDE_INPUTBROKER 1
+#define MESHTASTIC_EXCLUDE_SERIAL 1
+#endif
+
+// // Turn off wifi even if HW supports wifi (webserver relies on wifi and is also disabled)
+#ifdef MESHTASTIC_EXCLUDE_WIFI
+#define MESHTASTIC_EXCLUDE_WEBSERVER 1
+#undef HAS_WIFI
+#define HAS_WIFI 0
+#endif
+
+// // Turn off Bluetooth
+#ifdef MESHTASTIC_EXCLUDE_BLUETOOTH
+#undef HAS_BLUETOOTH
+#define HAS_BLUETOOTH 0
+#endif
+
+// // Turn off GPS
+#ifdef MESHTASTIC_EXCLUDE_GPS
+#undef HAS_GPS
+#define HAS_GPS 0
+#undef MESHTASTIC_EXCLUDE_RANGETEST
+#define MESHTASTIC_EXCLUDE_RANGETEST 1
+#endif
+
+// Turn off Screen
+#ifdef MESHTASTIC_EXCLUDE_SCREEN
+#undef HAS_SCREEN
+#define HAS_SCREEN 0
#endif
\ No newline at end of file
diff --git a/src/detect/LoRaRadioType.h b/src/detect/LoRaRadioType.h
new file mode 100644
index 000000000..eadd92e64
--- /dev/null
+++ b/src/detect/LoRaRadioType.h
@@ -0,0 +1,5 @@
+#pragma once
+
+enum LoRaRadioType { NO_RADIO, STM32WLx_RADIO, SIM_RADIO, RF95_RADIO, SX1262_RADIO, SX1268_RADIO, LLCC68_RADIO, SX1280_RADIO };
+
+extern LoRaRadioType radioType;
\ No newline at end of file
diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp
index bf206c190..149bb95f0 100644
--- a/src/detect/ScanI2C.cpp
+++ b/src/detect/ScanI2C.cpp
@@ -36,8 +36,8 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
{
- ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423};
- return firstOfOrNONE(3, types);
+ ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3};
+ return firstOfOrNONE(4, types);
}
ScanI2C::FoundDevice ScanI2C::find(ScanI2C::DeviceType) const
diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h
index 2b4b8a735..c8fcfee10 100644
--- a/src/detect/ScanI2C.h
+++ b/src/detect/ScanI2C.h
@@ -23,6 +23,7 @@ class ScanI2C
BME_680,
BME_280,
BMP_280,
+ BMP_085,
INA260,
INA219,
INA3221,
@@ -37,6 +38,9 @@ class ScanI2C
MPU6050,
LIS3DH,
BMA423,
+ BQ24295,
+ LSM6DS3,
+ TCA9555,
#ifdef HAS_NCP5623
NCP5623,
#endif
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index b3873dc91..13c2f4609 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);
@@ -174,8 +183,13 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL)
case ATECC608B_ADDR:
- type = ATECC608B;
- if (atecc.begin(addr.address) == true) {
+#ifdef RP2040_SLOW_CLOCK
+ if (atecc.begin(addr.address, Wire, Serial2) == true)
+#else
+ if (atecc.begin(addr.address) == true)
+#endif
+
+ {
LOG_INFO("ATECC608B initialized\n");
} else {
LOG_WARN("ATECC608B initialization failed\n");
@@ -233,6 +247,10 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
LOG_INFO("BME-280 sensor found at address 0x%x\n", (uint8_t)addr.address);
type = BME_280;
break;
+ case 0x55:
+ LOG_INFO("BMP-085 or BMP-180 sensor found at address 0x%x\n", (uint8_t)addr.address);
+ type = BMP_085;
+ break;
default:
LOG_INFO("BMP-280 sensor found at address 0x%x\n", (uint8_t)addr.address);
type = BMP_280;
@@ -241,6 +259,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
case INA_ADDR:
case INA_ADDR_ALTERNATE:
+ case INA_ADDR_WAVESHARE_UPS:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xFE), 2);
LOG_DEBUG("Register MFG_UID: 0x%x\n", registerValue);
if (registerValue == 0x5449) {
@@ -274,12 +293,31 @@ void ScanI2CTwoWire::scanPort(I2CPort port)
SCAN_SIMPLE_CASE(LPS22HB_ADDR, LPS22HB, "LPS22HB sensor found\n")
SCAN_SIMPLE_CASE(QMC6310_ADDR, QMC6310, "QMC6310 Highrate 3-Axis magnetic sensor found\n")
- SCAN_SIMPLE_CASE(QMI8658_ADDR, QMI8658, "QMI8658 Highrate 6-Axis inertial measurement sensor found\n")
+
+ case QMI8658_ADDR:
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0A), 1); // get ID
+ if (registerValue == 0xC0) {
+ type = BQ24295;
+ LOG_INFO("BQ24295 PMU found\n");
+ break;
+ }
+ registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 1); // get ID
+ if (registerValue == 0x6A) {
+ type = LSM6DS3;
+ LOG_INFO("LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address);
+ } else {
+ type = QMI8658;
+ LOG_INFO("QMI8658 Highrate 6-Axis inertial measurement sensor found\n");
+ }
+ break;
+
SCAN_SIMPLE_CASE(QMC5883L_ADDR, QMC5883L, "QMC5883L Highrate 3-Axis magnetic sensor found\n")
SCAN_SIMPLE_CASE(PMSA0031_ADDR, PMSA0031, "PMSA0031 air quality sensor found\n")
SCAN_SIMPLE_CASE(MPU6050_ADDR, MPU6050, "MPU6050 accelerometer found\n");
SCAN_SIMPLE_CASE(BMA423_ADDR, BMA423, "BMA423 accelerometer found\n");
+ SCAN_SIMPLE_CASE(LSM6DS3_ADDR, LSM6DS3, "LSM6DS3 accelerometer found at address 0x%x\n", (uint8_t)addr.address);
+ SCAN_SIMPLE_CASE(TCA9555_ADDR, TCA9555, "TCA9555 I2C expander found\n");
default:
LOG_INFO("Device found at address 0x%x was not able to be enumerated\n", addr.address);
diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index af622e3d8..0d0bfd9a2 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -1,12 +1,18 @@
+#include "configuration.h"
+#if !MESHTASTIC_EXCLUDE_GPS
+#include "Default.h"
#include "GPS.h"
#include "NodeDB.h"
#include "RTC.h"
-#include "configuration.h"
+
#include "main.h" // pmu_found
#include "sleep.h"
+
+#include "cas.h"
#include "ubx.h"
#ifdef ARCH_PORTDUINO
+#include "PortduinoGlue.h"
#include "meshUtils.h"
#include
#endif
@@ -15,11 +21,8 @@
#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;
-#elif defined(ARCH_RASPBERRY_PI)
-// need a translation layer to make _serial_gps work with pigpio https://abyz.me.uk/rpi/pigpio/cif.html#serOpen
-HardwareSerial *GPS::_serial_gps = NULL;
#else
HardwareSerial *GPS::_serial_gps = NULL;
#endif
@@ -50,6 +53,28 @@ void GPS::UBXChecksum(uint8_t *message, size_t length)
message[length - 1] = CK_B;
}
+// Calculate the checksum for a CAS packet
+void GPS::CASChecksum(uint8_t *message, size_t length)
+{
+ uint32_t cksum = ((uint32_t)message[5] << 24); // Message ID
+ cksum += ((uint32_t)message[4]) << 16; // Class
+ cksum += message[2]; // Payload Len
+
+ // Iterate over the payload as a series of uint32_t's and
+ // accumulate the cksum
+ uint32_t *payload = (uint32_t *)(message + 6);
+ for (size_t i = 0; i < (length - 10) / 4; i++) {
+ uint32_t p = payload[i];
+ cksum += p;
+ }
+
+ // Place the checksum values in the message
+ message[length - 4] = (cksum & 0xFF);
+ message[length - 3] = (cksum & (0xFF << 8)) >> 8;
+ message[length - 2] = (cksum & (0xFF << 16)) >> 16;
+ message[length - 1] = (cksum & (0xFF << 24)) >> 24;
+}
+
// Function to create a ublox packet for editing in memory
uint8_t GPS::makeUBXPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg)
{
@@ -71,6 +96,41 @@ uint8_t GPS::makeUBXPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_siz
return (payload_size + 8);
}
+// Function to create a CAS packet for editing in memory
+uint8_t GPS::makeCASPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg)
+{
+ // General CAS structure
+ // | H1 | H2 | payload_len | cls | msg | Payload ... | Checksum |
+ // Size: | 1 | 1 | 2 | 1 | 1 | payload_len | 4 |
+ // Pos: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 ... | 6 + payload_len ... |
+ // |------|------|-------------|------|------|------|--------------|---------------------------|
+ // | 0xBA | 0xCE | 0xXX | 0xXX | 0xXX | 0xXX | 0xXX | 0xXX ... | 0xXX | 0xXX | 0xXX | 0xXX |
+
+ // Construct the CAS packet
+ UBXscratch[0] = 0xBA; // header 1 (0xBA)
+ UBXscratch[1] = 0xCE; // header 2 (0xCE)
+ UBXscratch[2] = payload_size; // length 1
+ UBXscratch[3] = 0; // length 2
+ UBXscratch[4] = class_id; // class
+ UBXscratch[5] = msg_id; // id
+
+ UBXscratch[6 + payload_size] = 0x00; // Checksum
+ UBXscratch[7 + payload_size] = 0x00;
+ UBXscratch[8 + payload_size] = 0x00;
+ UBXscratch[9 + payload_size] = 0x00;
+
+ for (int i = 0; i < payload_size; i++) {
+ UBXscratch[6 + i] = pgm_read_byte(&msg[i]);
+ }
+ CASChecksum(UBXscratch, (payload_size + 10));
+
+#if defined(GPS_DEBUG) && defined(DEBUG_PORT)
+ LOG_DEBUG("Constructed CAS packet: \n");
+ DEBUG_PORT.hexDump(MESHTASTIC_LOG_LEVEL_DEBUG, UBXscratch, payload_size + 10);
+#endif
+ return (payload_size + 10);
+}
+
GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
{
uint8_t buffer[768] = {0};
@@ -80,6 +140,7 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
while (millis() < startTimeout) {
if (_serial_gps->available()) {
b = _serial_gps->read();
+
#ifdef GPS_DEBUG
LOG_DEBUG("%02X", (char *)buffer);
#endif
@@ -103,6 +164,67 @@ GPS_RESPONSE GPS::getACK(const char *message, uint32_t waitMillis)
return GNSS_RESPONSE_NONE;
}
+GPS_RESPONSE GPS::getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis)
+{
+ uint32_t startTime = millis();
+ uint8_t buffer[CAS_ACK_NACK_MSG_SIZE] = {0};
+ uint8_t bufferPos = 0;
+
+ // CAS-ACK-(N)ACK structure
+ // | H1 | H2 | Payload Len | cls | msg | Payload | Checksum (4) |
+ // | | | | | | Cls | Msg | Reserved | |
+ // |------|------|-------------|------|------|------|------|-------------|---------------------------|
+ // ACK-NACK| 0xBA | 0xCE | 0x04 | 0x00 | 0x05 | 0x00 | 0xXX | 0xXX | 0x00 | 0x00 | 0xXX | 0xXX | 0xXX | 0xXX |
+ // ACK-ACK | 0xBA | 0xCE | 0x04 | 0x00 | 0x05 | 0x01 | 0xXX | 0xXX | 0x00 | 0x00 | 0xXX | 0xXX | 0xXX | 0xXX |
+
+ while (millis() - startTime < waitMillis) {
+ if (_serial_gps->available()) {
+ buffer[bufferPos++] = _serial_gps->read();
+
+ // keep looking at the first two bytes of buffer until
+ // we have found the CAS frame header (0xBA, 0xCE), if not
+ // keep reading bytes until we find a frame header or we run
+ // out of time.
+ if ((bufferPos == 2) && !(buffer[0] == 0xBA && buffer[1] == 0xCE)) {
+ buffer[0] = buffer[1];
+ buffer[1] = 0;
+ bufferPos = 1;
+ }
+ }
+
+ // we have read all the bytes required for the Ack/Nack (14-bytes)
+ // and we must have found a frame to get this far
+ if (bufferPos == sizeof(buffer) - 1) {
+ uint8_t msg_cls = buffer[4]; // message class should be 0x05
+ uint8_t msg_msg_id = buffer[5]; // message id should be 0x00 or 0x01
+ uint8_t payload_cls = buffer[6]; // payload class id
+ uint8_t payload_msg = buffer[7]; // payload message id
+
+ // Check for an ACK-ACK for the specified class and message id
+ if ((msg_cls == 0x05) && (msg_msg_id == 0x01) && payload_cls == class_id && payload_msg == msg_id) {
+#ifdef GPS_DEBUG
+ LOG_INFO("Got ACK for class %02X message %02X in %d millis.\n", class_id, msg_id, millis() - startTime);
+#endif
+ return GNSS_RESPONSE_OK;
+ }
+
+ // Check for an ACK-NACK for the specified class and message id
+ if ((msg_cls == 0x05) && (msg_msg_id == 0x00) && payload_cls == class_id && payload_msg == msg_id) {
+#ifdef GPS_DEBUG
+ LOG_WARN("Got NACK for class %02X message %02X in %d millis.\n", class_id, msg_id, millis() - startTime);
+#endif
+ return GNSS_RESPONSE_NAK;
+ }
+
+ // This isn't the frame we are looking for, clear the buffer
+ // and try again until we run out of time.
+ memset(buffer, 0x0, sizeof(buffer));
+ bufferPos = 0;
+ }
+ }
+ return GNSS_RESPONSE_NONE;
+}
+
GPS_RESPONSE GPS::getACK(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis)
{
uint8_t b;
@@ -253,17 +375,10 @@ 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;
- }
-#endif
+
if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
LOG_DEBUG("Probing for GPS at %d \n", serialSpeeds[speedSelect]);
gnssModel = probe(serialSpeeds[speedSelect]);
@@ -281,7 +396,7 @@ bool GPS::setup()
gnssModel = GNSS_MODEL_UNKNOWN;
}
#else
- gnssModel = GNSS_MODEL_UC6850;
+ gnssModel = GNSS_MODEL_UC6580;
#endif
if (gnssModel == GNSS_MODEL_MTK) {
@@ -299,10 +414,68 @@ 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) {
+ } else if (gnssModel == GNSS_MODEL_MTK_L76B) {
+ // Waveshare Pico-GPS hat uses the L76B with 9600 baud
+ // Initialize the L76B Chip, use GPS + GLONASS
+ // See note in L76_Series_GNSS_Protocol_Specification, chapter 3.29
+ _serial_gps->write("$PMTK353,1,1,0,0,0*2B\r\n");
+ // Above command will reset the GPS and takes longer before it will accept new commands
+ delay(1000);
+ // only ask for RMC and GGA (GNRMC and GNGGA)
+ // See note in L76_Series_GNSS_Protocol_Specification, chapter 2.1
+ _serial_gps->write("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n");
+ delay(250);
+ // Enable SBAS
+ _serial_gps->write("$PMTK301,2*2E\r\n");
+ delay(250);
+ // Enable PPS for 2D/3D fix only
+ _serial_gps->write("$PMTK285,3,100*3F\r\n");
+ delay(250);
+ // Switch to Fitness Mode, for running and walking purpose with low speed (<5 m/s)
+ _serial_gps->write("$PMTK886,1*29\r\n");
+ delay(250);
+ } else if (gnssModel == GNSS_MODEL_ATGM336H) {
+ // Set the intial configuration of the device - these _should_ work for most AT6558 devices
+ msglen = makeCASPacket(0x06, 0x07, sizeof(_message_CAS_CFG_NAVX_CONF), _message_CAS_CFG_NAVX_CONF);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACKCas(0x06, 0x07, 250) != GNSS_RESPONSE_OK) {
+ LOG_WARN("ATGM336H - Could not set Configuration");
+ }
- // use GPS + GLONASS
- _serial_gps->write("$CFGSYS,h15\r\n");
+ // Set the update frequence to 1Hz
+ msglen = makeCASPacket(0x06, 0x04, sizeof(_message_CAS_CFG_RATE_1HZ), _message_CAS_CFG_RATE_1HZ);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACKCas(0x06, 0x04, 250) != GNSS_RESPONSE_OK) {
+ LOG_WARN("ATGM336H - Could not set Update Frequency");
+ }
+
+ // Set the NEMA output messages
+ // Ask for only RMC and GGA
+ uint8_t fields[] = {CAS_NEMA_RMC, CAS_NEMA_GGA};
+ for (int i = 0; i < sizeof(fields); i++) {
+ // Construct a CAS-CFG-MSG packet
+ uint8_t cas_cfg_msg_packet[] = {0x4e, fields[i], 0x01, 0x00};
+ msglen = makeCASPacket(0x06, 0x01, sizeof(cas_cfg_msg_packet), cas_cfg_msg_packet);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACKCas(0x06, 0x01, 250) != GNSS_RESPONSE_OK) {
+ LOG_WARN("ATGM336H - Could not enable NMEA MSG: %d\n", fields[i]);
+ }
+ }
+ } 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)
@@ -310,125 +483,267 @@ bool GPS::setup()
// 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);
+ } 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, 0x09, 300) != GNSS_RESPONSE_OK) {
- LOG_WARN("Unable to save GNSS module configuration.\n");
- } else {
- LOG_INFO("GNSS module configuration saved!\n");
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable NMEA messages for M10 GPS RAM.\n");
}
+ delay(250);
+ // Next disable unwanted NMEA messages in BBR layer
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_NMEA_BBR), _message_VALSET_DISABLE_NMEA_BBR);
+ clearBuffer();
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable NMEA messages for M10 GPS BBR.\n");
+ }
+ delay(250);
+ // Disable Info txt messages in RAM layer
+ msglen =
+ makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_TXT_INFO_RAM), _message_VALSET_DISABLE_TXT_INFO_RAM);
+ clearBuffer();
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable Info messages for M10 GPS RAM.\n");
+ }
+ delay(250);
+ // Next disable Info txt messages in BBR layer
+ msglen =
+ makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_TXT_INFO_BBR), _message_VALSET_DISABLE_TXT_INFO_BBR);
+ clearBuffer();
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable Info messages for M10 GPS BBR.\n");
+ }
+ // Do M10 configuration for Power Management.
+
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_PM_RAM), _message_VALSET_PM_RAM);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable powersaving for M10 GPS RAM.\n");
+ }
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_PM_BBR), _message_VALSET_PM_BBR);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable powersaving for M10 GPS BBR.\n");
+ }
+
+ delay(250);
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_ITFM_RAM), _message_VALSET_ITFM_RAM);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable Jamming detection M10 GPS RAM.\n");
+ }
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_ITFM_BBR), _message_VALSET_ITFM_BBR);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable Jamming detection M10 GPS BBR.\n");
+ }
+
+ // Here is where the init commands should go to do further M10 initialization.
+ delay(250);
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_SBAS_RAM), _message_VALSET_DISABLE_SBAS_RAM);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable SBAS M10 GPS RAM.\n");
+ }
+ delay(750); // will cause a receiver restart so wait a bit
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_DISABLE_SBAS_BBR), _message_VALSET_DISABLE_SBAS_BBR);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to disable SBAS M10 GPS BBR.\n");
+ }
+ delay(750); // will cause a receiver restart so wait a bit
+ // Done with initialization, Now enable wanted NMEA messages in BBR layer so they will survive a periodic sleep.
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_ENABLE_NMEA_BBR), _message_VALSET_ENABLE_NMEA_BBR);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable messages for M10 GPS BBR.\n");
+ }
+ delay(250);
+ // Next enable wanted NMEA messages in RAM layer
+ msglen = makeUBXPacket(0x06, 0x8A, sizeof(_message_VALSET_ENABLE_NMEA_RAM), _message_VALSET_ENABLE_NMEA_RAM);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x8A, 300) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to enable messages for M10 GPS RAM.\n");
+ }
+ // As the M10 has no flash, the best we can do to preserve the config is to set it in RAM and BBR.
+ // BBR will survive a restart, and power off for a while, but modules with small backup
+ // batteries or super caps will not retain the config for a long power off time.
+ }
+ msglen = makeUBXPacket(0x06, 0x09, sizeof(_message_SAVE), _message_SAVE);
+ _serial_gps->write(UBXscratch, msglen);
+ if (getACK(0x06, 0x09, 2000) != GNSS_RESPONSE_OK) {
+ LOG_WARN("Unable to save GNSS module configuration.\n");
+ } else {
+ LOG_INFO("GNSS module configuration saved!\n");
}
}
didSerialInit = true;
@@ -436,6 +751,7 @@ bool GPS::setup()
notifyDeepSleepObserver.observe(¬ifyDeepSleep);
notifyGPSSleepObserver.observe(¬ifyGPSSleep);
+
return true;
}
@@ -479,17 +795,27 @@ void GPS::setGPSPower(bool on, bool standbyOnly, uint32_t sleepTime)
return;
}
#endif
-#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76K and clones
+#ifdef PIN_GPS_STANDBY // Specifically the standby pin for L76B, L76K and clones
if (on) {
- LOG_INFO("Waking GPS");
- digitalWrite(PIN_GPS_STANDBY, 1);
+ LOG_INFO("Waking GPS\n");
pinMode(PIN_GPS_STANDBY, OUTPUT);
+ // Some PCB's use an inverse logic due to a transistor driver
+ // Example for this is the Pico-Waveshare Lora+GPS HAT
+#ifdef PIN_GPS_STANDBY_INVERTED
+ digitalWrite(PIN_GPS_STANDBY, 0);
+#else
+ digitalWrite(PIN_GPS_STANDBY, 1);
+#endif
return;
} else {
- LOG_INFO("GPS entering sleep");
+ LOG_INFO("GPS entering sleep\n");
// notifyGPSSleep.notifyObservers(NULL);
- digitalWrite(PIN_GPS_STANDBY, 0);
pinMode(PIN_GPS_STANDBY, OUTPUT);
+#ifdef PIN_GPS_STANDBY_INVERTED
+ digitalWrite(PIN_GPS_STANDBY, 1);
+#else
+ digitalWrite(PIN_GPS_STANDBY, 0);
+#endif
return;
}
#endif
@@ -497,10 +823,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 {
@@ -550,15 +883,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
}
}
@@ -566,11 +904,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 Default::getConfiguredOrDefaultMs(t, default_broadcast_interval_secs);
}
/** Get how long we should sleep between aqusition attempts in msecs
@@ -580,13 +919,13 @@ 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)
return t; // already maxint
- return t * 1000;
+ return Default::getConfiguredOrDefaultMs(t, default_gps_update_interval);
}
void GPS::publishUpdate()
@@ -601,21 +940,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
@@ -623,7 +965,7 @@ int32_t GPS::runOnce()
LOG_WARN("GPS FactoryReset requested\n");
if (gps->factoryReset()) { // If we don't succeed try again next time
devicestate.did_gps_reset = true;
- nodeDB.saveToDisk(SEGMENT_DEVICESTATE);
+ nodeDB->saveToDisk(SEGMENT_DEVICESTATE);
}
}
GPSInitFinished = true;
@@ -638,12 +980,12 @@ 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");
devicestate.did_gps_reset = false;
- nodeDB.saveDeviceStateToDisk();
+ nodeDB->saveDeviceStateToDisk();
return disable(); // Stop the GPS thread as it can do nothing useful until next reboot.
}
}
@@ -651,7 +993,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
@@ -753,10 +1096,18 @@ GnssModel_t GPS::probe(int serialSpeed)
uint8_t buffer[768] = {0};
delay(100);
- // Close all NMEA sentences , Only valid for MTK platform
+ // Close all NMEA sentences, valid for L76K, ATGM336H (and likely other AT6558 devices)
_serial_gps->write("$PCAS03,0,0,0,0,0,0,0,0,0,0,,,0,0*02\r\n");
delay(20);
+ // Get version information
+ clearBuffer();
+ _serial_gps->write("$PCAS06,1*1A\r\n");
+ if (getACK("$GPTXT,01,01,02,HW=ATGM336H", 500) == GNSS_RESPONSE_OK) {
+ LOG_INFO("ATGM336H GNSS init succeeded, using ATGM336H Module\n");
+ return GNSS_MODEL_ATGM336H;
+ }
+
// Get version information
clearBuffer();
_serial_gps->write("$PCAS06,0*1B\r\n");
@@ -765,6 +1116,18 @@ GnssModel_t GPS::probe(int serialSpeed)
return GNSS_MODEL_MTK;
}
+ // Close all NMEA sentences, valid for L76B MTK platform (Waveshare Pico GPS)
+ _serial_gps->write("$PMTK514,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*2E\r\n");
+ delay(20);
+
+ // Get version information
+ clearBuffer();
+ _serial_gps->write("$PMTK605*31\r\n");
+ if (getACK("Quectel-L76B", 500) == GNSS_RESPONSE_OK) {
+ LOG_INFO("L76B GNSS init succeeded, using L76B GNSS Module\n");
+ return GNSS_MODEL_MTK_L76B;
+ }
+
uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
UBXChecksum(cfg_rate, sizeof(cfg_rate));
clearBuffer();
@@ -850,9 +1213,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;
@@ -877,7 +1240,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
@@ -892,6 +1255,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;
@@ -903,8 +1270,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
@@ -924,8 +1291,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
@@ -942,7 +1309,6 @@ GPS *GPS::createGps()
LOG_DEBUG("Using GPIO%d for GPS RX\n", new_gps->rx_gpio);
LOG_DEBUG("Using GPIO%d for GPS TX\n", new_gps->tx_gpio);
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, new_gps->rx_gpio, new_gps->tx_gpio);
-
#else
_serial_gps->begin(GPS_BAUDRATE);
#endif
@@ -970,8 +1336,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
@@ -1001,7 +1367,21 @@ bool GPS::factoryReset()
// byte _message_CFG_RST_COLDSTART[] = {0xB5, 0x62, 0x06, 0x04, 0x04, 0x00, 0xFF, 0xB9, 0x00, 0x00, 0xC6, 0x8B};
// _serial_gps->write(_message_CFG_RST_COLDSTART, sizeof(_message_CFG_RST_COLDSTART));
// delay(1000);
+ } else if (gnssModel == GNSS_MODEL_MTK) {
+ // send the CAS10 to perform a factory restart of the device (and other device that support PCAS statements)
+ LOG_INFO("GNSS Factory Reset via PCAS10,3\n");
+ _serial_gps->write("$PCAS10,3*1F\r\n");
+ delay(100);
+ } else if (gnssModel == GNSS_MODEL_ATGM336H) {
+ LOG_INFO("Factory Reset via CAS-CFG-RST\n");
+ uint8_t msglen = makeCASPacket(0x06, 0x02, sizeof(_message_CAS_CFG_RST_FACTORY), _message_CAS_CFG_RST_FACTORY);
+ _serial_gps->write(UBXscratch, msglen);
+ delay(100);
} else {
+ // fire this for good measure, if we have an L76B - won't harm other devices.
+ _serial_gps->write("$PMTK104*37\r\n");
+ // No PMTK_ACK for this command.
+ delay(100);
// send the UBLOX Factory Reset Command regardless of detect state, something is very wrong, just assume it's UBLOX.
// Factory Reset
byte _message_reset[] = {0xB5, 0x62, 0x06, 0x09, 0x0D, 0x00, 0xFF, 0xFB, 0x00, 0x00, 0x00,
@@ -1061,7 +1441,7 @@ bool GPS::lookForLocation()
#ifndef TINYGPS_OPTION_NO_STATISTICS
if (reader.failedChecksum() > lastChecksumFailCount) {
- LOG_WARN("Warning, %u new GPS checksum failures, for a total of %u.\n", reader.failedChecksum() - lastChecksumFailCount,
+ LOG_WARN("%u new GPS checksum failures, for a total of %u.\n", reader.failedChecksum() - lastChecksumFailCount,
reader.failedChecksum());
lastChecksumFailCount = reader.failedChecksum();
}
@@ -1069,7 +1449,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
@@ -1086,6 +1466,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
@@ -1098,10 +1482,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();
@@ -1160,7 +1540,7 @@ bool GPS::lookForLocation()
t.tm_mon = reader.date.month() - 1;
t.tm_year = reader.date.year() - 1900;
t.tm_isdst = false;
- p.timestamp = mktime(&t);
+ p.timestamp = gm_mktime(&t);
// Nice to have, if available
if (reader.satellites.isUpdated()) {
@@ -1251,4 +1631,18 @@ int32_t GPS::disable()
setAwake(false);
return INT32_MAX;
-}
\ No newline at end of file
+}
+
+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();
+ }
+}
+#endif // Exclude GPS
\ No newline at end of file
diff --git a/src/gps/GPS.h b/src/gps/GPS.h
index d52c79182..77c6c0269 100644
--- a/src/gps/GPS.h
+++ b/src/gps/GPS.h
@@ -1,4 +1,6 @@
#pragma once
+#include "configuration.h"
+#if !MESHTASTIC_EXCLUDE_GPS
#include "GPSStatus.h"
#include "Observer.h"
@@ -21,10 +23,12 @@ struct uBloxGnssModelInfo {
};
typedef enum {
+ GNSS_MODEL_ATGM336H,
GNSS_MODEL_MTK,
GNSS_MODEL_UBLOX,
- GNSS_MODEL_UC6850,
+ GNSS_MODEL_UC6580,
GNSS_MODEL_UNKNOWN,
+ GNSS_MODEL_MTK_L76B
} GnssModel_t;
typedef enum {
@@ -70,7 +74,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
@@ -92,26 +96,55 @@ class GPS : private concurrency::OSThread
public:
/** If !NULL we will use this serial port to construct our GPS */
+#if defined(RPI_PICO_WAVESHARE)
+ static SerialUART *_serial_gps;
+#else
static HardwareSerial *_serial_gps;
-
+#endif
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[];
+
+ // CASIC commands for ATGM336H
+ static const uint8_t _message_CAS_CFG_RST_FACTORY[];
+ static const uint8_t _message_CAS_CFG_NAVX_CONF[];
+ static const uint8_t _message_CAS_CFG_RATE_1HZ[];
+
meshtastic_Position p = meshtastic_Position_init_default;
GPS() : concurrency::OSThread("GPS") {}
@@ -132,6 +165,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,13 +179,14 @@ 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();
// Create a ublox packet for editing in memory
uint8_t makeUBXPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg);
+ uint8_t makeCASPacket(uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg);
// scratch space for creating ublox packets
uint8_t UBXscratch[250] = {0};
@@ -160,6 +197,8 @@ class GPS : private concurrency::OSThread
GPS_RESPONSE getACK(uint8_t c, uint8_t i, uint32_t waitMillis);
GPS_RESPONSE getACK(const char *message, uint32_t waitMillis);
+ GPS_RESPONSE getACKCas(uint8_t class_id, uint8_t msg_id, uint32_t waitMillis);
+
/**
* Switch the GPS into a mode where we are actively looking for a lock, or alternatively switch GPS into a low power mode
*
@@ -219,6 +258,7 @@ class GPS : private concurrency::OSThread
// Calculate checksum
void UBXChecksum(uint8_t *message, size_t length);
+ void CASChecksum(uint8_t *message, size_t length);
/** Get how long we should stay looking for each aquisition
*/
@@ -246,4 +286,5 @@ class GPS : private concurrency::OSThread
GnssModel_t gnssModel = GNSS_MODEL_UNKNOWN;
};
-extern GPS *gps;
\ No newline at end of file
+extern GPS *gps;
+#endif // Exclude GPS
\ No newline at end of file
diff --git a/src/gps/GeoCoord.cpp b/src/gps/GeoCoord.cpp
index 19a753c02..cb4e69ff2 100644
--- a/src/gps/GeoCoord.cpp
+++ b/src/gps/GeoCoord.cpp
@@ -376,14 +376,17 @@ void GeoCoord::convertWGS84ToOSGB36(const double lat, const double lon, double &
}
/// Ported from my old java code, returns distance in meters along the globe
-/// surface (by magic?)
+/// surface (by Haversine formula)
float GeoCoord::latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
{
- double pk = (180 / 3.14169);
- double a1 = lat_a / pk;
- double a2 = lng_a / pk;
- double b1 = lat_b / pk;
- double b2 = lng_b / pk;
+ // Don't do math if the points are the same
+ if (lat_a == lat_b && lng_a == lng_b)
+ return 0.0;
+
+ double a1 = lat_a / DEG_CONVERT;
+ double a2 = lng_a / DEG_CONVERT;
+ double b1 = lat_b / DEG_CONVERT;
+ double b2 = lng_b / DEG_CONVERT;
double cos_b1 = cos(b1);
double cos_a1 = cos(a1);
double t1 = cos_a1 * cos(a2) * cos_b1 * cos(b2);
diff --git a/src/gps/GeoCoord.h b/src/gps/GeoCoord.h
index 06b11c3de..e811035db 100644
--- a/src/gps/GeoCoord.h
+++ b/src/gps/GeoCoord.h
@@ -11,6 +11,7 @@
#define PI 3.1415926535897932384626433832795
#define OLC_CODE_LEN 11
+#define DEG_CONVERT (180 / PI)
// Helper functions
// Raises a number to an exponent, handling negative exponents.
diff --git a/src/gps/NMEAWPL.cpp b/src/gps/NMEAWPL.cpp
index 9eff4d00e..71943b76c 100644
--- a/src/gps/NMEAWPL.cpp
+++ b/src/gps/NMEAWPL.cpp
@@ -1,3 +1,4 @@
+#if !MESHTASTIC_EXCLUDE_GPS
#include "NMEAWPL.h"
#include "GeoCoord.h"
#include "RTC.h"
@@ -74,10 +75,10 @@ uint32_t printWPL(char *buf, size_t bufsz, const meshtastic_Position &pos, const
uint32_t printGGA(char *buf, size_t bufsz, const meshtastic_Position &pos)
{
GeoCoord geoCoord(pos.latitude_i, pos.longitude_i, pos.altitude);
- tm *t = localtime((time_t *)&pos.timestamp);
+ tm *t = gmtime((time_t *)&pos.timestamp);
if (getRTCQuality() > 0) { // use the device clock if we got time from somewhere. If not, use the GPS timestamp.
uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice);
- t = localtime((time_t *)&rtc_sec);
+ t = gmtime((time_t *)&rtc_sec);
}
uint32_t len = snprintf(
@@ -93,4 +94,6 @@ uint32_t printGGA(char *buf, size_t bufsz, const meshtastic_Position &pos)
}
len += snprintf(buf + len, bufsz - len, "*%02X\r\n", chk);
return len;
-}
\ No newline at end of file
+}
+
+#endif
\ No newline at end of file
diff --git a/src/gps/RTC.cpp b/src/gps/RTC.cpp
index ef438a7dd..26af7cac2 100644
--- a/src/gps/RTC.cpp
+++ b/src/gps/RTC.cpp
@@ -40,7 +40,7 @@ void readFromRTC()
t.tm_hour = rtc.getHour();
t.tm_min = rtc.getMinute();
t.tm_sec = rtc.getSecond();
- tv.tv_sec = mktime(&t);
+ tv.tv_sec = gm_mktime(&t);
tv.tv_usec = 0;
LOG_DEBUG("Read RTC time from RV3028 as %ld\n", tv.tv_sec);
timeStartMsec = now;
@@ -68,7 +68,7 @@ void readFromRTC()
t.tm_hour = tc.hour;
t.tm_min = tc.minute;
t.tm_sec = tc.second;
- tv.tv_sec = mktime(&t);
+ tv.tv_sec = gm_mktime(&t);
tv.tv_usec = 0;
LOG_DEBUG("Read RTC time from PCF8563 as %ld\n", tv.tv_sec);
timeStartMsec = now;
@@ -128,7 +128,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
#else
rtc.initI2C();
#endif
- tm *t = localtime(&tv->tv_sec);
+ tm *t = gmtime(&tv->tv_sec);
rtc.setTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_wday, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
LOG_DEBUG("RV3028_RTC setTime %02d-%02d-%02d %02d:%02d:%02d %ld\n", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec, tv->tv_sec);
@@ -142,7 +142,7 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv)
#else
rtc.begin();
#endif
- tm *t = localtime(&tv->tv_sec);
+ tm *t = gmtime(&tv->tv_sec);
rtc.setDateTime(t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
LOG_DEBUG("PCF8563_RTC setDateTime %02d-%02d-%02d %02d:%02d:%02d %ld\n", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec, tv->tv_sec);
@@ -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
@@ -175,7 +175,9 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t)
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
*/
- time_t res = mktime(&t);
+ // horrible hack to make mktime TZ agnostic - best practise according to
+ // https://www.gnu.org/software/libc/manual/html_node/Broken_002ddown-Time.html
+ time_t res = gm_mktime(&t);
struct timeval tv;
tv.tv_sec = res;
tv.tv_usec = 0; // time.centisecond() * (10 / 1000);
@@ -189,14 +191,33 @@ bool perhapsSetRTC(RTCQuality q, struct tm &t)
}
}
+/**
+ * Returns the timezone offset in seconds.
+ *
+ * @return The timezone offset in seconds.
+ */
+int32_t getTZOffset()
+{
+ time_t now;
+ struct tm *gmt;
+ now = time(NULL);
+ gmt = gmtime(&now);
+ gmt->tm_isdst = -1;
+ return (int16_t)difftime(now, mktime(gmt));
+}
+
/**
* Returns the current time in seconds since the Unix epoch (January 1, 1970).
*
* @return The current time in seconds since the Unix epoch.
*/
-uint32_t getTime()
+uint32_t getTime(bool local)
{
- return (((uint32_t)millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
+ if (local) {
+ return (((uint32_t)millis() - timeStartMsec) / 1000) + zeroOffsetSecs + getTZOffset();
+ } else {
+ return (((uint32_t)millis() - timeStartMsec) / 1000) + zeroOffsetSecs;
+ }
}
/**
@@ -205,7 +226,19 @@ uint32_t getTime()
* @param minQuality The minimum quality of the RTC time required for it to be considered valid.
* @return The current time from the RTC if it meets the minimum quality requirement, or 0 if the time is not valid.
*/
-uint32_t getValidTime(RTCQuality minQuality)
+uint32_t getValidTime(RTCQuality minQuality, bool local)
{
- return (currentQuality >= minQuality) ? getTime() : 0;
-}
\ No newline at end of file
+ return (currentQuality >= minQuality) ? getTime(local) : 0;
+}
+
+time_t gm_mktime(struct tm *tm)
+{
+ setenv("TZ", "GMT0", 1);
+ time_t res = mktime(tm);
+ if (*config.device.tzdef) {
+ setenv("TZ", config.device.tzdef, 1);
+ } else {
+ setenv("TZ", "UTC0", 1);
+ }
+ return res;
+}
diff --git a/src/gps/RTC.h b/src/gps/RTC.h
index 527b31f46..0561819bd 100644
--- a/src/gps/RTC.h
+++ b/src/gps/RTC.h
@@ -29,13 +29,15 @@ bool perhapsSetRTC(RTCQuality q, const struct timeval *tv);
bool perhapsSetRTC(RTCQuality q, struct tm &t);
/// Return time since 1970 in secs. While quality is RTCQualityNone we will be returning time based at zero
-uint32_t getTime();
+uint32_t getTime(bool local = false);
/// Return time since 1970 in secs. If quality is RTCQualityNone return zero
-uint32_t getValidTime(RTCQuality minQuality);
+uint32_t getValidTime(RTCQuality minQuality, bool local = false);
void readFromRTC();
+time_t gm_mktime(struct tm *tm);
+
#define SEC_PER_DAY 86400
#define SEC_PER_HOUR 3600
#define SEC_PER_MIN 60
\ No newline at end of file
diff --git a/src/gps/cas.h b/src/gps/cas.h
new file mode 100644
index 000000000..53d75cda9
--- /dev/null
+++ b/src/gps/cas.h
@@ -0,0 +1,63 @@
+#pragma once
+
+// CASIC binary message definitions
+// Reference: https://www.icofchina.com/d/file/xiazai/2020-09-22/20f1b42b3a11ac52089caf3603b43fb5.pdf
+// ATGM33H-5N: https://www.icofchina.com/pro/mokuai/2016-08-01/4.html
+// (https://www.icofchina.com/d/file/xiazai/2016-12-05/b5c57074f4b1fcc62ba8c7868548d18a.pdf)
+
+// NEMA (Class ID - 0x4e) message IDs
+#define CAS_NEMA_GGA 0x00
+#define CAS_NEMA_GLL 0x01
+#define CAS_NEMA_GSA 0x02
+#define CAS_NEMA_GSV 0x03
+#define CAS_NEMA_RMC 0x04
+#define CAS_NEMA_VTG 0x05
+#define CAS_NEMA_GST 0x07
+#define CAS_NEMA_ZDA 0x08
+#define CAS_NEMA_DHV 0x0D
+
+// Size of a CAS-ACK-(N)ACK message (14 bytes)
+#define CAS_ACK_NACK_MSG_SIZE 0x0E
+
+// CFG-RST (0x06, 0x02)
+// Factory reset
+const uint8_t GPS::_message_CAS_CFG_RST_FACTORY[] = {
+ 0xFF, 0x03, // Fields to clear
+ 0x01, // Reset Mode: Controlled Software reset
+ 0x03 // Startup Mode: Factory
+};
+
+// CFG_RATE (0x06, 0x01)
+// 1HZ update rate, this should always be the case after
+// factory reset but update it regardless
+const uint8_t GPS::_message_CAS_CFG_RATE_1HZ[] = {
+ 0xE8, 0x03, // Update Rate: 0x03E8 = 1000ms
+ 0x00, 0x00 // Reserved
+};
+
+// CFG-NAVX (0x06, 0x07)
+// Initial ATGM33H-5N configuration, Updates for Dynamic Mode, Fix Mode, and SV system
+// Qwirk: The ATGM33H-5N-31 should only support GPS+BDS, however it will happily enable
+// and use GPS+BDS+GLONASS iff the correct CFG_NAVX command is used.
+const uint8_t GPS::_message_CAS_CFG_NAVX_CONF[] = {
+ 0x03, 0x01, 0x00, 0x00, // Update Mask: Dynamic Mode, Fix Mode, Nav Settings
+ 0x03, // Dynamic Mode: Automotive
+ 0x03, // Fix Mode: Auto 2D/3D
+ 0x00, // Min SV
+ 0x00, // Max SVs
+ 0x00, // Min CNO
+ 0x00, // Reserved1
+ 0x00, // Init 3D fix
+ 0x00, // Min Elevation
+ 0x00, // Dr Limit
+ 0x07, // Nav System: 2^0 = GPS, 2^1 = BDS 2^2 = GLONASS: 2^3
+ // 3=GPS+BDS, 7=GPS+BDS+GLONASS
+ 0x00, 0x00, // Rollover Week
+ 0x00, 0x00, 0x00, 0x00, // Fix Altitude
+ 0x00, 0x00, 0x00, 0x00, // Fix Height Error
+ 0x00, 0x00, 0x00, 0x00, // PDOP Maximum
+ 0x00, 0x00, 0x00, 0x00, // TDOP Maximum
+ 0x00, 0x00, 0x00, 0x00, // Position Accuracy Max
+ 0x00, 0x00, 0x00, 0x00, // Time Accuracy Max
+ 0x00, 0x00, 0x00, 0x00 // Static Hold Threshold
+};
\ 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 61d0eea5a..04915fe07 100644
--- a/src/graphics/EInkDisplay2.cpp
+++ b/src/graphics/EInkDisplay2.cpp
@@ -2,105 +2,49 @@
#ifdef USE_EINK
#include "EInkDisplay2.h"
-#include "GxEPD2_BW.h"
#include "SPILock.h"
#include "main.h"
#include
-// #ifdef HELTEC_WIRELESS_PAPER
-// SPIClass *hspi = NULL;
-// #endif
+/*
+ The macros EINK_DISPLAY_MODEL, EINK_WIDTH, and EINK_HEIGHT are defined as build_flags in a variant's platformio.ini
+ Previously, these macros were defined at the top of this file.
-#define COLORED GxEPD_BLACK
-#define UNCOLORED GxEPD_WHITE
+ For archival reasons, note that the following configurations had also been tested during this period:
+ * ifdef RAK4631
+ - 4.2 inch
+ EINK_DISPLAY_MODEL: GxEPD2_420_M01
+ EINK_WIDTH: 300
+ EINK_WIDTH: 400
-#if defined(TTGO_T_ECHO)
-#define TECHO_DISPLAY_MODEL GxEPD2_154_D67
-#elif defined(RAK4630)
+ - 2.9 inch
+ EINK_DISPLAY_MODEL: GxEPD2_290_T5D
+ EINK_WIDTH: 296
+ EINK_HEIGHT: 128
-// GxEPD2_213_BN - RAK14000 2.13 inch b/w 250x122 - changed from GxEPD2_213_B74 - which was not going to give partial update
-// support
-#define TECHO_DISPLAY_MODEL GxEPD2_213_BN
-
-// 4.2 inch 300x400 - GxEPD2_420_M01
-// #define TECHO_DISPLAY_MODEL GxEPD2_420_M01
-
-// 2.9 inch 296x128 - GxEPD2_290_T5D
-// #define TECHO_DISPLAY_MODEL GxEPD2_290_T5D
-
-// 1.54 inch 200x200 - GxEPD2_154_M09
-// #define TECHO_DISPLAY_MODEL GxEPD2_154_M09
-
-#elif defined(MAKERPYTHON)
-// 2.9 inch 296x128 - GxEPD2_290_T5D
-#define TECHO_DISPLAY_MODEL GxEPD2_290_T5D
-
-#elif defined(PCA10059)
-
-// 4.2 inch 300x400 - GxEPD2_420_M01
-#define TECHO_DISPLAY_MODEL GxEPD2_420_M01
-
-#elif defined(M5_COREINK)
-// M5Stack CoreInk
-// 1.54 inch 200x200 - GxEPD2_154_M09
-#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_BN
-#endif
-
-GxEPD2_BW *adafruitDisplay;
+ - 1.54 inch
+ EINK_DISPLAY_MODEL: GxEPD2_154_M09
+ EINK_WIDTH: 200
+ EINK_HEIGHT: 200
+*/
+// Constructor
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);
-#elif defined(RAK4630)
+ // Set dimensions in OLEDDisplay base class
+ this->geometry = GEOMETRY_RAWMODE;
+ this->displayWidth = EINK_WIDTH;
+ this->displayHeight = EINK_HEIGHT;
- // GxEPD2_213_BN - RAK14000 2.13 inch b/w 250x122
- setGeometry(GEOMETRY_RAWMODE, 250, 122);
+ // Round shortest side up to nearest byte, to prevent truncation causing an undersized buffer
+ uint16_t shortSide = min(EINK_WIDTH, EINK_HEIGHT);
+ uint16_t longSide = max(EINK_WIDTH, EINK_HEIGHT);
+ if (shortSide % 8 != 0)
+ shortSide = (shortSide | 7) + 1;
- // GxEPD2_420_M01
- // setGeometry(GEOMETRY_RAWMODE, 300, 400);
-
- // GxEPD2_290_T5D
- // setGeometry(GEOMETRY_RAWMODE, 296, 128);
-
- // GxEPD2_154_M09
- // setGeometry(GEOMETRY_RAWMODE, 200, 200);
-
-#elif defined(HELTEC_WIRELESS_PAPER)
- // setGeometry(GEOMETRY_RAWMODE, 212, 104);
- setGeometry(GEOMETRY_RAWMODE, 250, 122);
-#elif defined(MAKERPYTHON)
- // GxEPD2_290_T5D
- setGeometry(GEOMETRY_RAWMODE, 296, 128);
-
-#elif defined(PCA10059)
-
- // GxEPD2_420_M01
- setGeometry(GEOMETRY_RAWMODE, 300, 400);
-
-#elif defined(M5_COREINK)
-
- // M5Stack_CoreInk 200x200
- // 1.54 inch 200x200 - GxEPD2_154_M09
- setGeometry(GEOMETRY_RAWMODE, EPD_HEIGHT, EPD_WIDTH);
-#elif defined(my)
-
- // GxEPD2_290_T5D
- setGeometry(GEOMETRY_RAWMODE, 296, 128);
- 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
+ this->displayBufferSize = longSide * (shortSide / 8);
}
-// FIXME quick hack to limit drawing to a very slow rate
-uint32_t lastDrawMsec;
-
/**
* Force a display update if we haven't drawn within the specified msecLimit
*/
@@ -112,57 +56,37 @@ bool EInkDisplay::forceDisplay(uint32_t msecLimit)
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)
- 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;
- } else {
- // LOG_DEBUG("Skipping eink display\n");
+ else
return false;
+
+ // FIXME - only draw bits have changed (use backbuf similar to the other displays)
+ for (uint32_t y = 0; y < displayHeight; y++) {
+ for (uint32_t x = 0; x < displayWidth; x++) {
+ // get src pixel in the page based ordering the OLED lib uses FIXME, super inefficient
+ auto b = buffer[x + (y / 8) * displayWidth];
+ auto isset = b & (1 << (y & 7));
+ adafruitDisplay->drawPixel(x, y, isset ? GxEPD_BLACK : GxEPD_WHITE);
+ }
}
+
+ // Trigger the refresh in GxEPD2
+ LOG_DEBUG("Updating E-Paper... ");
+ adafruitDisplay->nextPage();
+
+ // End the update process
+ endUpdate();
+
+ LOG_DEBUG("done\n");
+ return true;
+}
+
+// End the update process - virtual method, overriden in derived class
+void EInkDisplay::endUpdate()
+{
+ // Power off display hardware, then deep-sleep (Except Wireless Paper V1.1, no deep-sleep)
+ adafruitDisplay->hibernate();
}
// Write the buffer to the display memory
@@ -171,8 +95,10 @@ 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)
+
+ if (lastDrawMsec) {
forceDisplay(slowUpdateMsec); // Show the first screen a few seconds after boot, then slower
+ }
}
// Send a command to the display (low level function)
@@ -187,87 +113,83 @@ void EInkDisplay::setDetected(uint8_t detected)
(void)detected;
}
-// Connect to the display
+// Connect to the display - variant specific
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);
-#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)
{
- auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
+ auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
+ 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);
-
+ auto lowLevel = new EINK_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
+ // RAK14000 2.13 inch b/w 250x122 does actually now support fast refresh
adafruitDisplay->setRotation(3);
- // Partial update support for 1.54, 2.13 RAK14000 b/w , 2.9 and 4.2
+ // Fast refresh support for 1.54, 2.13 RAK14000 b/w , 2.9 and 4.2
// adafruitDisplay->setRotation(1);
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
} else {
(void)adafruitDisplay;
}
}
-#elif defined(HELTEC_WIRELESS_PAPER)
+
+#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER)
{
- auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
- 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));
+ // Start HSPI
+ hspi = new SPIClass(HSPI);
+ hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS
+
+ // VExt already enabled in setup()
+ // RTC GPIO hold disabled in setup()
+
+ // Create GxEPD2 objects
+ auto lowLevel = new EINK_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);
- adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
}
#elif defined(PCA10059)
{
- auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
+ auto lowLevel = new EINK_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));
adafruitDisplay->setRotation(3);
adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight);
}
#elif defined(M5_COREINK)
- auto lowLevel = new TECHO_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
- adafruitDisplay = new GxEPD2_BW(*lowLevel);
+ auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
+ adafruitDisplay = new GxEPD2_BW(*lowLevel);
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)
+ adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
+#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);
+ auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY);
+ adafruitDisplay = new GxEPD2_BW(*lowLevel);
adafruitDisplay->init(115200, true, 40, false, SPI, SPISettings(4000000, MSBFIRST, SPI_MODE0));
adafruitDisplay->setRotation(1);
- adafruitDisplay->setPartialWindow(0, 0, EPD_WIDTH, EPD_HEIGHT);
+ adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
}
#endif
- // adafruitDisplay->setFullWindow();
- // adafruitDisplay->fillScreen(UNCOLORED);
- // adafruitDisplay->drawCircle(100, 100, 20, COLORED);
- // adafruitDisplay->display(false);
-
return true;
}
diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h
index 2529b1f0e..f74416494 100644
--- a/src/graphics/EInkDisplay2.h
+++ b/src/graphics/EInkDisplay2.h
@@ -1,16 +1,27 @@
#pragma once
+#ifdef USE_EINK
+
+#include "GxEPD2_BW.h"
#include
+#if defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_WIRELESS_PAPER)
+// 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.
*
+ * Note: EInkDynamicDisplay derives from this class.
+ *
* Remaining TODO:
* optimize display() to only draw changed pixels (see other OLED subclasses for examples)
* implement displayOn/displayOff to turn off the TFT device (and backlight)
* Use the fast NRF52 SPI API rather than the slow standard arduino version
*
* turn radio back on - currently with both on spi bus is fucked? or are we leaving chip select asserted?
+ * Suggestion: perhaps similar to HELTEC_WIRELESS_PAPER issue, which resolved with rtc_gpio_hold_dis()
*/
class EInkDisplay : public OLEDDisplay
{
@@ -32,7 +43,14 @@ class EInkDisplay : public OLEDDisplay
*
* @return true if we did draw the screen
*/
- bool forceDisplay(uint32_t msecLimit = 1000);
+ virtual bool forceDisplay(uint32_t msecLimit = 1000);
+
+ /**
+ * Run any code needed to complete an update, after the physical refresh has completed.
+ * Split from forceDisplay(), to enable async refresh in derived EInkDynamicDisplay class.
+ *
+ */
+ virtual void endUpdate();
/**
* shim to make the abstraction happy
@@ -49,4 +67,18 @@ class EInkDisplay : public OLEDDisplay
// Connect to the display
virtual bool connect() override;
+
+ // AdafruitGFX display object - instantiated in connect(), variant specific
+ GxEPD2_BW *adafruitDisplay = NULL;
+
+ // If display uses HSPI
+#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0)
+ SPIClass *hspi = NULL;
+#endif
+
+ private:
+ // FIXME quick hack to limit drawing to a very slow rate
+ uint32_t lastDrawMsec = 0;
};
+
+#endif
\ No newline at end of file
diff --git a/src/graphics/EInkDynamicDisplay.cpp b/src/graphics/EInkDynamicDisplay.cpp
new file mode 100644
index 000000000..b396446fa
--- /dev/null
+++ b/src/graphics/EInkDynamicDisplay.cpp
@@ -0,0 +1,553 @@
+#include "configuration.h"
+
+#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
+#include "EInkDynamicDisplay.h"
+
+// Constructor
+EInkDynamicDisplay::EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus)
+ : EInkDisplay(address, sda, scl, geometry, i2cBus), NotifiedWorkerThread("EInkDynamicDisplay")
+{
+ // If tracking ghost pixels, grab memory
+#ifdef EINK_LIMIT_GHOSTING_PX
+ dirtyPixels = new uint8_t[EInkDisplay::displayBufferSize](); // Init with zeros
+#endif
+}
+
+// Destructor
+EInkDynamicDisplay::~EInkDynamicDisplay()
+{
+ // If we were tracking ghost pixels, free the memory
+#ifdef EINK_LIMIT_GHOSTING_PX
+ delete[] dirtyPixels;
+#endif
+}
+
+// Screen requests a BACKGROUND frame
+void EInkDynamicDisplay::display()
+{
+ addFrameFlag(BACKGROUND);
+ update();
+}
+
+// Screen requests a RESPONSIVE frame
+bool EInkDynamicDisplay::forceDisplay(uint32_t msecLimit)
+{
+ addFrameFlag(RESPONSIVE);
+ return update(); // (Unutilized) Base class promises to return true if update ran
+}
+
+// Add flag for the next frame
+void EInkDynamicDisplay::addFrameFlag(frameFlagTypes flag)
+{
+ // OR the new flag into the existing flags
+ this->frameFlags = (frameFlagTypes)(this->frameFlags | flag);
+}
+
+// GxEPD2 code to set fast refresh
+void EInkDynamicDisplay::configForFastRefresh()
+{
+ // Variant-specific code can go here
+#if defined(PRIVATE_HW)
+#else
+ // Otherwise:
+ adafruitDisplay->setPartialWindow(0, 0, adafruitDisplay->width(), adafruitDisplay->height());
+#endif
+}
+
+// GxEPD2 code to set full refresh
+void EInkDynamicDisplay::configForFullRefresh()
+{
+ // Variant-specific code can go here
+#if defined(PRIVATE_HW)
+#else
+ // Otherwise:
+ adafruitDisplay->setFullWindow();
+#endif
+}
+
+// Run any relevant GxEPD2 code, so next update will use correct refresh type
+void EInkDynamicDisplay::applyRefreshMode()
+{
+ // Change from FULL to FAST
+ if (currentConfig == FULL && refresh == FAST) {
+ configForFastRefresh();
+ currentConfig = FAST;
+ }
+
+ // Change from FAST back to FULL
+ else if (currentConfig == FAST && refresh == FULL) {
+ configForFullRefresh();
+ currentConfig = FULL;
+ }
+}
+
+// Update fastRefreshCount
+void EInkDynamicDisplay::adjustRefreshCounters()
+{
+ if (refresh == FAST)
+ fastRefreshCount++;
+
+ else if (refresh == FULL)
+ fastRefreshCount = 0;
+}
+
+// Trigger the display update by calling base class
+bool EInkDynamicDisplay::update()
+{
+ // Detemine the refresh mode to use, and start the update
+ bool refreshApproved = determineMode();
+ if (refreshApproved) {
+ EInkDisplay::forceDisplay(0); // Bypass base class' own rate-limiting system
+ storeAndReset(); // Store the result of this loop for next time. Note: call *before* endOrDetach()
+ endOrDetach(); // endUpdate() right now, or set the async refresh flag (if FULL and HAS_EINK_ASYNCFULL)
+ } else
+ storeAndReset(); // No update, no post-update code, just store the results
+
+ return refreshApproved; // (Unutilized) Base class promises to return true if update ran
+}
+
+// Figure out who runs the post-update code
+void EInkDynamicDisplay::endOrDetach()
+{
+ // If the GxEPD2 version reports that it has the async modifications
+#ifdef HAS_EINK_ASYNCFULL
+ if (previousRefresh == FULL) {
+ asyncRefreshRunning = true; // Set the flag - checked in determineMode(); cleared by onNotify()
+
+ if (previousFrameFlags & BLOCKING)
+ awaitRefresh();
+ else {
+ // Async begins
+ LOG_DEBUG("Async full-refresh begins (dropping frames)\n");
+ notifyLater(intervalPollAsyncRefresh, DUE_POLL_ASYNCREFRESH, true); // Hand-off to NotifiedWorkerThread
+ }
+ }
+
+ // Fast Refresh
+ else if (previousRefresh == FAST)
+ EInkDisplay::endUpdate(); // Still block while updating, but EInkDisplay needs us to call endUpdate() ourselves.
+
+ // Fallback - If using an unmodified version of GxEPD2 for some reason
+#else
+ if (previousRefresh == FULL || previousRefresh == FAST) { // If refresh wasn't skipped (on unspecified..)
+ LOG_WARN(
+ "GxEPD2 version has not been modified to support async refresh; using fallback behavior. Please update lib_deps in "
+ "variant's platformio.ini file\n");
+ EInkDisplay::endUpdate();
+ }
+#endif
+}
+
+// Assess situation, pick a refresh type
+bool EInkDynamicDisplay::determineMode()
+{
+ checkInitialized();
+ checkForPromotion();
+#if defined(HAS_EINK_ASYNCFULL)
+ checkBusyAsyncRefresh();
+#endif
+ checkRateLimiting();
+
+ // If too soon for a new frame, or display busy, abort early
+ if (refresh == SKIPPED)
+ return false; // No refresh
+
+ // -- New frame is due --
+
+ resetRateLimiting(); // Once determineMode() ends, will have to wait again
+ hashImage(); // Generate here, so we can still copy it to previousImageHash, even if we skip the comparison check
+ LOG_DEBUG("determineMode(): "); // Begin log entry
+
+ // Once mode determined, any remaining checks will bypass
+ checkCosmetic();
+ checkDemandingFast();
+ checkFrameMatchesPrevious();
+ checkConsecutiveFastRefreshes();
+#ifdef EINK_LIMIT_GHOSTING_PX
+ checkExcessiveGhosting();
+#endif
+ checkFastRequested();
+
+ if (refresh == UNSPECIFIED)
+ LOG_WARN("There was a flaw in the determineMode() logic.\n");
+
+ // -- Decision has been reached --
+ applyRefreshMode();
+ adjustRefreshCounters();
+
+#ifdef EINK_LIMIT_GHOSTING_PX
+ // Full refresh clears any ghosting
+ if (refresh == FULL)
+ resetGhostPixelTracking();
+#endif
+
+ // Return - call a refresh or not?
+ if (refresh == SKIPPED)
+ return false; // Don't trigger a refresh
+ else
+ return true; // Do trigger a refresh
+}
+
+// Is this the very first frame?
+void EInkDynamicDisplay::checkInitialized()
+{
+ if (!initialized) {
+ // Undo GxEPD2_BW::partialWindow(), if set by developer in EInkDisplay::connect()
+ configForFullRefresh();
+
+ // Clear any existing image, so we can draw logo with fast-refresh, but also to set GxEPD2_EPD::_initial_write
+ adafruitDisplay->clearScreen();
+
+ LOG_DEBUG("initialized, ");
+ initialized = true;
+
+ // Use a fast-refresh for the next frame; no skipping or else blank screen when waking from deep sleep
+ addFrameFlag(DEMAND_FAST);
+ }
+}
+
+// Was a frame skipped (rate, display busy) that should have been a FAST refresh?
+void EInkDynamicDisplay::checkForPromotion()
+{
+ // If a frame was skipped (rate, display busy), then promote a BACKGROUND frame
+ // Because we DID want a RESPONSIVE/COSMETIC/DEMAND_FULL frame last time, we just didn't get it
+
+ switch (previousReason) {
+ case ASYNC_REFRESH_BLOCKED_DEMANDFAST:
+ addFrameFlag(DEMAND_FAST);
+ break;
+ case ASYNC_REFRESH_BLOCKED_COSMETIC:
+ addFrameFlag(COSMETIC);
+ break;
+ case ASYNC_REFRESH_BLOCKED_RESPONSIVE:
+ case EXCEEDED_RATELIMIT_FAST:
+ addFrameFlag(RESPONSIVE);
+ break;
+ default:
+ break;
+ }
+}
+
+// Is it too soon for another frame of this type?
+void EInkDynamicDisplay::checkRateLimiting()
+{
+ uint32_t now = millis();
+
+ // Sanity check: millis() overflow - just let the update run..
+ if (previousRunMs > now)
+ return;
+
+ // Skip update: too soon for BACKGROUND
+ if (frameFlags == BACKGROUND) {
+ if (now - previousRunMs < EINK_LIMIT_RATE_BACKGROUND_SEC * 1000) {
+ refresh = SKIPPED;
+ reason = EXCEEDED_RATELIMIT_FULL;
+ return;
+ }
+ }
+
+ // No rate-limit for these special cases
+ if (frameFlags & COSMETIC || frameFlags & DEMAND_FAST)
+ return;
+
+ // Skip update: too soon for RESPONSIVE
+ if (frameFlags & RESPONSIVE) {
+ if (now - previousRunMs < EINK_LIMIT_RATE_RESPONSIVE_SEC * 1000) {
+ refresh = SKIPPED;
+ reason = EXCEEDED_RATELIMIT_FAST;
+ LOG_DEBUG("refresh=SKIPPED, reason=EXCEEDED_RATELIMIT_FAST, frameFlags=0x%x\n", frameFlags);
+ return;
+ }
+ }
+}
+
+// Is this frame COSMETIC (splash screens?)
+void EInkDynamicDisplay::checkCosmetic()
+{
+ // If a decision was already reached, don't run the check
+ if (refresh != UNSPECIFIED)
+ return;
+
+ // A full refresh is requested for cosmetic purposes: we have a decision
+ if (frameFlags & COSMETIC) {
+ refresh = FULL;
+ reason = FLAGGED_COSMETIC;
+ LOG_DEBUG("refresh=FULL, reason=FLAGGED_COSMETIC, frameFlags=0x%x\n", frameFlags);
+ }
+}
+
+// Is this a one-off special circumstance, where we REALLY want a fast refresh?
+void EInkDynamicDisplay::checkDemandingFast()
+{
+ // If a decision was already reached, don't run the check
+ if (refresh != UNSPECIFIED)
+ return;
+
+ // A fast refresh is demanded: we have a decision
+ if (frameFlags & DEMAND_FAST) {
+ refresh = FAST;
+ reason = FLAGGED_DEMAND_FAST;
+ LOG_DEBUG("refresh=FAST, reason=FLAGGED_DEMAND_FAST, frameFlags=0x%x\n", frameFlags);
+ }
+}
+
+// Does the new frame match the currently displayed image?
+void EInkDynamicDisplay::checkFrameMatchesPrevious()
+{
+ // If a decision was already reached, don't run the check
+ if (refresh != UNSPECIFIED)
+ return;
+
+ // If frame is *not* a duplicate, abort the check
+ if (imageHash != previousImageHash)
+ return;
+
+#if !defined(EINK_BACKGROUND_USES_FAST)
+ // If BACKGROUND, and last update was FAST: redraw the same image in FULL (for display health + image quality)
+ if (frameFlags == BACKGROUND && fastRefreshCount > 0) {
+ refresh = FULL;
+ reason = REDRAW_WITH_FULL;
+ LOG_DEBUG("refresh=FULL, reason=REDRAW_WITH_FULL, frameFlags=0x%x\n", frameFlags);
+ return;
+ }
+#endif
+
+ // Not redrawn, not COSMETIC, not DEMAND_FAST
+ refresh = SKIPPED;
+ reason = FRAME_MATCHED_PREVIOUS;
+ LOG_DEBUG("refresh=SKIPPED, reason=FRAME_MATCHED_PREVIOUS, frameFlags=0x%x\n", frameFlags);
+}
+
+// Have too many fast-refreshes occured consecutively, since last full refresh?
+void EInkDynamicDisplay::checkConsecutiveFastRefreshes()
+{
+ // If a decision was already reached, don't run the check
+ if (refresh != UNSPECIFIED)
+ return;
+
+ // If too many FAST refreshes consecutively - force a FULL refresh
+ if (fastRefreshCount >= EINK_LIMIT_FASTREFRESH) {
+ refresh = FULL;
+ reason = EXCEEDED_LIMIT_FASTREFRESH;
+ LOG_DEBUG("refresh=FULL, reason=EXCEEDED_LIMIT_FASTREFRESH, frameFlags=0x%x\n", frameFlags);
+ }
+}
+
+// No objections, we can perform fast-refresh, if desired
+void EInkDynamicDisplay::checkFastRequested()
+{
+ if (refresh != UNSPECIFIED)
+ return;
+
+ if (frameFlags == BACKGROUND) {
+#ifdef EINK_BACKGROUND_USES_FAST
+ // If we want BACKGROUND to use fast. (FULL only when a limit is hit)
+ refresh = FAST;
+ reason = BACKGROUND_USES_FAST;
+ LOG_DEBUG("refresh=FAST, reason=BACKGROUND_USES_FAST, fastRefreshCount=%lu, frameFlags=0x%x\n", fastRefreshCount,
+ frameFlags);
+#else
+ // If we do want to use FULL for BACKGROUND updates
+ refresh = FULL;
+ reason = FLAGGED_BACKGROUND;
+ LOG_DEBUG("refresh=FULL, reason=FLAGGED_BACKGROUND\n");
+#endif
+ }
+
+ // Sanity: confirm that we did ask for a RESPONSIVE frame.
+ if (frameFlags & RESPONSIVE) {
+ refresh = FAST;
+ reason = NO_OBJECTIONS;
+ LOG_DEBUG("refresh=FAST, reason=NO_OBJECTIONS, fastRefreshCount=%lu, frameFlags=0x%x\n", fastRefreshCount, frameFlags);
+ }
+}
+
+// Reset the timer used for rate-limiting
+void EInkDynamicDisplay::resetRateLimiting()
+{
+ previousRunMs = millis();
+}
+
+// Generate a hash of this frame, to compare against previous update
+void EInkDynamicDisplay::hashImage()
+{
+ imageHash = 0;
+
+ // Sum all bytes of the image buffer together
+ for (uint16_t b = 0; b < (displayWidth / 8) * displayHeight; b++) {
+ imageHash += buffer[b];
+ }
+}
+
+// Store the results of determineMode() for future use, and reset for next call
+void EInkDynamicDisplay::storeAndReset()
+{
+ previousFrameFlags = frameFlags;
+ previousRefresh = refresh;
+ previousReason = reason;
+
+ // Only store image hash if the display will update
+ if (refresh != SKIPPED) {
+ previousImageHash = imageHash;
+ }
+
+ frameFlags = BACKGROUND;
+ refresh = UNSPECIFIED;
+}
+
+#ifdef EINK_LIMIT_GHOSTING_PX
+// Count how many ghost pixels the new image will display
+void EInkDynamicDisplay::countGhostPixels()
+{
+ // If a decision was already reached, don't run the check
+ if (refresh != UNSPECIFIED)
+ return;
+
+ // Start a new count
+ ghostPixelCount = 0;
+
+ // Check new image, bit by bit, for any white pixels at locations marked "dirty"
+ for (uint16_t i = 0; i < displayBufferSize; i++) {
+ for (uint8_t bit = 0; bit < 7; bit++) {
+
+ const bool dirty = (dirtyPixels[i] >> bit) & 1; // Has pixel location been drawn to since full-refresh?
+ const bool shouldBeBlank = !((buffer[i] >> bit) & 1); // Is pixel location white in the new image?
+
+ // If pixel is (or has been) black since last full-refresh, and now is white: ghosting
+ if (dirty && shouldBeBlank)
+ ghostPixelCount++;
+
+ // Update the dirty status for this pixel - will this location become a ghost if set white in future?
+ if (!dirty && !shouldBeBlank)
+ dirtyPixels[i] |= (1 << bit);
+ }
+ }
+
+ LOG_DEBUG("ghostPixels=%hu, ", ghostPixelCount);
+}
+
+// Check if ghost pixel count exceeds the defined limit
+void EInkDynamicDisplay::checkExcessiveGhosting()
+{
+ // If a decision was already reached, don't run the check
+ if (refresh != UNSPECIFIED)
+ return;
+
+ countGhostPixels();
+
+ // If too many ghost pixels, select full refresh
+ if (ghostPixelCount > EINK_LIMIT_GHOSTING_PX) {
+ refresh = FULL;
+ reason = EXCEEDED_GHOSTINGLIMIT;
+ LOG_DEBUG("refresh=FULL, reason=EXCEEDED_GHOSTINGLIMIT, frameFlags=0x%x\n", frameFlags);
+ }
+}
+
+// Clear the dirty pixels array. Call when full-refresh cleans the display.
+void EInkDynamicDisplay::resetGhostPixelTracking()
+{
+ // Copy the current frame into dirtyPixels[] from the display buffer
+ memcpy(dirtyPixels, EInkDisplay::buffer, EInkDisplay::displayBufferSize);
+}
+#endif // EINK_LIMIT_GHOSTING_PX
+
+// Handle any asyc tasks
+void EInkDynamicDisplay::onNotify(uint32_t notification)
+{
+ // Which task
+ switch (notification) {
+ case DUE_POLL_ASYNCREFRESH:
+ pollAsyncRefresh();
+ break;
+ }
+}
+
+#ifdef HAS_EINK_ASYNCFULL
+// Public: wait for an refresh already in progress, then run the post-update code. See Screen::setScreensaverFrames()
+void EInkDynamicDisplay::joinAsyncRefresh()
+{
+ // If no async refresh running, nothing to do
+ if (!asyncRefreshRunning)
+ return;
+
+ LOG_DEBUG("Joining an async refresh in progress\n");
+
+ // Continually poll the BUSY pin
+ while (adafruitDisplay->epd2.isBusy())
+ yield();
+
+ // If asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done
+ adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
+ EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
+ asyncRefreshRunning = false; // Unset the flag
+ LOG_DEBUG("Refresh complete\n");
+
+ // Note: this code only works because of a modification to meshtastic/GxEPD2.
+ // It is only equipped to intercept calls to nextPage()
+}
+
+// Called from NotifiedWorkerThread. Run the post-update code if the hardware is ready
+void EInkDynamicDisplay::pollAsyncRefresh()
+{
+ // In theory, this condition should never be met
+ if (!asyncRefreshRunning)
+ return;
+
+ // Still running, check back later
+ if (adafruitDisplay->epd2.isBusy()) {
+ // Schedule next call of pollAsyncRefresh()
+ NotifiedWorkerThread::notifyLater(intervalPollAsyncRefresh, DUE_POLL_ASYNCREFRESH, true);
+ return;
+ }
+
+ // If asyncRefreshRunning flag is still set, but display's BUSY pin reports the refresh is done
+ adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
+ EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
+ asyncRefreshRunning = false; // Unset the flag
+ LOG_DEBUG("Async full-refresh complete\n");
+
+ // Note: this code only works because of a modification to meshtastic/GxEPD2.
+ // It is only equipped to intercept calls to nextPage()
+}
+
+// Check the status of "async full-refresh"; skip if running
+void EInkDynamicDisplay::checkBusyAsyncRefresh()
+{
+ // No refresh taking place, continue with determineMode()
+ if (!asyncRefreshRunning)
+ return;
+
+ // Full refresh still running
+ if (adafruitDisplay->epd2.isBusy()) {
+ // No refresh
+ refresh = SKIPPED;
+
+ // Set the reason, marking what type of frame we're skipping
+ if (frameFlags & DEMAND_FAST)
+ reason = ASYNC_REFRESH_BLOCKED_DEMANDFAST;
+ else if (frameFlags & COSMETIC)
+ reason = ASYNC_REFRESH_BLOCKED_COSMETIC;
+ else if (frameFlags & RESPONSIVE)
+ reason = ASYNC_REFRESH_BLOCKED_RESPONSIVE;
+ else
+ reason = ASYNC_REFRESH_BLOCKED_BACKGROUND;
+
+ return;
+ }
+}
+
+// Hold control while an async refresh runs
+void EInkDynamicDisplay::awaitRefresh()
+{
+ // Continually poll the BUSY pin
+ while (adafruitDisplay->epd2.isBusy())
+ yield();
+
+ // End the full-refresh process
+ adafruitDisplay->endAsyncFull(); // Run the end of nextPage() code
+ EInkDisplay::endUpdate(); // Run base-class code to finish off update (NOT our derived class override)
+ asyncRefreshRunning = false; // Unset the flag
+}
+#endif // HAS_EINK_ASYNCFULL
+
+#endif // USE_EINK_DYNAMICDISPLAY
\ No newline at end of file
diff --git a/src/graphics/EInkDynamicDisplay.h b/src/graphics/EInkDynamicDisplay.h
new file mode 100644
index 000000000..8f3ce205a
--- /dev/null
+++ b/src/graphics/EInkDynamicDisplay.h
@@ -0,0 +1,148 @@
+#pragma once
+
+#include "configuration.h"
+
+#if defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
+
+#include "EInkDisplay2.h"
+#include "GxEPD2_BW.h"
+#include "concurrency/NotifiedWorkerThread.h"
+
+/*
+ Derives from the EInkDisplay adapter class.
+ Accepts suggestions from Screen class about frame type.
+ Determines which refresh type is most suitable.
+ (Full, Fast, Skip)
+*/
+
+class EInkDynamicDisplay : public EInkDisplay, protected concurrency::NotifiedWorkerThread
+{
+ public:
+ // Constructor
+ // ( Parameters unused, passed to EInkDisplay. Maintains compatibility OLEDDisplay class )
+ EInkDynamicDisplay(uint8_t address, int sda, int scl, OLEDDISPLAY_GEOMETRY geometry, HW_I2C i2cBus);
+ ~EInkDynamicDisplay();
+
+ // What kind of frame is this
+ enum frameFlagTypes : uint8_t {
+ BACKGROUND = (1 << 0), // For frames via display()
+ RESPONSIVE = (1 << 1), // For frames via forceDisplay()
+ COSMETIC = (1 << 2), // For splashes
+ DEMAND_FAST = (1 << 3), // Special case only
+ BLOCKING = (1 << 4), // Modifier - block while refresh runs
+ };
+ void addFrameFlag(frameFlagTypes flag);
+
+ // Set the correct frame flag, then call universal "update()" method
+ void display() override;
+ bool forceDisplay(uint32_t msecLimit) override; // Shadows base class. Parameter and return val unused.
+
+ protected:
+ enum refreshTypes : uint8_t { // Which refresh operation will be used
+ UNSPECIFIED,
+ FULL,
+ FAST,
+ SKIPPED,
+ };
+ enum reasonTypes : uint8_t { // How was the decision reached
+ NO_OBJECTIONS,
+ ASYNC_REFRESH_BLOCKED_DEMANDFAST,
+ ASYNC_REFRESH_BLOCKED_COSMETIC,
+ ASYNC_REFRESH_BLOCKED_RESPONSIVE,
+ ASYNC_REFRESH_BLOCKED_BACKGROUND,
+ EXCEEDED_RATELIMIT_FAST,
+ EXCEEDED_RATELIMIT_FULL,
+ FLAGGED_COSMETIC,
+ FLAGGED_DEMAND_FAST,
+ EXCEEDED_LIMIT_FASTREFRESH,
+ EXCEEDED_GHOSTINGLIMIT,
+ FRAME_MATCHED_PREVIOUS,
+ BACKGROUND_USES_FAST,
+ FLAGGED_BACKGROUND,
+ REDRAW_WITH_FULL,
+ };
+
+ enum notificationTypes : uint8_t { // What was onNotify() called for
+ NONE = 0, // This behavior (NONE=0) is fixed by NotifiedWorkerThread class
+ DUE_POLL_ASYNCREFRESH = 1,
+ };
+ const uint32_t intervalPollAsyncRefresh = 100;
+
+ void onNotify(uint32_t notification) override; // Handle any async tasks - overrides NotifiedWorkerThread
+ void configForFastRefresh(); // GxEPD2 code to set fast-refresh
+ void configForFullRefresh(); // GxEPD2 code to set full-refresh
+ bool determineMode(); // Assess situation, pick a refresh type
+ void applyRefreshMode(); // Run any relevant GxEPD2 code, so next update will use correct refresh type
+ void adjustRefreshCounters(); // Update fastRefreshCount
+ bool update(); // Trigger the display update - determine mode, then call base class
+ void endOrDetach(); // Run the post-update code, or delegate it off to checkBusyAsyncRefresh()
+
+ // Checks as part of determineMode()
+ void checkInitialized(); // Is this the very first frame?
+ void checkForPromotion(); // Was a frame skipped (rate, display busy) that should have been a FAST refresh?
+ void checkRateLimiting(); // Is this frame too soon?
+ void checkCosmetic(); // Was the COSMETIC flag set?
+ void checkDemandingFast(); // Was the DEMAND_FAST flag set?
+ void checkFrameMatchesPrevious(); // Does the new frame match the existing display image?
+ void checkConsecutiveFastRefreshes(); // Too many fast-refreshes consecutively?
+ void checkFastRequested(); // Was the flag set for RESPONSIVE, or only BACKGROUND?
+
+ void resetRateLimiting(); // Set previousRunMs - this now counts as an update, for rate-limiting
+ void hashImage(); // Generate a hashed version of this frame, to compare against previous update
+ void storeAndReset(); // Keep results of determineMode() for later, tidy-up for next call
+
+ // What we are determining for this frame
+ frameFlagTypes frameFlags = BACKGROUND; // Frame characteristics - determineMode() input
+ refreshTypes refresh = UNSPECIFIED; // Refresh type - determineMode() output
+ reasonTypes reason = NO_OBJECTIONS; // Reason - why was refresh type used
+
+ // What happened last time determineMode() ran
+ frameFlagTypes previousFrameFlags = BACKGROUND; // (Previous) Frame flags
+ refreshTypes previousRefresh = UNSPECIFIED; // (Previous) Outcome
+ reasonTypes previousReason = NO_OBJECTIONS; // (Previous) Reason
+
+ bool initialized = false; // Have we drawn at least one frame yet?
+ uint32_t previousRunMs = -1; // When did determineMode() last run (rather than rejecting for rate-limiting)
+ uint32_t imageHash = 0; // Hash of the current frame. Don't bother updating if nothing has changed!
+ uint32_t previousImageHash = 0; // Hash of the previous update's frame
+ uint32_t fastRefreshCount = 0; // How many fast-refreshes consecutively since last full refresh?
+ refreshTypes currentConfig = FULL; // Which refresh type is GxEPD2 currently configured for
+
+ // Optional - track ghosting, pixel by pixel
+#ifdef EINK_LIMIT_GHOSTING_PX
+ void countGhostPixels(); // Count any pixels which have moved from black to white since last full-refresh
+ void checkExcessiveGhosting(); // Check if ghosting exceeds defined limit
+ void resetGhostPixelTracking(); // Clear the dirty pixels array. Call when full-refresh cleans the display.
+ uint8_t *dirtyPixels; // Any pixels that have been black since last full-refresh (dynamically allocated mem)
+ uint32_t ghostPixelCount = 0; // Number of pixels with problematic ghosting. Retained here for LOG_DEBUG use
+#endif
+
+ // Conditional - async full refresh - only with modified meshtastic/GxEPD2
+#if defined(HAS_EINK_ASYNCFULL)
+ public:
+ void joinAsyncRefresh(); // Main thread joins an async refresh already in progress. Blocks, then runs post-update code
+
+ protected:
+ void pollAsyncRefresh(); // Run the post-update code if the hardware is ready
+ void checkBusyAsyncRefresh(); // Check if display is busy running an async full-refresh (rejecting new frames)
+ void awaitRefresh(); // Hold control while an async refresh runs
+ void endUpdate() override {} // Disable base-class behavior of running post-update immediately after forceDisplay()
+ bool asyncRefreshRunning = false; // Flag, checked by checkBusyAsyncRefresh()
+#else
+ public:
+ void joinAsyncRefresh() {} // Dummy method
+
+ protected:
+ void pollAsyncRefresh() {} // Dummy method. In theory, not reachable
+#endif
+};
+
+// Hide the ugly casts used in Screen.cpp
+#define EINK_ADD_FRAMEFLAG(display, flag) static_cast(display)->addFrameFlag(EInkDynamicDisplay::flag)
+#define EINK_JOIN_ASYNCREFRESH(display) static_cast(display)->joinAsyncRefresh()
+
+#else // !USE_EINK_DYNAMICDISPLAY
+// Dummy-macro, removes the need for include guards
+#define EINK_ADD_FRAMEFLAG(display, flag)
+#define EINK_JOIN_ASYNCREFRESH(display)
+#endif
\ No newline at end of file
diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp
index b626e393a..e5f392036 100644
--- a/src/graphics/Screen.cpp
+++ b/src/graphics/Screen.cpp
@@ -25,12 +25,15 @@ along with this program. If not, see .
#include
#include "DisplayFormatters.h"
+#if !MESHTASTIC_EXCLUDE_GPS
#include "GPS.h"
+#endif
#include "MeshService.h"
#include "NodeDB.h"
#include "error.h"
#include "gps/GeoCoord.h"
#include "gps/RTC.h"
+#include "graphics/ScreenFonts.h"
#include "graphics/images.h"
#include "input/TouchScreenImpl1.h"
#include "main.h"
@@ -43,18 +46,17 @@ 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
-#ifdef OLED_RU
-#include "fonts/OLEDDisplayFontsRU.h"
-#endif
-
-#ifdef OLED_UA
-#include "fonts/OLEDDisplayFontsUA.h"
+#if ARCH_PORTDUINO
+#include "platform/portduino/PortduinoGlue.h"
#endif
using namespace meshtastic; /** @todo remove */
@@ -71,7 +73,7 @@ namespace graphics
// #define SHOW_REDRAWS
// A text message frame + debug frame + all the node infos
-static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
+FrameCallback *normalFrames;
static uint32_t targetFramerate = IDLE_FRAMERATE;
static char btPIN[16] = "888888";
@@ -92,8 +94,10 @@ std::vector moduleFrames;
// Stores the last 4 of our hardware ID, to make finding the device for pairing easier
static char ourId[5];
+#if HAS_GPS
// GeoCoord object for the screen
GeoCoord geoCoord;
+#endif
#ifdef SHOW_REDRAWS
static bool heartbeat = false;
@@ -104,31 +108,7 @@ static uint16_t displayWidth, displayHeight;
#define SCREEN_WIDTH displayWidth
#define SCREEN_HEIGHT displayHeight
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
- !defined(DISPLAY_FORCE_SMALL_FONTS)
-// The screen is bigger so use bigger fonts
-#define FONT_SMALL ArialMT_Plain_16 // Height: 19
-#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28
-#define FONT_LARGE ArialMT_Plain_24 // Height: 28
-#else
-#ifdef OLED_RU
-#define FONT_SMALL ArialMT_Plain_10_RU
-#else
-#ifdef OLED_UA
-#define FONT_SMALL ArialMT_Plain_10_UA
-#else
-#define FONT_SMALL ArialMT_Plain_10 // Height: 13
-#endif
-#endif
-#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19
-#define FONT_LARGE ArialMT_Plain_24 // Height: 28
-#endif
-
-#define fontHeight(font) ((font)[1] + 1) // height is position 1
-
-#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
-#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
-#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE)
+#include "graphics/ScreenFonts.h"
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
@@ -143,7 +123,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);
@@ -267,7 +247,7 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i
if ((millis() / 10000) % 2) {
display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Set the region using the");
display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "Meshtastic Android, iOS,");
- display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Flasher or CLI client.");
+ display->drawString(x, y + FONT_HEIGHT_SMALL * 4 - 3, "Web or CLI clients.");
} else {
display->drawString(x, y + FONT_HEIGHT_SMALL * 2 - 3, "Visit meshtastic.org");
display->drawString(x, y + FONT_HEIGHT_SMALL * 3 - 3, "for more information.");
@@ -282,10 +262,65 @@ static void drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUiState *state, i
#ifdef USE_EINK
/// Used on eink displays while in deep sleep
-static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
+static void drawDeepSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
+ // Next frame should use full-refresh, and block while running, else device will sleep before async callback
+ EINK_ADD_FRAMEFLAG(display, COSMETIC);
+ EINK_ADD_FRAMEFLAG(display, BLOCKING);
+
+ LOG_DEBUG("Drawing deep sleep screen\n");
drawIconScreen("Sleeping...", display, state, x, y);
}
+
+/// Used on eink displays when screen updates are paused
+static void drawScreensaverOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
+{
+ LOG_DEBUG("Drawing screensaver overlay\n");
+
+ EINK_ADD_FRAMEFLAG(display, COSMETIC); // Take the opportunity for a full-refresh
+
+ // Config
+ display->setFont(FONT_SMALL);
+ display->setTextAlignment(TEXT_ALIGN_LEFT);
+ const char *pauseText = "Screen Paused";
+ const char *idText = owner.short_name;
+ constexpr uint16_t padding = 5;
+ constexpr uint8_t dividerGap = 1;
+ constexpr uint8_t imprecision = 5; // How far the box origins can drift from center. Combat burn-in.
+
+ // Dimensions
+ const uint16_t idTextWidth = display->getStringWidth(idText, strlen(idText));
+ const uint16_t pauseTextWidth = display->getStringWidth(pauseText, strlen(pauseText));
+ const uint16_t boxWidth = padding + idTextWidth + padding + padding + pauseTextWidth + padding;
+ const uint16_t boxHeight = padding + FONT_HEIGHT_SMALL + padding;
+
+ // Position
+ const int16_t boxLeft = (display->width() / 2) - (boxWidth / 2) + random(-imprecision, imprecision + 1);
+ // const int16_t boxRight = boxLeft + boxWidth - 1;
+ const int16_t boxTop = (display->height() / 2) - (boxHeight / 2 + random(-imprecision, imprecision + 1));
+ const int16_t boxBottom = boxTop + boxHeight - 1;
+ const int16_t idTextLeft = boxLeft + padding;
+ const int16_t idTextTop = boxTop + padding;
+ const int16_t pauseTextLeft = boxLeft + padding + idTextWidth + padding + padding;
+ const int16_t pauseTextTop = boxTop + padding;
+ const int16_t dividerX = boxLeft + padding + idTextWidth + padding;
+ const int16_t dividerTop = boxTop + 1 + dividerGap;
+ const int16_t dividerBottom = boxBottom - 1 - dividerGap;
+
+ // Draw: box
+ display->setColor(EINK_WHITE);
+ display->fillRect(boxLeft - 1, boxTop - 1, boxWidth + 2, boxHeight + 2); // Clear a slightly oversized area for the box
+ display->setColor(EINK_BLACK);
+ display->drawRect(boxLeft, boxTop, boxWidth, boxHeight);
+
+ // Draw: Text
+ display->drawString(idTextLeft, idTextTop, idText);
+ display->drawString(pauseTextLeft, pauseTextTop, pauseText);
+ display->drawString(pauseTextLeft + 1, pauseTextTop, pauseText); // Faux bold
+
+ // Draw: divider
+ display->drawLine(dividerX, dividerTop, dividerX, dividerBottom);
+}
#endif
static void drawModuleFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
@@ -374,7 +409,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state
static char tempBuf[237];
const meshtastic_MeshPacket &mp = devicestate.rx_text_message;
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp));
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
// LOG_DEBUG("drawing text message from 0x%x: %s\n", mp.from,
// mp.decoded.variant.data.decoded.bytes);
@@ -412,7 +447,7 @@ static void drawWaypointFrame(OLEDDisplay *display, OLEDDisplayUiState *state, i
static char tempBuf[237];
meshtastic_MeshPacket &mp = devicestate.rx_waypoint;
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNode(getFrom(&mp));
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL);
@@ -493,7 +528,7 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStat
{
char usersString[20];
snprintf(usersString, sizeof(usersString), "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal());
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x, y + 3, 8, 8, imgUser);
#else
@@ -503,7 +538,7 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const NodeStat
if (config.display.heading_bold)
display->drawString(x + 11, y - 2, usersString);
}
-
+#if HAS_GPS
// Draw GPS status summary
static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps)
{
@@ -551,15 +586,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 +627,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";
@@ -640,7 +680,7 @@ static void drawGPScoordinates(OLEDDisplay *display, int16_t x, int16_t y, const
}
}
}
-
+#endif
namespace
{
@@ -795,16 +835,16 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
if (state->currentFrame != prevFrame) {
prevFrame = state->currentFrame;
- nodeIndex = (nodeIndex + 1) % nodeDB.getNumMeshNodes();
- meshtastic_NodeInfoLite *n = nodeDB.getMeshNodeByIndex(nodeIndex);
- if (n->num == nodeDB.getNodeNum()) {
+ nodeIndex = (nodeIndex + 1) % nodeDB->getNumMeshNodes();
+ meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(nodeIndex);
+ if (n->num == nodeDB->getNodeNum()) {
// Don't show our node, just skip to next
- nodeIndex = (nodeIndex + 1) % nodeDB.getNumMeshNodes();
- n = nodeDB.getMeshNodeByIndex(nodeIndex);
+ nodeIndex = (nodeIndex + 1) % nodeDB->getNumMeshNodes();
+ n = nodeDB->getMeshNodeByIndex(nodeIndex);
}
}
- meshtastic_NodeInfoLite *node = nodeDB.getMeshNodeByIndex(nodeIndex);
+ meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(nodeIndex);
display->setFont(FONT_SMALL);
@@ -842,7 +882,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_
} else {
strncpy(distStr, "? km", sizeof(distStr));
}
- meshtastic_NodeInfoLite *ourNode = nodeDB.getMeshNode(nodeDB.getNodeNum());
+ meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
int16_t compassX = 0, compassY = 0;
@@ -906,13 +946,52 @@ 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)
{
+ graphics::normalFrames = new FrameCallback[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
+#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) || defined(HX8357_CS)
+ dispdev = new TFTDisplay(address.address, -1, -1, geometry,
+ (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
+#elif defined(USE_EINK) && !defined(USE_EINK_DYNAMICDISPLAY)
+ dispdev = new EInkDisplay(address.address, -1, -1, geometry,
+ (address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
+#elif defined(USE_EINK) && defined(USE_EINK_DYNAMICDISPLAY)
+ dispdev = new EInkDynamicDisplay(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);
}
+Screen::~Screen()
+{
+ delete[] graphics::normalFrames;
+}
+
/**
* Prepare the display for the unit going to the lowest power mode possible. Most screens will just
* poweroff, but eink screens will show a "I'm sleeping" graphic, possibly with a QR code
@@ -920,15 +999,17 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
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();
+ setOn(false, drawDeepSleepScreen);
+#ifdef PIN_EINK_EN
+ digitalWrite(PIN_EINK_EN, LOW); // power off backlight
#endif
+#else
+ // Without E-Ink display:
setOn(false);
+#endif
}
-void Screen::handleSetOn(bool on)
+void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
{
if (!useDisplay)
return;
@@ -939,14 +1020,20 @@ 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 {
+#ifdef USE_EINK
+ // eInkScreensaver parameter is usually NULL (default argument), default frame used instead
+ setScreensaverFrames(einkScreensaver);
+#endif
LOG_INFO("Turning off screen\n");
- dispdev.displayOff();
+ dispdev->displayOff();
#ifdef T_WATCH_S3
PMU->disablePowerOutput(XPOWERS_ALDO2);
#endif
@@ -963,56 +1050,62 @@ 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;
// Add frames.
+ EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST);
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) || defined(HX8357_CS)
+ static_cast(dispdev)->flipScreenVertically();
+#else
+ dispdev->flipScreenVertically();
+#endif
}
#endif
@@ -1020,20 +1113,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
@@ -1050,11 +1153,34 @@ void Screen::setup()
MeshModule::observeUIEvents(&uiFrameEventObserver);
}
-void Screen::forceDisplay()
+void Screen::forceDisplay(bool forceUiUpdate)
{
// Nasty hack to force epaper updates for 'key' frames. FIXME, cleanup.
#ifdef USE_EINK
- dispdev.forceDisplay();
+ // If requested, make sure queued commands are run, and UI has rendered a new frame
+ if (forceUiUpdate) {
+ // No delay between UI frame rendering
+ setFastFramerate();
+
+ // Make sure all CMDs have run first
+ while (!cmdQueue.isEmpty())
+ runOnce();
+
+ // Ensure at least one frame has drawn
+ uint64_t startUpdate;
+ do {
+ startUpdate = millis(); // Handle impossibly unlikely corner case of a millis() overflow..
+ delay(10);
+ ui->update();
+ } while (ui->getUiState()->lastUpdate < startUpdate);
+
+ // Return to normal frame rate
+ targetFramerate = IDLE_FRAMERATE;
+ ui->setTargetFPS(targetFramerate);
+ }
+
+ // Tell EInk class to update the display
+ static_cast(dispdev)->forceDisplay();
#endif
}
@@ -1085,10 +1211,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;
}
@@ -1136,6 +1262,7 @@ int32_t Screen::runOnce()
break;
case Cmd::STOP_BLUETOOTH_PIN_SCREEN:
case Cmd::STOP_BOOT_SCREEN:
+ EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
setFrames();
break;
case Cmd::PRINT:
@@ -1161,16 +1288,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 +1313,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 +1345,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();
}
}
@@ -1234,6 +1361,63 @@ void Screen::setWelcomeFrames()
}
}
+#ifdef USE_EINK
+/// Determine which screensaver frame to use, then set the FrameCallback
+void Screen::setScreensaverFrames(FrameCallback einkScreensaver)
+{
+ // Remember current frame, restore position at power-on
+ uint8_t frameNumber = ui->getUiState()->currentFrame;
+
+ // Retain specified frame / overlay callback beyond scope of this method
+ static FrameCallback screensaverFrame;
+ static OverlayCallback screensaverOverlay;
+
+#if defined(HAS_EINK_ASYNCFULL) && defined(USE_EINK_DYNAMICDISPLAY)
+ // Join (await) a currently running async refresh, then run the post-update code.
+ // Avoid skipping of screensaver frame. Would otherwise be handled by NotifiedWorkerThread.
+ EINK_JOIN_ASYNCREFRESH(dispdev);
+#endif
+
+ // If: one-off screensaver frame passed as argument. Handles doDeepSleep()
+ if (einkScreensaver != NULL) {
+ screensaverFrame = einkScreensaver;
+ ui->setFrames(&screensaverFrame, 1);
+ }
+
+ // Else, display the usual "overlay" screensaver
+ else {
+ screensaverOverlay = drawScreensaverOverlay;
+ ui->setOverlays(&screensaverOverlay, 1);
+ }
+
+ // Request new frame, ASAP
+ setFastFramerate();
+ uint64_t startUpdate;
+ do {
+ startUpdate = millis(); // Handle impossibly unlikely corner case of a millis() overflow..
+ delay(1);
+ ui->update();
+ } while (ui->getUiState()->lastUpdate < startUpdate);
+
+ // Old EInkDisplay class
+#if !defined(USE_EINK_DYNAMICDISPLAY)
+ static_cast(dispdev)->forceDisplay(0); // Screen::forceDisplay(), but override rate-limit
+#endif
+
+ // Prepare now for next frame, shown when display wakes
+ ui->setOverlays(NULL, 0); // Clear overlay
+ setFrames(); // Return to normal display updates
+ ui->switchToFrame(frameNumber); // Attempt to return to same frame after power-on
+
+ // Pick a refresh method, for when display wakes
+#ifdef EINK_HASQUIRK_GHOSTING
+ EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // Really ugly to see ghosting from "screen paused"
+#else
+ EINK_ADD_FRAMEFLAG(dispdev, RESPONSIVE); // Really nice to wake screen with a fast-refresh
+#endif
+}
+#endif
+
// restore our regular frame list
void Screen::setFrames()
{
@@ -1248,7 +1432,7 @@ void Screen::setFrames()
#endif
// We don't show the node info our our node (if we have it yet - we should)
- size_t numMeshNodes = nodeDB.getNumMeshNodes();
+ size_t numMeshNodes = nodeDB->getNumMeshNodes();
if (numMeshNodes > 0)
numMeshNodes--;
@@ -1294,7 +1478,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 +1487,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)
@@ -1316,6 +1500,7 @@ void Screen::handleStartBluetoothPinScreen(uint32_t pin)
{
LOG_DEBUG("showing bluetooth screen\n");
showingNormalScreen = false;
+ EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
static FrameCallback frames[] = {drawFrameBluetooth};
snprintf(btPIN, sizeof(btPIN), "%06u", pin);
@@ -1324,8 +1509,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();
}
@@ -1333,6 +1518,11 @@ void Screen::handleShutdownScreen()
{
LOG_DEBUG("showing shutdown screen\n");
showingNormalScreen = false;
+#ifdef USE_EINK
+ EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
+ EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
+ handleSetOn(true); // Ensure power-on to receive deep-sleep screensaver (PowerFSM should handle?)
+#endif
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
drawFrameText(display, state, x, y, "Shutting down...");
@@ -1346,6 +1536,11 @@ void Screen::handleRebootScreen()
{
LOG_DEBUG("showing reboot screen\n");
showingNormalScreen = false;
+#ifdef USE_EINK
+ EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Use fast-refresh for next frame, no skip please
+ EINK_ADD_FRAMEFLAG(dispdev, BLOCKING); // Edge case: if this frame is promoted to COSMETIC, wait for update
+ handleSetOn(true); // Power-on to show rebooting screen (PowerFSM should handle?)
+#endif
auto frame = [](OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) -> void {
drawFrameText(display, state, x, y, "Rebooting...");
@@ -1358,6 +1553,7 @@ void Screen::handleStartFirmwareUpdateScreen()
{
LOG_DEBUG("showing firmware screen\n");
showingNormalScreen = false;
+ EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // E-Ink: Explicitly use fast-refresh for next frame
static FrameCallback frames[] = {drawFrameFirmware};
setFrameImmediateDraw(frames);
@@ -1367,17 +1563,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 +1601,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 +1619,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 +1630,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 +1646,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;
}
@@ -1470,8 +1666,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
char channelStr[20];
{
concurrency::LockGuard guard(&lock);
- auto chName = channels.getPrimaryName();
- snprintf(channelStr, sizeof(channelStr), "%s", chName);
+ snprintf(channelStr, sizeof(channelStr), "#%s", channels.getName(channels.getPrimaryIndex()));
}
// Display power status
@@ -1494,8 +1689,9 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
} else {
drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus);
}
+#if HAS_GPS
// 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) {
@@ -1504,7 +1700,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus);
}
}
-
+#endif
display->setColor(WHITE);
// Draw the channel name
display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr);
@@ -1513,7 +1709,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
#ifdef ARCH_ESP32
if (millis() - storeForwardModule->lastHeartbeat >
(storeForwardModule->heartbeatInterval * 1200)) { // no heartbeat, overlap a bit
-#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS)) && \
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgQuestionL1);
@@ -1524,7 +1720,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
imgQuestion);
#endif
} else {
-#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) || defined(HX8357_CS)) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8,
imgSFL1);
@@ -1537,7 +1733,9 @@ 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) || defined(HX8357_CS) || \
+ ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8,
imgInfoL1);
@@ -1564,7 +1762,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 +1816,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));
@@ -1691,7 +1896,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
// Show uptime as days, hours, minutes OR seconds
std::string uptime = screen->drawTimeDelta(days, hours, minutes, seconds);
- uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice);
+ uint32_t rtc_sec = getValidTime(RTCQuality::RTCQualityDevice, true); // Display local timezone
if (rtc_sec > 0) {
long hms = rtc_sec % SEC_PER_DAY;
// hms += tz.tz_dsttime * SEC_PER_HOUR;
@@ -1715,7 +1920,8 @@ 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 HAS_GPS
+ 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,11 +1930,9 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat
// Line 4
drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus);
} else {
- drawGPSpowerstat(display, x - (SCREEN_WIDTH / 4), y + FONT_HEIGHT_SMALL * 2, gpsStatus);
-#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);
}
+#endif
/* Display a heartbeat pixel that blinks every time the frame is redrawn */
#ifdef SHOW_REDRAWS
if (heartbeat)
@@ -1745,7 +1949,7 @@ int Screen::handleStatusUpdate(const meshtastic::Status *arg)
if (showingNormalScreen && nodeStatus->getLastNumTotal() != nodeStatus->getNumTotal()) {
setFrames(); // Regen the list of screens
}
- nodeDB.updateGUI = false;
+ nodeDB->updateGUI = false;
break;
}
@@ -1770,7 +1974,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);
}
}
@@ -1794,4 +1998,4 @@ int Screen::handleInputEvent(const InputEvent *event)
} // namespace graphics
#else
graphics::Screen::Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY) {}
-#endif // HAS_SCREEN
\ No newline at end of file
+#endif // HAS_SCREEN
diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h
index 554fa0aeb..2cb1cd5a9 100644
--- a/src/graphics/Screen.h
+++ b/src/graphics/Screen.h
@@ -20,7 +20,7 @@ class Screen
void setOn(bool) {}
void print(const char *) {}
void doDeepSleep() {}
- void forceDisplay() {}
+ void forceDisplay(bool forceUiUpdate = false) {}
void startBluetoothPinScreen(uint32_t pin) {}
void stopBluetoothPinScreen() {}
void startRebootScreen() {}
@@ -47,6 +47,7 @@ class Screen
#endif
#include "EInkDisplay2.h"
+#include "EInkDynamicDisplay.h"
#include "TFTDisplay.h"
#include "TypedQueue.h"
#include "commands.h"
@@ -72,6 +73,10 @@ class Screen
#define MILES_TO_FEET 5280
#endif
+// Intuitive colors. E-Ink display is inverted from OLED(?)
+#define EINK_BLACK OLEDDISPLAY_COLOR::WHITE
+#define EINK_WHITE OLEDDISPLAY_COLOR::BLACK
+
namespace graphics
{
@@ -124,6 +129,8 @@ class Screen : public concurrency::OSThread
public:
explicit Screen(ScanI2C::DeviceAddress, meshtastic_Config_DisplayConfig_OledType, OLEDDISPLAY_GEOMETRY);
+ ~Screen();
+
Screen(const Screen &) = delete;
Screen &operator=(const Screen &) = delete;
@@ -136,14 +143,14 @@ class Screen : public concurrency::OSThread
// Not thread safe - must be called before any other methods are called.
void setup();
- /// Turns the screen on/off.
- void setOn(bool on)
+ /// Turns the screen on/off. Optionally, pass a custom screensaver frame for E-Ink
+ void setOn(bool on, FrameCallback einkScreensaver = NULL)
{
if (!on)
- handleSetOn(
- false); // We handle off commands immediately, because they might be called because the CPU is shutting down
+ // We handle off commands immediately, because they might be called because the CPU is shutting down
+ handleSetOn(false, einkScreensaver);
else
- enqueueCmd(ScreenCmd{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF});
+ enqueueCmd(ScreenCmd{.cmd = Cmd::SET_ON});
}
/**
@@ -311,19 +318,26 @@ class Screen : public concurrency::OSThread
int handleInputEvent(const InputEvent *arg);
/// Used to force (super slow) eink displays to draw critical frames
- void forceDisplay();
+ void forceDisplay(bool forceUiUpdate = false);
/// Draws our SSL cert screen during boot (called from WebServer)
void setSSLFrames();
void setWelcomeFrames();
+#ifdef USE_EINK
+ /// Draw an image to remain on E-Ink display after screen off
+ void setScreensaverFrames(FrameCallback einkScreensaver = NULL);
+#endif
+
protected:
/// Updates the UI.
//
// Called periodically from the main loop.
int32_t runOnce() final;
+ bool isAUTOOled = false;
+
private:
struct ScreenCmd {
Cmd cmd;
@@ -346,7 +360,7 @@ class Screen : public concurrency::OSThread
}
// Implementations of various commands, called from doTask().
- void handleSetOn(bool on);
+ void handleSetOn(bool on, FrameCallback einkScreensaver = NULL);
void handleOnPress();
void handleShowNextFrame();
void handleShowPrevFrame();
@@ -385,22 +399,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) || defined(RAK14014)
- 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/ScreenFonts.h b/src/graphics/ScreenFonts.h
new file mode 100644
index 000000000..4b34563f7
--- /dev/null
+++ b/src/graphics/ScreenFonts.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#ifdef OLED_RU
+#include "graphics/fonts/OLEDDisplayFontsRU.h"
+#endif
+
+#ifdef OLED_UA
+#include "graphics/fonts/OLEDDisplayFontsUA.h"
+#endif
+
+#if (defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) || defined(ST7789_CS) || defined(HX8357_CS)) && \
+ !defined(DISPLAY_FORCE_SMALL_FONTS)
+// The screen is bigger so use bigger fonts
+#define FONT_SMALL ArialMT_Plain_16 // Height: 19
+#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28
+#define FONT_LARGE ArialMT_Plain_24 // Height: 28
+#else
+#ifdef OLED_RU
+#define FONT_SMALL ArialMT_Plain_10_RU
+#else
+#ifdef OLED_UA
+#define FONT_SMALL ArialMT_Plain_10_UA
+#else
+#define FONT_SMALL ArialMT_Plain_10 // Height: 13
+#endif
+#endif
+#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19
+#define FONT_LARGE ArialMT_Plain_24 // Height: 28
+#endif
+
+#define fontHeight(font) ((font)[1] + 1) // height is position 1
+
+#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL)
+#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM)
+#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE)
diff --git a/src/graphics/TFTDisplay.cpp b/src/graphics/TFTDisplay.cpp
index 63db8120a..12e549424 100644
--- a/src/graphics/TFTDisplay.cpp
+++ b/src/graphics/TFTDisplay.cpp
@@ -1,5 +1,9 @@
#include "configuration.h"
#include "main.h"
+#if ARCH_PORTDUINO
+#include "mesh_bus_spi.h"
+#include "platform/portduino/PortduinoGlue.h"
+#endif
#ifndef TFT_BACKLIGHT_ON
#define TFT_BACKLIGHT_ON HIGH
@@ -16,6 +20,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 +73,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 +91,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,11 +109,11 @@ class LGFX : public lgfx::LGFX_Device
}
};
-static LGFX tft;
+static LGFX *tft = nullptr;
#elif defined(RAK14014)
#include
-TFT_eSPI tft = TFT_eSPI();
+TFT_eSPI *tft = nullptr;
#elif defined(ST7789_CS)
#include // Graphics and font library for ST7735 driver chip
@@ -233,7 +239,7 @@ class LGFX : public lgfx::LGFX_Device
}
};
-static LGFX tft;
+static LGFX *tft = nullptr;
#elif defined(ILI9341_DRIVER)
@@ -322,23 +328,190 @@ 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 && HAS_SCREEN != 0
+#include // Graphics and font library for ST7735 driver chip
+
+class LGFX : public lgfx::LGFX_Device
+{
+ lgfx::Panel_LCD *_panel_instance;
+ lgfx::Mesh_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;
+ _bus_instance.spi_device(DisplaySPI, settingsStrings[displayspidev]);
+
+ 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;
+
+#elif defined(HX8357_CS)
+#include // Graphics and font library for HX8357 driver chip
+
+class LGFX : public lgfx::LGFX_Device
+{
+ lgfx::Panel_HX8357D _panel_instance;
+ lgfx::Bus_SPI _bus_instance;
+#if defined(USE_XPT2046)
+ lgfx::Touch_XPT2046 _touch_instance;
+#endif
+
+ public:
+ LGFX(void)
+ {
+ // Panel_HX8357D
+ {
+ // configure SPI
+ auto cfg = _bus_instance.config();
+
+ cfg.spi_host = HX8357_SPI_HOST;
+ cfg.spi_mode = 0;
+ cfg.freq_write = SPI_FREQUENCY; // SPI clock for transmission (up to 80MHz, rounded to the value obtained by dividing
+ // 80MHz by an integer)
+ cfg.freq_read = SPI_READ_FREQUENCY; // SPI clock when receiving
+ cfg.spi_3wire = false; // Set to true if reception is done on the MOSI pin
+ cfg.use_lock = true; // Set to true to use transaction locking
+ cfg.dma_channel = SPI_DMA_CH_AUTO; // SPI_DMA_CH_AUTO; // Set DMA channel to use (0=not use DMA / 1=1ch / 2=ch /
+ // SPI_DMA_CH_AUTO=auto setting)
+ cfg.pin_sclk = HX8357_SCK; // Set SPI SCLK pin number
+ cfg.pin_mosi = HX8357_MOSI; // Set SPI MOSI pin number
+ cfg.pin_miso = HX8357_MISO; // Set SPI MISO pin number (-1 = disable)
+ cfg.pin_dc = HX8357_RS; // Set SPI DC pin number (-1 = disable)
+
+ _bus_instance.config(cfg); // applies the set value to the bus.
+ _panel_instance.setBus(&_bus_instance); // set the bus on the panel.
+ }
+ {
+ // Set the display panel control.
+ auto cfg = _panel_instance.config(); // Gets a structure for display panel settings.
+
+ cfg.pin_cs = HX8357_CS; // Pin number where CS is connected (-1 = disable)
+ cfg.pin_rst = HX8357_RESET; // Pin number where RST is connected (-1 = disable)
+ cfg.pin_busy = HX8357_BUSY; // Pin number where BUSY is connected (-1 = disable)
+
+ cfg.panel_width = TFT_WIDTH; // actual displayable width
+ cfg.panel_height = TFT_HEIGHT; // actual displayable height
+ cfg.offset_x = TFT_OFFSET_X; // Panel offset amount in X direction
+ cfg.offset_y = TFT_OFFSET_Y; // Panel offset amount in Y direction
+ cfg.offset_rotation = TFT_OFFSET_ROTATION; // Rotation direction value offset 0~7 (4~7 is upside down)
+ 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 = 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;
+ cfg.bus_shared = true; // If the bus is shared with the SD card, set to true (bus control with drawJpgFile etc.)
+
+ _panel_instance.config(cfg);
+ }
+#if defined(USE_XPT2046)
+ {
+ // Configure settings for touch control.
+ auto touch_cfg = _touch_instance.config();
+
+ touch_cfg.pin_cs = TOUCH_CS;
+ touch_cfg.x_min = 0;
+ touch_cfg.x_max = TFT_HEIGHT - 1;
+ touch_cfg.y_min = 0;
+ touch_cfg.y_max = TFT_WIDTH - 1;
+ touch_cfg.pin_int = -1;
+ touch_cfg.bus_shared = true;
+ touch_cfg.offset_rotation = 1;
+
+ _touch_instance.config(touch_cfg);
+ _panel_instance.setTouch(&_touch_instance);
+ }
+#endif
+ setPanel(&_panel_instance);
+ }
+};
+
+static LGFX *tft = nullptr;
#endif
-#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014)
+#if defined(ST7735_CS) || defined(ST7789_CS) || defined(ILI9341_DRIVER) || defined(RAK14014) || defined(HX8357_CS) || \
+ (ARCH_PORTDUINO && HAS_SCREEN != 0)
#include "SPILock.h"
#include "TFTDisplay.h"
#include
+#ifdef UNPHONE
+#include "unPhone.h"
+extern unPhone unphone;
+#endif
+
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);
@@ -346,19 +519,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);
}
}
}
@@ -377,57 +557,67 @@ 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);
- }
-#endif
-#if defined(TFT_BL) && defined(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);
+#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
+ tft->wakeup();
+ tft->powerSaveOff();
+#elif 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
-
+#ifdef UNPHONE
+ unphone.backlight(true); // using unPhone library
+#endif
#ifdef RAK14014
#elif !defined(M5STACK)
- tft.setBrightness(128);
+ 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);
- }
-#endif
-#if defined(TFT_BL) && defined(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);
+#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE)
+ tft->sleep();
+ tft->powerSaveOn();
+#elif 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
+#ifdef UNPHONE
+ unphone.backlight(false); // using unPhone library
+#endif
#ifdef RAK14014
#elif !defined(M5STACK)
- tft.setBrightness(0);
+ tft->setBrightness(0);
#endif
break;
}
@@ -442,7 +632,7 @@ 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
}
@@ -450,7 +640,7 @@ bool TFTDisplay::hasTouch(void)
{
#ifdef RAK14014
#elif !defined(M5STACK)
- return tft.touch() != nullptr;
+ return tft->touch() != nullptr;
#else
return false;
#endif
@@ -460,7 +650,7 @@ bool TFTDisplay::getTouch(int16_t *x, int16_t *y)
{
#ifdef RAK14014
#elif !defined(M5STACK)
- return tft.getTouch(x, y);
+ return tft->getTouch(x, y);
#else
return false;
#endif
@@ -476,43 +666,49 @@ 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
+#ifdef UNPHONE
+ unphone.backlight(true); // using unPhone library
+ LOG_INFO("Power to TFT Backlight\n");
#endif
- tft.init();
+ tft->init();
#if defined(M5STACK)
- tft.setRotation(0);
+ tft->setRotation(0);
#elif defined(RAK14014)
- tft.setRotation(1);
- tft.setSwapBytes(true);
-// tft.fillScreen(TFT_BLACK);
-#elif defined(T_DECK) || defined(PICOMPUTER_S3)
- tft.setRotation(1); // T-Deck has the TFT in landscape
+ 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;
}
-#endif
\ No newline at end of file
+#endif
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..5c6fb4275 100644
--- a/src/graphics/images.h
+++ b/src/graphics/images.h
@@ -14,7 +14,8 @@ 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) || defined(HX8357_CS) || \
+ ARCH_PORTDUINO) && \
!defined(DISPLAY_FORCE_SMALL_FONTS)
const uint8_t imgQuestionL1[] PROGMEM = {0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff};
const uint8_t imgQuestionL2[] PROGMEM = {0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f};
diff --git a/src/graphics/img/icon.xbm b/src/graphics/img/icon.xbm
index 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/graphics/mesh_bus_spi.cpp b/src/graphics/mesh_bus_spi.cpp
new file mode 100644
index 000000000..a9536d490
--- /dev/null
+++ b/src/graphics/mesh_bus_spi.cpp
@@ -0,0 +1,188 @@
+// This code has been copied from LovyanGFX to make the SPI device selectable for touchscreens.
+// Ideally this could eventually be an inherited class from BUS_SPI,
+// but currently too many internal objects are set private.
+
+#include "configuration.h"
+#if ARCH_PORTDUINO
+#include "lgfx/v1/misc/pixelcopy.hpp"
+#include "main.h"
+#include "mesh_bus_spi.h"
+#include
+#include
+
+namespace lgfx
+{
+inline namespace v1
+{
+//----------------------------------------------------------------------------
+
+void Mesh_Bus_SPI::config(const config_t &config)
+{
+ _cfg = config;
+
+ if (_cfg.pin_dc >= 0) {
+ pinMode(_cfg.pin_dc, pin_mode_t::output);
+ gpio_hi(_cfg.pin_dc);
+ }
+}
+
+bool Mesh_Bus_SPI::init(void)
+{
+ dc_h();
+ pinMode(_cfg.pin_dc, pin_mode_t::output);
+ if (SPIName != "")
+ PrivateSPI->begin(SPIName.c_str());
+ else
+ PrivateSPI->begin();
+ return true;
+}
+
+void Mesh_Bus_SPI::release(void)
+{
+ PrivateSPI->end();
+}
+
+void Mesh_Bus_SPI::spi_device(HardwareSPI *newSPI, std::string newSPIName)
+{
+ PrivateSPI = newSPI;
+ SPIName = newSPIName;
+}
+void Mesh_Bus_SPI::beginTransaction(void)
+{
+ dc_h();
+ SPISettings setting(_cfg.freq_write, MSBFIRST, _cfg.spi_mode);
+ PrivateSPI->beginTransaction(setting);
+}
+
+void Mesh_Bus_SPI::endTransaction(void)
+{
+ PrivateSPI->endTransaction();
+ dc_h();
+}
+
+void Mesh_Bus_SPI::beginRead(void)
+{
+ PrivateSPI->endTransaction();
+ // SPISettings setting(_cfg.freq_read, BitOrder::MSBFIRST, _cfg.spi_mode, false);
+ SPISettings setting(_cfg.freq_read, MSBFIRST, _cfg.spi_mode);
+ PrivateSPI->beginTransaction(setting);
+}
+
+void Mesh_Bus_SPI::endRead(void)
+{
+ PrivateSPI->endTransaction();
+ beginTransaction();
+}
+
+void Mesh_Bus_SPI::wait(void) {}
+
+bool Mesh_Bus_SPI::busy(void) const
+{
+ return false;
+}
+
+bool Mesh_Bus_SPI::writeCommand(uint32_t data, uint_fast8_t bit_length)
+{
+ dc_l();
+ PrivateSPI->transfer((uint8_t *)&data, bit_length >> 3);
+ dc_h();
+ return true;
+}
+
+void Mesh_Bus_SPI::writeData(uint32_t data, uint_fast8_t bit_length)
+{
+ PrivateSPI->transfer((uint8_t *)&data, bit_length >> 3);
+}
+
+void Mesh_Bus_SPI::writeDataRepeat(uint32_t data, uint_fast8_t bit_length, uint32_t length)
+{
+ const uint8_t dst_bytes = bit_length >> 3;
+ uint32_t limit = (dst_bytes == 3) ? 12 : 16;
+ auto buf = _flip_buffer.getBuffer(512);
+ size_t fillpos = 0;
+ reinterpret_cast(buf)[0] = data;
+ fillpos += dst_bytes;
+ uint32_t len;
+ do {
+ len = ((length - 1) % limit) + 1;
+ if (limit <= 64)
+ limit <<= 1;
+
+ while (fillpos < len * dst_bytes) {
+ memcpy(&buf[fillpos], buf, fillpos);
+ fillpos += fillpos;
+ }
+
+ PrivateSPI->transfer(buf, len * dst_bytes);
+ } while (length -= len);
+}
+
+void Mesh_Bus_SPI::writePixels(pixelcopy_t *param, uint32_t length)
+{
+ const uint8_t dst_bytes = param->dst_bits >> 3;
+ uint32_t limit = (dst_bytes == 3) ? 12 : 16;
+ uint32_t len;
+ do {
+ len = ((length - 1) % limit) + 1;
+ if (limit <= 32)
+ limit <<= 1;
+ auto buf = _flip_buffer.getBuffer(len * dst_bytes);
+ param->fp_copy(buf, 0, len, param);
+ PrivateSPI->transfer(buf, len * dst_bytes);
+ } while (length -= len);
+}
+
+void Mesh_Bus_SPI::writeBytes(const uint8_t *data, uint32_t length, bool dc, bool use_dma)
+{
+ if (dc)
+ dc_h();
+ else
+ dc_l();
+ PrivateSPI->transfer(const_cast(data), length);
+ if (!dc)
+ dc_h();
+}
+
+uint32_t Mesh_Bus_SPI::readData(uint_fast8_t bit_length)
+{
+ uint32_t res = 0;
+ bit_length >>= 3;
+ if (!bit_length)
+ return res;
+ int idx = 0;
+ do {
+ res |= PrivateSPI->transfer(0) << idx;
+ idx += 8;
+ } while (--bit_length);
+ return res;
+}
+
+bool Mesh_Bus_SPI::readBytes(uint8_t *dst, uint32_t length, bool use_dma)
+{
+ do {
+ dst[0] = PrivateSPI->transfer(0);
+ ++dst;
+ } while (--length);
+ return true;
+}
+
+void Mesh_Bus_SPI::readPixels(void *dst, pixelcopy_t *param, uint32_t length)
+{
+ uint32_t bytes = param->src_bits >> 3;
+ uint32_t dstindex = 0;
+ uint32_t len = 4;
+ uint8_t buf[24];
+ param->src_data = buf;
+ do {
+ if (len > length)
+ len = length;
+ readBytes((uint8_t *)buf, len * bytes, true);
+ param->src_x = 0;
+ dstindex = param->fp_copy(dst, dstindex, dstindex + len, param);
+ length -= len;
+ } while (length);
+}
+
+} // namespace v1
+} // namespace lgfx
+#endif
\ No newline at end of file
diff --git a/src/graphics/mesh_bus_spi.h b/src/graphics/mesh_bus_spi.h
new file mode 100644
index 000000000..903f7ad9d
--- /dev/null
+++ b/src/graphics/mesh_bus_spi.h
@@ -0,0 +1,100 @@
+#if ARCH_PORTDUINO
+/*----------------------------------------------------------------------------/
+ Lovyan GFX - Graphics library for embedded devices.
+
+Original Source:
+ https://github.com/lovyan03/LovyanGFX/
+
+Licence:
+ [FreeBSD](https://github.com/lovyan03/LovyanGFX/blob/master/license.txt)
+
+Author:
+ [lovyan03](https://twitter.com/lovyan03)
+
+Contributors:
+ [ciniml](https://github.com/ciniml)
+ [mongonta0716](https://github.com/mongonta0716)
+ [tobozo](https://github.com/tobozo)
+/----------------------------------------------------------------------------*/
+#pragma once
+
+#include
+
+#include "lgfx/v1/Bus.hpp"
+#include "lgfx/v1/platforms/common.hpp"
+
+namespace lgfx
+{
+inline namespace v1
+{
+//----------------------------------------------------------------------------
+
+class Mesh_Bus_SPI : public IBus
+{
+ public:
+ struct config_t {
+ uint32_t freq_write = 16000000;
+ uint32_t freq_read = 8000000;
+ // bool spi_3wire = true;
+ // bool use_lock = true;
+ int16_t pin_sclk = -1;
+ int16_t pin_miso = -1;
+ int16_t pin_mosi = -1;
+ int16_t pin_dc = -1;
+ uint8_t spi_mode = 0;
+ };
+
+ const config_t &config(void) const { return _cfg; }
+
+ void config(const config_t &config);
+
+ bus_type_t busType(void) const override { return bus_type_t::bus_spi; }
+
+ bool init(void) override;
+ void release(void) override;
+ void spi_device(HardwareSPI *newSPI, std::string newSPIName);
+
+ void beginTransaction(void) override;
+ void endTransaction(void) override;
+ void wait(void) override;
+ bool busy(void) const override;
+
+ bool writeCommand(uint32_t data, uint_fast8_t bit_length) override;
+ void writeData(uint32_t data, uint_fast8_t bit_length) override;
+ void writeDataRepeat(uint32_t data, uint_fast8_t bit_length, uint32_t count) override;
+ void writePixels(pixelcopy_t *param, uint32_t length) override;
+ void writeBytes(const uint8_t *data, uint32_t length, bool dc, bool use_dma) override;
+
+ void initDMA(void) {}
+ void flush(void) {}
+ void addDMAQueue(const uint8_t *data, uint32_t length) override { writeBytes(data, length, true, true); }
+ void execDMAQueue(void) {}
+ uint8_t *getDMABuffer(uint32_t length) override { return _flip_buffer.getBuffer(length); }
+
+ void beginRead(void) override;
+ void endRead(void) override;
+ uint32_t readData(uint_fast8_t bit_length) override;
+ bool readBytes(uint8_t *dst, uint32_t length, bool use_dma) override;
+ void readPixels(void *dst, pixelcopy_t *param, uint32_t length) override;
+
+ private:
+ HardwareSPI *PrivateSPI;
+ std::string SPIName;
+ __attribute__((always_inline)) inline void dc_h(void) { gpio_hi(_cfg.pin_dc); }
+ __attribute__((always_inline)) inline void dc_l(void) { gpio_lo(_cfg.pin_dc); }
+
+ config_t _cfg;
+ FlipBuffer _flip_buffer;
+ bool _need_wait;
+ uint32_t _mask_reg_dc;
+ uint32_t _last_apb_freq = -1;
+ uint32_t _clkdiv_write;
+ uint32_t _clkdiv_read;
+ volatile uint32_t *_gpio_reg_dc_h;
+ volatile uint32_t *_gpio_reg_dc_l;
+};
+
+//----------------------------------------------------------------------------
+} // namespace v1
+} // namespace lgfx
+#endif
\ No newline at end of file
diff --git a/src/input/LinuxInput.cpp b/src/input/LinuxInput.cpp
new file mode 100644
index 000000000..1ace2044c
--- /dev/null
+++ b/src/input/LinuxInput.cpp
@@ -0,0 +1,184 @@
+#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()
+{
+ if (fd >= 0)
+ 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..43d08493c
--- /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